Compare commits

...

528 Commits

Author SHA1 Message Date
lda ff85b72899 Fix IPv6 issue in parser (#52)
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Has been cancelled Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Has been cancelled Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Has been cancelled Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Has been cancelled Details
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Has been cancelled Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Has been cancelled Details
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Has been cancelled Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Has been cancelled Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Has been cancelled Details
Fixes compilation issue in the parser (and checks IPv6 slightly more).
---

Please review the developer certificate of origin:

1. The contribution was created in whole or in part by me, and I have
the right to submit it under the open source licenses of the
Telodendria project; or
1. The contribution is based upon a previous work that, to the best of
my knowledge, is covered under an appropriate open source license and
I have the right under that license to submit that work with
modifications, whether created in whole or in part by me, under the
Telodendria project license; or
1. The contribution was provided directly to me by some other person
who certified (1), (2), or (3), and I have not modified it.
1. I understand and agree that this project and the contribution are
made public and that a record of the contribution—including all
personal information I submit with it—is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] I have read the Telodendria Project development certificate of
origin, and I certify that I have permission to submit this patch
under the conditions specified in it.

Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: #52
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-03-29 11:48:25 -05:00
lda bccbb3bcac Fix other double-free issue with router. (#53)
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Waiting to run Details
Similar issue to #33.
---

Please review the developer certificate of origin:

1. The contribution was created in whole or in part by me, and I have
the right to submit it under the open source licenses of the
Telodendria project; or
1. The contribution is based upon a previous work that, to the best of
my knowledge, is covered under an appropriate open source license and
I have the right under that license to submit that work with
modifications, whether created in whole or in part by me, under the
Telodendria project license; or
1. The contribution was provided directly to me by some other person
who certified (1), (2), or (3), and I have not modified it.
1. I understand and agree that this project and the contribution are
made public and that a record of the contribution&mdash;including all
personal information I submit with it&mdash;is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] I have read the Telodendria Project development certificate of
origin, and I certify that I have permission to submit this patch
under the conditions specified in it.

Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: #53
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-03-29 11:48:04 -05:00
Jordan Bancino dede82ad33 Update docs/dev/hosting.md
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Successful in 19s Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Successful in 24s Details
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Failing after 24s Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Successful in 31s Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Successful in 30s Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Successful in 42s Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Failing after 42s Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Successful in 50s Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Successful in 1m38s Details
2024-01-14 14:13:53 -05:00
Jordan Bancino fde2b26857 Update docs/dev/hosting.md
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Successful in 14s Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Successful in 15s Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Successful in 30s Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Successful in 26s Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Failing after 26s Details
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Failing after 23s Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Successful in 27s Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Successful in 33s Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Successful in 42s Details
2024-01-14 09:43:47 -05:00
Jordan Bancino 6305f5d76e Update docs/dev/hosting.md
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Waiting to run Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Successful in 25s Details
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Successful in 23s Details
2024-01-14 09:36:57 -05:00
Jordan Bancino 95a5f6f087 Fix compile warnings on 32-bit systems.
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Failing after 14s Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Failing after 17s Details
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Successful in 21s Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Successful in 25s Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Successful in 24s Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Successful in 26s Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Successful in 26s Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Successful in 30s Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Successful in 37s Details
2024-01-13 20:41:29 -05:00
Jordan Bancino 129802fe94 Fix ordering of LDFLAGS.
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Failing after 13s Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Failing after 18s Details
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Successful in 20s Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Failing after 25s Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Failing after 26s Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Successful in 27s Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Failing after 27s Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Successful in 30s Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Successful in 39s Details
2024-01-13 20:31:49 -05:00
Jordan Bancino c7d44866c3 Put -L before -l in LDFLAGS.
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Failing after 12s Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Failing after 17s Details
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Failing after 19s Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Failing after 23s Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Failing after 22s Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Failing after 23s Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Failing after 26s Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Failing after 28s Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Failing after 35s Details
2024-01-13 20:26:46 -05:00
Jordan Bancino 85672985eb Fix compiler warnings on 32-bit platforms in json.c.
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Failing after 14s Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Failing after 16s Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Failing after 23s Details
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Failing after 19s Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Failing after 22s Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Failing after 24s Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Failing after 24s Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Failing after 28s Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Failing after 36s Details
2024-01-13 20:25:16 -05:00
Jordan Bancino 19443a1c24 Fix unused argument error on Clang. 2024-01-13 20:25:03 -05:00
Jordan Bancino 15fb6d8c2a Make sure CI checks out submodules.
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Failing after 13s Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Failing after 18s Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Failing after 24s Details
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Successful in 21s Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Failing after 24s Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Failing after 27s Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Successful in 28s Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Successful in 29s Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Failing after 31s Details
2024-01-13 20:14:42 -05:00
Jordan Bancino cd22aea772 Require Cytoplasm to be compiled separately.
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Failing after 5s Details
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Failing after 6s Details
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Failing after 5s Details
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Failing after 8s Details
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Failing after 10s Details
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Failing after 10s Details
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Failing after 8s Details
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Failing after 12s Details
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Failing after 14s Details
Also add the Gitea CI runner jobs.
2024-01-13 20:11:58 -05:00
Jordan Bancino ae0724f01c Support building a local copy of Cytoplasm.
Previously, one would have to install Cytoplasm globally to compile
Telodendria. Now, Telodendria builds and links against its own copy
unless --cytoplasm is set to nothing.
2024-01-13 20:02:57 -05:00
Jordan Bancino e62389aa14 Make Telodendria compatible with latest Cytoplasm.
This also brings Telodendria to C99 compliance.
2024-01-13 20:02:07 -05:00
Jordan Bancino f2a4a64b27 Add Cytoplasm as a submodule of Telodendria. 2024-01-13 18:05:00 -05:00
Jordan Bancino 258f509413 Update docs/dev/hosting.md 2024-01-13 10:09:39 -05:00
Jordan Bancino 2e92daeb00 Update docs/dev/hosting.md 2024-01-13 10:06:06 -05:00
Jordan Bancino ac7ea4dec1 Add docs/dev/hosting.md 2024-01-13 09:51:14 -05:00
Jordan Bancino 54420f0036 Improvements to #44: Implement #22 and #9 (#51)
This pull request makes a very small commit on top of #44.

Closes #44.
Closes #9.
Closes #22.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: lda <lda@freetards.xyz>
Co-authored-by: lda <lda@noreply.git.telodendria.io>
Reviewed-on: #51
2024-01-11 19:33:50 -05:00
lda 243de4f1a0 Add new entry to CONTRIBUTORS.txt (#50)
Reviewed-on: #50
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-01-06 10:02:53 -05:00
Jordan Bancino 35b9ef51f9 PID File: Properly initialize variables in Main(). 2024-01-05 21:02:45 -05:00
Jordan Bancino 83eb69f2cd Use j2s for the Config API. (#49)
Closes #7.
Closes #46.
Closes #47.

This pull request makes some minor on top of #46.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-authored-by: lda <lda@freetards.xyz>
Co-authored-by: lda <lda@noreply.git.telodendria.io>
Reviewed-on: #49
2024-01-05 21:00:27 -05:00
Jordan Bancino 22d0e88dde Update copyright year in code. 2024-01-05 20:23:27 -05:00
Jordan Bancino f83be63d53 Add CONTRIBUTORS.txt and make a note of it in the LICENSE.txt and documentation. 2024-01-05 18:57:19 -05:00
lda af4fd5dceb Update licensing text for 2024 (#48)
Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: #48
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-01-04 19:32:03 -05:00
lda 2c6d5194d2 Set an error message with MatrixErrorCreate whenever applicable (#45)
Closes #6.

Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: #45
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2023-12-02 10:24:08 -05:00
Jordan Bancino 0a91a0c40b Remove site files & update README with current site.
The website is now managed in Grav.
2023-11-21 09:16:50 -05:00
Jordan Bancino d3dcf334f0 Update change log. 2023-11-21 09:14:03 -05:00
lda 3dae19e82d Refactor code to comply with #8 (#39)
Reviewed-on: #39
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2023-11-21 09:11:45 -05:00
Jordan Bancino 2d30719be4 Implement Registration Token Administrator API (#43)
This pull request fully implements and documents all of the registration token administrator API endpoints.

**NOTE:** There is a memory leak when listing all of the registration tokens. Debug this before merging.
 
~~Closes~~ Supersedes #37.

Closes #26.

This pull request is based off of #37, which addresses #26. This pull makes a number of improvements to the logic, organization, and behavior of the API endpoints.

Co-authored-by: hatkid <daichi93100@gmail.com>
Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-authored-by: lda <lda@freetards.xyz>
Co-authored-by: Load Accumulator <lda@freetards.xyz>
Reviewed-on: #43
2023-11-10 09:30:53 -05:00
Jordan Bancino 503bfb6104 Suppress warnings of unused variables.
I will use them eventually when I implement all these functions.

I'm probably going to start CI work here soon, so I don't want warnings showing up in the logs unless they're important.
2023-11-08 15:17:27 -05:00
Jordan Bancino 77800e4117 Housekeeping tasks with documentation and versioning. 2023-11-08 14:30:45 -05:00
Jordan Bancino db4ae0408c Fix admin endpoint names in documentation 2023-11-08 11:21:20 -05:00
Jordan Bancino edee1288d8 Remove unused global variables. 2023-11-08 09:45:49 -05:00
Jordan Bancino 4b90800a2b Fix leak in RouteConfig, update documentation.
Closes #15.
2023-11-07 01:26:26 -05:00
Jordan Bancino 6e7f170768 Start working on #15. 2023-11-06 20:42:39 -05:00
Jordan Bancino 1f02f3c2a2 Allow customization of compiler in configure. 2023-11-04 15:56:57 -04:00
Jordan Bancino 582c79b608 Schema are now generated out-of-tree.
Closes #40.
2023-11-04 15:28:20 -04:00
Jordan Bancino 42a901b7f5 Update docs/dev/rationale.md 2023-11-01 20:35:48 -04:00
Jordan Bancino 1fee47a628 Use `Makefile`s instead of a custom script (#38)
This pull request also requires the use of the external [Cytoplasm](/Telodendria/Cytoplasm) repository by removing the in-tree copy of Cytoplasm. The increased modularity requires a little more complex build process, but is overall better. Closes #19

The appropriate documentation has been updated. Closes #18

---

Please review the developer certificate of origin:

1. The contribution was created in whole or in part by me, and I have
the right to submit it under the open source licenses of the
Telodendria project; or
1. The contribution is based upon a previous work that, to the best of
my knowledge, is covered under an appropriate open source license and
I have the right under that license to submit that work with
modifications, whether created in whole or in part by me, under the
Telodendria project license; or
1. The contribution was provided directly to me by some other person
who certified (1), (2), or (3), and I have not modified it.
1. I understand and agree that this project and the contribution are
made public and that a record of the contribution&mdash;including all
personal information I submit with it&mdash;is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] I have read the Telodendria Project development certificate of
origin, and I certify that I have permission to submit this patch
under the conditions specified in it.

Reviewed-on: #38
2023-11-01 12:27:45 -04:00
Jordan Bancino 6377689a83 Update docs/CHANGELOG.md 2023-11-01 12:16:03 -04:00
Jordan Bancino 23237f97b5 Format source code and apply license. 2023-09-28 19:16:45 -04:00
Jordan Bancino 82ae2d4e41 Update 'docs/CHANGELOG.md' 2023-09-25 16:18:35 -04:00
Jordan Bancino 7d3d84d969 Move Admin routes to /_telodendria/admin/v1 (with version number)
Closes #16.
2023-09-25 16:10:44 -04:00
Jordan Bancino bf1ce839d0 If HOST_NAME_MAX is undefined, use _POSIX_HOST_NAME_MAX.
Probably good enough. Closes #2
2023-09-25 16:02:09 -04:00
Jordan Bancino f8d3e54fec Use int instead of char in EvalExpr.
Closes #5
2023-09-25 15:58:43 -04:00
lda cb41716bf3 Implement #27 (#36)
Implements #27.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: Telodendria/telodendria#36
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-committed-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
2023-09-25 09:39:21 -04:00
Jordan Bancino 6247085df1 Add change log entry for #35. 2023-09-11 11:01:50 -04:00
lda 0172fa083b Fixes issue #33 related to a memory issue, and format some code. (#35)
Fixes #33.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Reviewed-on: Telodendria/telodendria#35
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-committed-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
2023-09-11 10:57:16 -04:00
Jordan Bancino 0b820b80f7 Convert configuration documentation. 2023-09-09 17:43:43 -04:00
Jordan Bancino 6dcfa7dc02 Remove `send-patch` and `tp`. See #20. 2023-09-09 16:58:47 -04:00
Jordan Bancino 024482d4e8 Put the finishing touches on `CONTRIBUTING.md`. 2023-09-09 16:57:52 -04:00
Jordan Bancino 7a091c5b93 Add contributing documentation. 2023-09-09 16:50:38 -04:00
Jordan Bancino 9e8523d92e Fix issue config. 2023-09-09 15:39:12 -04:00
Jordan Bancino 419017bcc9 Don't allow blank issues. 2023-09-09 15:36:58 -04:00
Jordan Bancino 1a009e87c3 Fix a few small bugs in Gitea templates. 2023-09-09 15:32:43 -04:00
Jordan Bancino 08594e0fb1 Add Gitea issue templates. 2023-09-09 15:28:33 -04:00
Jordan Bancino 56508afe4c Merge branch 'master' of https://git.telodendria.io/telodendria/telodendria 2023-09-09 00:12:07 -04:00
Jordan Bancino ac3582ddeb Fix table rendering in stats.md 2023-09-09 00:12:00 -04:00
Jordan Bancino 61f7ab1040 Fix table rendering in stats.md 2023-09-09 00:11:32 -04:00
Jordan Bancino a672c05112 Finish moving over current Administrator API documentation. 2023-09-09 00:03:09 -04:00
Jordan Bancino 09023089f5 Another typo. 2023-09-08 23:27:55 -04:00
Jordan Bancino 6d6fd1645c Fix typo in privileges.md. 2023-09-08 23:27:14 -04:00
Jordan Bancino 043c2e9e33 Add privileges documentation. 2023-09-08 23:26:47 -04:00
Jordan Bancino 9e2f047e82 Fix typo in docs/user/admin/README.md 2023-09-08 17:23:06 -04:00
Jordan Bancino c78d075a93 Add admin documentation home page. 2023-09-08 17:22:24 -04:00
Jordan Bancino e4a217550f Add logo and center title. 2023-09-08 16:40:33 -04:00
Jordan Bancino d50372a91a Add technical rationale document. 2023-09-08 16:34:27 -04:00
Jordan Bancino fa3b5e95bd Add a nice README which will serve as the basis for the website. 2023-09-08 16:04:48 -04:00
Jordan Bancino 7033a1f0b1 Add repository structure documentation. 2023-09-08 12:12:07 -04:00
Jordan Bancino f6c54cbc7f Add setup documentation. 2023-09-08 11:58:54 -04:00
Jordan Bancino 3cb04417ff Add porting instructions. 2023-09-07 21:53:22 -04:00
Jordan Bancino 93e6582db5 Add usage and install documentation. 2023-09-07 21:10:46 -04:00
Jordan Bancino ee62d31c68 Add documentation home page. 2023-09-07 20:23:40 -04:00
Jordan Bancino 4bd527aa9a Remove old change log. 2023-09-07 20:06:12 -04:00
Jordan Bancino 79ce36c860 Add `CHANGELOG.md` 2023-09-07 19:53:56 -04:00
Jordan Bancino be3ee54bd8 Merge pull request 'added git ignore and removed cvs ignore files' (#29) from array-in-a-matrix/telodendria:master into master
Reviewed-on: Telodendria/telodendria#29
2023-09-06 14:37:29 -04:00
array-in-a-matrix 5367ffca96 replaced .cvsignore with .gitignore 2023-09-06 11:45:44 -04:00
Jordan Bancino c5b2fcf586 Convert Cytoplasm README to markdown. 2023-09-05 22:10:33 -04:00
Jordan Bancino 2b43a93524 Break proposals/admin.7 into multiple issues.
Closes #17
2023-09-05 21:54:26 -04:00
Jordan Bancino fd28f97449 Finish remaining issues in TODO.txt and add docs/ROADMAP.md. 2023-09-05 21:30:09 -04:00
Jordan Bancino 745a208f14 Move over some more issues. 2023-09-05 19:31:46 -04:00
Jordan Bancino 8843b34ba6 Merge branch 'master' of https://git.telodendria.io/telodendria/telodendria 2023-09-05 17:11:13 -04:00
Jordan Bancino b059f966f1 Make progress toward migrating TODO.txt to issues.
Still have a ways to go, but wanted to save my progress.
2023-09-05 17:09:58 -04:00
Jordan Bancino 8bf8afd91d Merge pull request '[MOD] Make the README(Markdown) file reflect the current situation.' (#11) from lda/telodendria:lda-dev into master
Reviewed-on: Telodendria/telodendria#11
2023-09-05 16:34:04 -04:00
lda 6db3f3c612
[MOD] Make the README(Markdown) file reflect the current situation.
Signed-off-by: LoaD Accumulator <lda@freetards.xyz>
2023-09-05 22:25:39 +02:00
Jordan Bancino fd1172ff56 Summarize Matrix room activity in TODO.txt
I really want to get back to work on Telodendria. I need to find some
more time for it.
2023-09-04 13:35:28 +00:00
Jordan Bancino 468656eee6 Apply #72 2023-09-04 13:30:22 +00:00
Jordan Bancino b625655439 Fix memory leak in some generated code. 2023-08-17 18:09:53 +00:00
Jordan Bancino dab666c969 Update documentation on UtilServerTs(). 2023-08-17 18:09:20 +00:00
Jordan Bancino 36413b4dca Ignore special case in Int64Str. This is hack but good enough for now. 2023-08-13 12:33:18 +00:00
Jordan Bancino 1c32e18c74 Json now uses Int64 for integers.
This should fix all timestamp errors on 32-bit systems in Cytoplasm and
Telodendria.
2023-08-13 03:11:40 +00:00
Jordan Bancino 2382638005 Make all timestamps use UInt64. 2023-08-12 23:02:06 +00:00
Jordan Bancino 38a303da91 Allow forcing the emulation mode for testing purposes. 2023-08-12 21:51:51 +00:00
Jordan Bancino 2f76d5b9ae Add signed 64-bit integer support. 2023-08-12 19:59:16 +00:00
Jordan Bancino 93c4b6bfc4 Add 64-bit emulation for platforms that don't have a native 64-bit type. 2023-08-12 16:30:24 +00:00
Jordan Bancino d565640455 Add schema for PDUs v1 and v3, the only unique PDU formats.
All other room versions use one of these two PDU formats.
2023-08-10 00:08:33 +00:00
Jordan Bancino bc71a7ec01 Add admin privilege to manage room aliases. 2023-08-09 15:50:03 +00:00
Jordan Bancino 69d28f39d1 Begin working on room aliases.
These endpoints will help me build out some functionality that will be
needed when actually implementing rooms.
2023-08-09 15:19:45 +00:00
Jordan Bancino e0af88145e Add RouteCreateRoom stub. 2023-08-08 15:11:50 +00:00
Jordan Bancino 6ef965d1e0 Apply patch to make MatrixErrorCreate() take a custom message. 2023-08-05 19:26:03 +00:00
Jordan Bancino 28d9e1cb3b Add RoomCreateRequest schema. 2023-08-05 15:00:52 +00:00
Jordan Bancino 78302d6320 Print admin registration token to configured log, not default stdout.
If Telodendria is started as a daemon and is logging to the syslog, we
want the token to show up in the syslog.
2023-08-05 14:12:02 +00:00
Jordan Bancino 6ec87b8d76 Properly exclude Schema directories from showing up in CVS. 2023-08-05 14:03:08 +00:00
Jordan Bancino 5f3220372e Implement filter validation by using j2s. 2023-08-05 13:46:23 +00:00
Jordan Bancino 77d71989df Don't install configuration files. 2023-08-04 21:15:49 +00:00
Jordan Bancino e9600a21e9 Add support for arrays of primitives. 2023-08-04 20:47:36 +00:00
Jordan Bancino 942d2aad18 Fix memory leaks and remove logging messages. 2023-08-01 22:36:52 +00:00
Jordan Bancino aeaa8487c3 Add leaky Cytoplasm JSON -> Struct code generator.
It is basically complete, I just have to finish cleaning up some of the
memory leaks and remove the log messages.
2023-08-01 20:23:19 +00:00
Jordan Bancino ed37afe564 Add [time] to interpolate dates and times using strftime(). 2023-07-29 18:27:35 +00:00
Jordan Bancino 2e193d4bcf Format code. 2023-07-29 18:27:17 +00:00
Jordan Bancino d22baf440f Update TODO. 2023-07-29 15:50:45 +00:00
Jordan Bancino c467d0913d Add tp, a simple but powerful text preprocessor.
I might rename it, and I have yet to document it in a man page, but I have
found this extremely useful so I thought I'd give it a home here.
2023-07-29 00:04:42 +00:00
Jordan Bancino 68b644a4f2 Add HashMapKeys() and HashMapValues() functions for convenience. 2023-07-18 00:15:29 +00:00
Jordan Bancino b65394ab50 Add basic Graph API.
This is going to be useful with state resolution and dependency ordering,
both of which will be crutial components of Telodendria.
2023-07-16 01:12:56 +00:00
Jordan Bancino a958c4a529 Apply #71: Add ArrayUnique() function. 2023-07-15 22:27:24 +00:00
Jordan Bancino c96ac30f28 Fix Unicode handling in Json, don't sign-extend bytes in Stream. 2023-07-15 17:57:21 +00:00
Jordan Bancino a4330123b9 Add EventRedact() prototype. 2023-07-09 23:18:18 +00:00
Jordan Bancino b1c23ee53a Begin prototyping state resolution API. 2023-07-07 03:15:13 +00:00
Jordan Bancino bc8283f844 Begin prototyping room API. 2023-07-07 02:56:09 +00:00
Jordan Bancino 5f34b846ee Fix stable release version scheme (more details in an upcoming newsletter) 2023-07-01 20:18:59 +00:00
Jordan Bancino 3054a80906 Fix Unicode handling. 2023-06-21 02:49:58 +00:00
Jordan Bancino 539fde773f Fix CanonicalJson and ArraySort. 2023-06-21 02:37:56 +00:00
Jordan Bancino 1fa07d2d3c Update TODO. 2023-06-18 03:25:03 +00:00
Jordan Bancino 80da024e4e Update change log. 2023-06-18 03:19:12 +00:00
Jordan Bancino e3badbd55c Format source code. 2023-06-18 02:53:52 +00:00
Jordan Bancino 861d4146c0 Add Filter API to validate filters and process events. 2023-06-18 02:53:06 +00:00
Jordan Bancino f9e1250d47 Begin working on filtering. 2023-06-17 19:09:06 +00:00
Jordan Bancino 4e184102cb Add Sha1 implementation. 2023-06-17 17:36:46 +00:00
Jordan Bancino 8bda70b1fb Refactor Sha API to return raw bytes, added Sha1() function. 2023-06-17 17:36:11 +00:00
Jordan Bancino 8f0d197480 Bump version: v0.3.0 -> v0.4.0 2023-06-16 16:01:19 +00:00
Jordan Bancino b9641e89d6 Update TODO 2023-06-13 14:06:05 +00:00
Jordan Bancino 1381a31cbd Update TODO. 2023-06-13 14:05:34 +00:00
Jordan Bancino c3287c1674 Update TODO. 2023-06-12 18:00:26 +00:00
Jordan Bancino 071a86114c Apply #70: Add StrLower() function. 2023-06-12 14:10:59 +00:00
Jordan Bancino 1f14169284 Apply #70: Fix warnings of unused variables. 2023-06-12 14:10:08 +00:00
Jordan Bancino 2f946848cb Telodendria Bot can now send messages again.
The problem here is that some Matrix homeservers reject requests that don't
have a Content-Length. http was not sending a Content-Length because it was
reading from standard input. By reading from an actual file, we can actually
easily get the size of the file to send as the Content-Length.
2023-06-12 14:01:56 +00:00
Jordan Bancino 2b3d0aaeaf Prepare for v0.3.0 release.
Not exactly as complete as I initially imagined, but I want to get something
out because I've been spending too much time on the same issues.
2023-06-10 13:29:56 +00:00
Jordan Bancino af15234799 Make the debug server more accurately mimic the actual server. 2023-06-10 13:16:41 +00:00
Jordan Bancino 971f099bb6 Fix potential race condition. 2023-06-09 19:27:49 +00:00
Jordan Bancino 96a1d3c3c4 Move stream assignment; this makes debugging easier. 2023-06-04 18:50:02 +00:00
Jordan Bancino 095e05e927 Make Memory use a recursive mutex.
This allows some additional operations to be performed inside the memory
hooks, although it's still a bad idea to allocate or free memory while
inside the hook.
2023-06-04 18:44:37 +00:00
Jordan Bancino c511ca9f0f Add a recipe to build the examples. 2023-05-30 13:20:23 +00:00
Jordan Bancino faaa12c51f Re-add memory bounds checking.
Also fixed a recursive lock error in some configurations, and replaced
a usage of strcpy() with strncpy().
2023-05-29 23:53:17 +00:00
Jordan Bancino 88f73a6131 Fix a few segfaults in http and json. 2023-05-29 22:48:50 +00:00
Jordan Bancino c206ec495d Format code. 2023-05-27 18:22:38 +00:00
Jordan Bancino 27e40135ad Log a message stating that TLS is disabled if the config requests it. 2023-05-27 17:55:49 +00:00
Jordan Bancino 5df684b609 Show human-readable thread ID in log messages.
This helps debug some multithreading issues.
2023-05-27 17:10:07 +00:00
Jordan Bancino b5b1a021d8 It appears that OpenSSL closes the socket descriptor for us.
When we close it again manually, we may actually be closing another thread's
file descriptor, which causes all kinds of problems.
2023-05-27 17:03:40 +00:00
Jordan Bancino 54924b9444 Fix potential write out of bounds. 2023-05-26 23:47:36 +00:00
Jordan Bancino 42526c95bb Remove memory bound checking logic.
While this was nifty and impressive, it seems like it is too easy to mess
up, and is too platform-specific.
2023-05-26 23:10:00 +00:00
Jordan Bancino 34f33a1c1b Fix SIGBUS on some platforms. Memory alignment matters!
This is a temporary fix; I'm not convinced it will work on all platforms,
so I will probably end up removing all of the memory boundary code
altogether.
2023-05-26 23:05:35 +00:00
Jordan Bancino d24c1161f6 Add a Cytoplasm example, which I'll use to debug some stuff. 2023-05-26 22:25:03 +00:00
Jordan Bancino d81600d944 Fix more memory leaks in json.
By doing a little reference tracking, we can make sure we don't miss any
JsonValues that may be floating around by the time we're done.
2023-05-26 22:11:07 +00:00
Jordan Bancino efb27c9db8 Push Main return value into MainArgs.
This prevents weird things from happing on some architectures.
2023-05-26 21:05:06 +00:00
Jordan Bancino 3b659ce09e Adjust milestone requirements. I really want v0.3.0 out soon. 2023-05-26 19:21:25 +00:00
Jordan Bancino 34eb9ff670 Report memory hook issues as errors, segfault on error. 2023-05-25 18:26:17 +00:00
Jordan Bancino e87a0647e0 Fix incorrect block size. 2023-05-25 00:03:17 +00:00
Jordan Bancino 836229fd1a Don't log anything at the end of main; user may have redirected and closeed stream.
This would cause a segfault.
2023-05-24 23:32:53 +00:00
Jordan Bancino 2693b89598 Add sane default memory hook to Cytoplasm, fix more leaks in json. 2023-05-23 22:43:37 +00:00
Jordan Bancino e22cf38eac Fix leaks in json. 2023-05-23 20:34:27 +00:00
Jordan Bancino b1049a9a70 Fix leak in http. 2023-05-23 20:19:27 +00:00
Jordan Bancino 441599b088 Don't leak so much memory. 2023-05-23 17:53:19 +00:00
Jordan Bancino 42191ec03f Properly initialize and destroy logging mutex in LogConfigCreate() and
LogConfigFree(), respectively.
2023-05-23 17:42:08 +00:00
Jordan Bancino a4f369a0a9 Fix leak in HttpClient. 2023-05-21 13:55:49 +00:00
Jordan Bancino 4cc876eb10 Fix warning about integer size mismatch. 2023-05-21 13:35:56 +00:00
Jordan Bancino 51b9e2eaed Clean Cytoplasm when td clean is run. 2023-05-21 13:34:05 +00:00
Jordan Bancino e71ffec164 Add some basic heap memory bounds protection. 2023-05-21 13:24:00 +00:00
Jordan Bancino d38ec7cb38 Document hdoc 2023-05-21 00:21:39 +00:00
Jordan Bancino 896f561213 Apparently this expansion syntax only works on ksh. 2023-05-20 21:38:03 +00:00
Jordan Bancino f2f972bb9d Fix memory leaks in hdoc. 2023-05-15 22:38:52 +00:00
Jordan Bancino ff0a9f33b8 Update TODO. 2023-05-15 01:43:46 +00:00
Jordan Bancino 4043285413 Telodendria and all tools now use the Cytoplasm runtime stub.
This reduces the amount of manual work programs have to do, and gives us
some free features, like automatic leak reports in all tools.
2023-05-14 22:15:48 +00:00
Jordan Bancino 8021cff122 Make Telodendria use Cytoplasm.
This commit removes all the duplicate code and makes Telodendria use the
new Cytoplasm library.
2023-05-14 19:35:23 +00:00
Jordan Bancino 5c8a42117c Import new Cytoplasm library based off of code from Telodendria.
Telodendria doesn't use this library yet, but it will soon.
2023-05-13 17:30:09 +00:00
Jordan Bancino a1e3bd7d8e Clean up TODO 2023-05-11 12:00:44 +00:00
Jordan Bancino 4d9c907b58 Attach device ID to authenticated user.
Now RouteWhoAmI can use UserAuthenticate just like the other endpoints.
2023-05-11 03:03:40 +00:00
Jordan Bancino c1c57fd4cf Make RandState thread-specific. 2023-05-11 02:17:04 +00:00
Jordan Bancino 609890654e Fix handling of STATIC and broken compilation on GNU systems. 2023-05-10 03:32:56 +00:00
Jordan Bancino 31866a14b4 Remove some extraneous whitespace from Util.c, not sure how that got there. 2023-05-07 12:08:26 +00:00
Jordan Bancino e6dd20e2b2 Update changelog. 2023-05-07 03:21:52 +00:00
Jordan Bancino ebc3da9b23 Fix some compile warnings. 2023-05-06 23:06:13 +00:00
Jordan Bancino a2eec3946c I missed an instance of strcmp() 2023-05-06 23:02:46 +00:00
Jordan Bancino 0b11b97022 Add StrEquals(), as equality checking is the most common use for strcmp().
This allows us to get rid of the hideous MATRIX_PATH_PART_EQUALS macro,
and prevents inconsistent usage of strcmp() (for example, !strcmp() vs
strcmp() == 0).

StrEquals() also has sensible behavior for dealing with NULL strings (it
doesn't just segfault like strcmp()).
2023-05-06 22:34:36 +00:00
Jordan Bancino 0e69a12784 Register route. 2023-05-06 22:06:54 +00:00
Jordan Bancino 2a87583d2f Add /_matrix/client/v3/account/deactivate 2023-05-06 22:06:23 +00:00
Jordan Bancino 95ceba0645 Add length calculations to JsonEncode() so we can set Content-Length. 2023-05-06 19:23:13 +00:00
Jordan Bancino bbea55be6c Document http-debug-server, fixed typo in HttpServer docs. 2023-05-06 18:21:21 +00:00
Jordan Bancino 3d9a7664b1 Add support for building static and shared libraries. 2023-05-06 17:04:35 +00:00
Jordan Bancino cb7ee91908 Update telodendria(8) 2023-04-30 02:20:59 +00:00
Jordan Bancino f1f66c6331 Fix the Makefile by sourcing tools/env.sh if TELODENDRIA_ENV is unset. 2023-04-30 01:47:27 +00:00
Jordan Bancino abbbfe4d7f Replace all usages of curl in tp, document tp and tt. 2023-04-29 23:28:33 +00:00
Jordan Bancino 872fa1aa66 More documentation. It never ends! 2023-04-29 19:52:51 +00:00
Jordan Bancino a08018870e Remove obsolete documentation. 2023-04-29 18:47:20 +00:00
Jordan Bancino e0c94d7bd2 Site now pulls built man pages. 2023-04-29 18:39:01 +00:00
Jordan Bancino f819093b7d Don't spew unknown expression warnings if @suppress-warnings is set. 2023-04-29 15:28:42 +00:00
Jordan Bancino 0b1b4a8b29 Cope with preprocessor macro usage at the top level of the header.
This workaround allows us to parse the Routes.h properly, although it
notably lacks support for multi-word unknown expressions.
2023-04-29 15:24:46 +00:00
Jordan Bancino a3cc06ff2a Fix line numbers when parsing multi-line macros. 2023-04-29 15:16:58 +00:00
Jordan Bancino 2d8d5244c4 Document some new headers. 2023-04-29 15:04:16 +00:00
Jordan Bancino b70c3f0bed Finish converting all existing documentation. Next up is writing new docs. 2023-04-29 02:54:49 +00:00
Jordan Bancino 71fa96d10d Format source code. 2023-04-27 20:05:08 +00:00
Jordan Bancino a00ded6d06 Add support for parsing global variables, not just function declarations. 2023-04-27 20:04:15 +00:00
Jordan Bancino 9292f1d9da Add docs recipe for generating documentation from the headers.
Note that since all the headers are not documented yet, this will fail
because an undocumented function is a fatal error in hdoc.
2023-04-27 18:39:05 +00:00
Jordan Bancino 9880aac674 Split format recipe into format and license.
Format formats only C source code. License applies the license file to
C source code *and* header files.
2023-04-27 18:03:32 +00:00
Jordan Bancino 24a03ba126 Added some more header documentation. 2023-04-27 18:00:26 +00:00
Jordan Bancino 95cb14213f Add support for return types that are const, structs, or enums. 2023-04-27 16:02:15 +00:00
Jordan Bancino 6e976a2b8d Fix line counts on syntax errors. 2023-04-27 15:54:21 +00:00
Jordan Bancino 72467f6503 Begin moving documentation to headers. 2023-04-27 03:08:39 +00:00
Jordan Bancino 3b06ab120b Add some flags to hdoc to make it more useful. 2023-04-27 02:30:44 +00:00
Jordan Bancino 2447bb63cc Add hdoc, a simple tool for generating documentation from a C header.
This is a very early prototype. It works, but it is probably not efficient
or reliable. However, the documentation format it parses is stable, so I
will begin moving the documentation into the headers.
2023-04-27 01:34:49 +00:00
Jordan Bancino fdcf7ec065 Update changelog, add LD_EXTRA variable in td. 2023-04-26 01:43:32 +00:00
Jordan Bancino 459b2e856f Format source code. 2023-04-25 22:13:28 +00:00
Jordan Bancino 96ca9a725d Finish up capabilities for now. 2023-04-25 22:11:34 +00:00
Jordan Bancino e1367d5bff Use snprintf() instead of sprintf().
All linker warnings should now be eliminated.
2023-04-25 22:01:58 +00:00
Jordan Bancino d83db35df0 Use strncpy() instead of strcpy().
The OpenBSD linker is complaining about it. Even though every single
case strcpy() was used is safe, strncpy() provides a little bit of extra
security, and makes the linker happy.
2023-04-25 21:54:51 +00:00
Jordan Bancino d933d12e1b Make Rand use a provided Mersenne Twister.
This implementation is loosely inspired by the original paper on the
Mersenne Twister, and borrows code from a public-domain implementation of
it, adapting it to fit the style of Telodendria's code, and fixing a few
bugs regarding the size of the data type used.

Neither C nor POSIX provide a good, thread-safe pseudorandom number
generator. The OpenBSD linker started complaining about the use of
rand_r(), and no standard alternative presented itself as worthy of
consideration, so I finally decided it was time to roll my own PRNG.
2023-04-25 21:28:55 +00:00
Jordan Bancino 098eed44a0 Fix bug in MatrixWellKnown(), begin /_matrix/client/v3/capabilities 2023-04-25 18:37:25 +00:00
Jordan Bancino 2ac08ad74d Clean out finished TODO items. 2023-04-25 01:54:03 +00:00
Jordan Bancino 3e4698bf72 Update porting documentation. 2023-04-24 22:24:32 +00:00
Jordan Bancino 38565d4aa6 Update changelog. 2023-04-24 21:21:25 +00:00
Jordan Bancino 05cf076ebc Begin debugging OpenSSL. 2023-04-24 17:05:59 +00:00
Jordan Bancino a525830b64 Telodendria now responds correctly to SIGTERM. 2023-04-24 16:23:50 +00:00
Jordan Bancino 279f261aed Fix race conditions that are possible in Db.
This is accomplished by locking the entire database, and keeping it locked
until the last reference is unlocked. We get rid of per-reference locks,
because those are what cause race conditions.
2023-04-24 16:19:17 +00:00
Jordan Bancino 313249ca88 Fixed some caching bugs in Db.
Note that Db has the potential to deadlock when caching is being used,
and when caching isn't being used, an inconsistent state can occur. Future
changes to Db will fix both of these issues.
2023-04-24 15:43:24 +00:00
Jordan Bancino 737e060243 Finish up Uia fallback. 2023-04-22 15:40:25 +00:00
Jordan Bancino 5431c2cd90 Fix double-free in UiaCleanup(). 2023-04-22 13:45:01 +00:00
Jordan Bancino 2bbe13aaf0 Fix error setting up UIA flows. 2023-04-22 02:06:22 +00:00
Jordan Bancino 866343071a Continued work on UIA fallback. 2023-04-22 00:59:03 +00:00
Jordan Bancino 6a5d89e14b Work on implementing user interactive authentication fallback. 2023-04-21 02:13:06 +00:00
Jordan Bancino fb24f93aaa Fix bug with TLS where cert and key files were used after freeing them. 2023-04-20 21:12:08 +00:00
Jordan Bancino 0b7282c36a Fix build errors on some compilers due to missing symbols. 2023-04-20 19:19:23 +00:00
Jordan Bancino ec09882dbe Fix warnings about memset() 2023-04-20 17:50:22 +00:00
Jordan Bancino 3b28af2031 Fix warning on some compilers. 2023-04-20 17:40:14 +00:00
Jordan Bancino 19b0dcac5a Don't build the routing tree until absolutely necessary.
Saves us a lot of time and memory in case of an error.
2023-04-20 17:37:18 +00:00
Jordan Bancino 4a5c7480aa Fix up a few small warnings. 2023-04-20 13:49:25 +00:00
Jordan Bancino 687b89a83a Add some more user documentation to clarify how configuration works. 2023-04-20 01:39:09 +00:00
Jordan Bancino 25b7c0d059 Begin documenting Admin API. 2023-04-19 21:45:15 +00:00
Jordan Bancino ffeb45375e Implement /_telodendria/admin/privileges
Note that it's not exactly as the proposal defines it; theres a bit more
nuance that will be documented soon.
2023-04-19 18:52:05 +00:00
Jordan Bancino ae38791df2 Format code, remove dataDir from sample production.conf 2023-04-19 02:07:38 +00:00
Jordan Bancino 0cca38115a Move configuration to database, add process control API, fix memory leaks. 2023-04-19 00:33:38 +00:00
Jordan Bancino ff4d265dcc Registration tokens now determine what privileges a user gets. 2023-04-16 18:32:22 +00:00
Jordan Bancino 582df63a31 Format source code. 2023-04-16 17:51:52 +00:00
Jordan Bancino 4ee66ae3c7 Begin implementing user privileges. 2023-04-16 17:51:03 +00:00
Jordan Bancino 768ecda41a Break up todo items into more milestones. 2023-04-15 13:31:08 +00:00
Jordan Bancino 6ca1265076 Begin working on User-Interactive authentication fallback. 2023-04-15 02:36:28 +00:00
Jordan Bancino e882693c78 Add route for requestToken endpoints. 2023-04-14 23:59:40 +00:00
Jordan Bancino b21d018daa Move router building function into a more sensible location. 2023-04-14 21:37:00 +00:00
Jordan Bancino 83971dfaff Refactor routing system to use HttpRouter. 2023-04-14 21:20:56 +00:00
Jordan Bancino a90f7c4b9e Apply #69 2023-04-14 17:50:14 +00:00
Jordan Bancino 3192063340 Fix broken tt script. 2023-04-07 14:20:22 +00:00
Jordan Bancino 1f8df737da Add HttpRouter API; still have to convert the code to use it. 2023-04-06 01:48:32 +00:00
Jordan Bancino 7c865d06fd Format code. 2023-04-01 02:46:59 +00:00
Jordan Bancino e0c8530b12 Clean up http client enough to replace curl in send-patch. 2023-04-01 02:46:07 +00:00
Jordan Bancino e592840c99 Clean up some HTTP client stuff. 2023-04-01 02:13:41 +00:00
Jordan Bancino 7b3d537175 Remove UtilStreamCopy() 2023-04-01 00:20:18 +00:00
Jordan Bancino f341fd2b6e Fix OpenSSL server accept call.
Apparently it can EAGAIN on non-blocking connections... I don't think
LibreSSL's TLS library does this, but something to keep in mind if it
doesn't work for somebody.
2023-03-31 23:10:52 +00:00
Jordan Bancino eef615fc9a Fix warnings in RouteChangePwd.c 2023-03-28 02:28:58 +00:00
Jordan Bancino 9b21e2460a Accept #67: Add the password modification endpoint. 2023-03-28 01:17:47 +00:00
Jordan Bancino c6f4a4a546 Fix leak of StreamStdout() when logging to a file. 2023-03-27 17:56:45 +00:00
Jordan Bancino a9da9fbca7 Update TODO 2023-03-27 17:52:20 +00:00
Jordan Bancino c37d3801b2 Fix warning about uninitialized variable. 2023-03-25 00:00:53 +00:00
Jordan Bancino a24c27bf4f Fix leak in TlsOpenSSL. 2023-03-24 17:37:44 +00:00
Jordan Bancino af776c64a7 Remove duplicate return line. 2023-03-24 14:18:29 +00:00
Jordan Bancino a25573063f Fix up TlsOpenSSL a little bit.
Server is still broken...
2023-03-24 03:05:12 +00:00
Jordan Bancino aeb49f80e5 Add support for OpenSSL. This is a good demo of how easy it is to support TLS libraries. 2023-03-24 02:41:01 +00:00
Jordan Bancino 007e639b0c Don't require the TLS_ in the environment variable.
It's redundant.
2023-03-24 00:25:44 +00:00
Jordan Bancino fe32c652cd Fix bug in HttpClient where it wouldn't retry on EAGAIN. 2023-03-24 00:23:49 +00:00
Jordan Bancino 20d41d794b Fix compile error. I had this in here at some point, not sure where it went. 2023-03-23 17:41:02 +00:00
Jordan Bancino b4e4263cea Remove my testing configuration from contrib/development.conf 2023-03-23 16:40:23 +00:00
Jordan Bancino e13442c122 Fix strange behavior in TlsLibreSSL.
tls_read() and tls_write() may return TLS_WANT_POLLIN or TLS_WANT_POLLOUT
if data isn't ready to be read or written yet. We have to account for this
by converting it to EAGAIN, which is how a typical read() or write()
function should behave.

Also installed a SIGPIPE handler; we do not want to be terminated by
SIGPIPE, and it's safe to ignore this signal because it should be
handled thoroughly in the code.
2023-03-23 16:39:15 +00:00
Jordan Bancino 2441f07848 Add support for spinning up multiple HTTP servers.
This is useful for having a TLS and a non-TLS version port, like Synapse.
I verified that the multiple-servers does in fact work as intended,
although the TLS server part is broken; I must be doing something
incorrectly with LibreSSL in setting up the server.
2023-03-23 02:12:45 +00:00
Jordan Bancino 2fab7b55fe Remove obsolete warning about setting root directory.
We don't use chroot() anymore.
2023-03-22 18:30:30 +00:00
Jordan Bancino 089d8d4d94 Only install the memory hook if -v is given.
This way, we can still set the debug level in the configuration, and not
see the log just absolutely flooded with memory allocations and whatnot.
This is helpful because I want debug messages to show up in development,
but not in production, but having all the memory logging makes it
almost impossible to pick anything else out of the log. I want the
feature available, just not on by default because it's useful in limited
circumstances.
2023-03-22 18:29:05 +00:00
Jordan Bancino 9ec330f40a Log once we get the response status, not right when we get the request. 2023-03-22 18:13:59 +00:00
Jordan Bancino fccd15b239 Don't unconditionally close standard input; it may have been used and
closed before. If it was never used, it was never opened.
2023-03-22 18:12:46 +00:00
Jordan Bancino 35f65a667d Update changelog. 2023-03-22 17:49:06 +00:00
Jordan Bancino 8faf6f2126 Delete TelodendriaConfig.c 2023-03-22 17:20:53 +00:00
Jordan Bancino fc975e6a93 TelodendriaConfig -> Config 2023-03-22 17:17:30 +00:00
Jordan Bancino 413c7ad803 Start building support for running multiple HTTP servers.
The standard use case for this is going to be running a TLS and a non-TLS
HTTP server. I can't see a need for *more* than two, but it is theoretically
possible.

We shouldn't have to change anything with the database or anything; it
should suffice to simply spin up more HTTP servers, and they should
interact with each other the same way a single HTTP server with multiple
threads will.
2023-03-22 17:00:48 +00:00
Jordan Bancino e30fa3ee33 Remove remnants of non-global LogConfig from TelodendriaConfig 2023-03-22 16:31:24 +00:00
Jordan Bancino e6f3dfad18 Add an ArraySet() method for replacing entries in an array.
This is much more efficient than using ArrayDelete() and ArrayInsert(),
and will serve us well in the future.
2023-03-22 16:31:06 +00:00
Jordan Bancino b8d00bc8bf Make td run warn if Memory.txt gets created. 2023-03-22 14:57:57 +00:00
Jordan Bancino f3c4c0ac65 Add a global log configuration.
This is the easiest and cleanest way to get logging into some of the
fundamental APIs, such as the database and TLS APIs. We don't want to
have to pass logging functions to those, but they can safely use the
global logging configuration.
2023-03-22 14:52:04 +00:00
Jordan Bancino 8782aa046d Fix compile error in Tls, work on getting certs and keys into HttpServer. 2023-03-22 02:18:31 +00:00
Jordan Bancino bdaea9872e Format source code. 2023-03-22 01:46:45 +00:00
Jordan Bancino b58ca7d22e Start working on adding TLS support to HttpServer. 2023-03-22 01:46:24 +00:00
Jordan Bancino 6561b5bae1 Add TLS build support to td. 2023-03-22 01:17:42 +00:00
Jordan Bancino 996356832e Define TLS API, update HttpClient to support optional TLS.
Also added a LibreSSL TLS implementation. Client is verified to work;
server has not been tested yet.
2023-03-22 00:41:21 +00:00
Jordan Bancino d1b4ecff48 Apparently the order of LDFLAGS matters to some compilers.
Also don't to -pg in debug mode, because that's not supported on all
compilers either.
2023-03-22 00:13:27 +00:00
Jordan Bancino 63d07365db Don't close standard output twice, that leads to a segfault! 2023-03-22 00:12:04 +00:00
Jordan Bancino 2a92d0de7e Fix bit flag check. 2023-03-22 00:11:24 +00:00
Jordan Bancino 4a27f50538 Remove calls to pledge(), unveil(), and chroot().
Not only does this make us more POSIX, it actually makes things a lot
easier because TLS implementations will need to be able to access the
trusted certificates file, which most likely will not live in the
data directory.
2023-03-20 19:23:37 +00:00
Jordan Bancino 6c9e939b9f Fix broken IoVprintf().
You can't call vsnprintf() on the same va_list more than once! I learned
this the hard way with StreamVprintf().
2023-03-18 14:55:04 +00:00
Jordan Bancino 5289c16e2b Convert all code to new Stream API.
Also made a number of improvmements to tt, making it compatible with more
shells.
2023-03-18 14:32:09 +00:00
Jordan Bancino a97a593f21 Format code. 2023-03-16 16:53:18 +00:00
Jordan Bancino 27b3b6cdc6 Add StreamPuts(), don't make StreamVprintf() defer to IoVprintf(). 2023-03-16 16:51:41 +00:00
Jordan Bancino 8539a03d5b Add StreamFile() convenience method. 2023-03-16 16:25:24 +00:00
Jordan Bancino e0a3760a37 Don't buffer in IoWrite(), indicate that fwrite() returns size_t, not ssize_t. 2023-03-16 14:17:04 +00:00
Jordan Bancino 6ee1857f5f Format source code. 2023-03-16 12:29:38 +00:00
Jordan Bancino 7d9770fc12 Add some convenience functions for working with Io and Stream.
Also broke out IoFd into it's own file, and did the same with IoFile.
2023-03-16 12:28:55 +00:00
Jordan Bancino 65f4c90df3 Rename HttpStream() to HttpServerStream() to match HttpClientStream() 2023-03-16 02:17:48 +00:00
Jordan Bancino ff52cc78dc Update TODO 2023-03-15 17:30:14 +00:00
Jordan Bancino ab4755240a Add IoCopy() and StreamCopy()
Both do buffered reads and writes, but IoCopy() uses IoRead() and
IoWrite() directly, whereas StreamCopy() relies on StreamGetc() and
StreamPutc(), which manipulate the stream buffers.
2023-03-15 17:14:16 +00:00
Jordan Bancino 92da3542a6 Move low-level fopencookie()/funopen() functionality to Io API.
The Stream API now provides the buffered I/O functionality analogous to
the C standard library.
2023-03-15 16:47:34 +00:00
Jordan Bancino 5dbaf3c223 Apply #65, remove printf() in Db, and make tt show login messages. 2023-03-15 13:36:49 +00:00
Jordan Bancino afc7667737 Begin prototyping Stream API. 2023-03-15 02:40:23 +00:00
Jordan Bancino ae97d8116c Apply #64: Registration tokens. 2023-03-14 00:37:24 +00:00
Jordan Bancino 76bfa120ee Fix CPU pin if clients don't shutdown() their socket properly.
If we haven't read any bytes yet, then we try a few times a few ms apart
to see if we get anything. If not, treat it as an EOF. Otherwise, read
bytes until we get an EOF or EAGAIN. EAGAIN after a consistent read of
bytes is treaded as an EOF immediately.
2023-03-12 15:08:50 +00:00
Jordan Bancino 62cd1cdc98 Misc changes. 2023-03-12 03:37:57 +00:00
Jordan Bancino 7fa982a16f Fix bug in Uia 2023-03-12 03:36:40 +00:00
Jordan Bancino aba1ef9251 Add some more documentation. 2023-03-12 03:36:19 +00:00
Jordan Bancino b4a394c44b Move send-patch and td to section one of the manual.
I think they fit there better.
2023-03-10 23:02:09 +00:00
Jordan Bancino 64add9c9ab Fix unused variable warning. 2023-03-10 20:12:25 +00:00
Jordan Bancino 3037f12907 Add tt and http-debug-server tools. 2023-03-10 18:48:52 +00:00
Jordan Bancino 2d9b706f38 Fix I/O in JsonConsumeWhitespace() and UtilStreamCopy().
These functions previously operated on the assumption that fgetc() would
block; however it will not block on HttpServer streams because those are
non-blocking. They now check error conditions properly before failing
prematurely.
2023-03-10 18:46:03 +00:00
Jordan Bancino fd12dee62e Make sure a newline is printed after JSON object is pretty-printed. 2023-03-10 04:30:56 +00:00
Jordan Bancino 8ead9cc93a Apply #63, make some general bug fixes. 2023-03-10 03:24:04 +00:00
Jordan Bancino 3af2d3d12b Format code, update TODO items. 2023-03-09 03:35:40 +00:00
Jordan Bancino 7344d4fa46 Apparently some shells interpret the - in a string as an argument delimiter??? 2023-03-09 02:49:14 +00:00
Jordan Bancino ca053a12b1 Break out main() into Main.c, fix some compile warnings on Cygwin. 2023-03-09 02:46:04 +00:00
Jordan Bancino 9a1300ff2e Make sure input is initialized. 2023-03-09 02:22:13 +00:00
Jordan Bancino c32c3abfd6 Update TODO 2023-03-09 00:10:32 +00:00
Jordan Bancino 012c334ee5 Make send-patch use json instead of jq. 2023-03-09 00:06:15 +00:00
Jordan Bancino 20ebeb9c32 Switch tp from using jq to json; format td 2023-03-08 22:47:40 +00:00
Jordan Bancino 76413f834e Make json use ^ for removing fields, instead of !
Room IDs start with '!', we want to be able to access these.
2023-03-08 22:47:07 +00:00
Jordan Bancino efdf168085 Allow encoding user-provided strings. 2023-03-08 20:31:49 +00:00
Jordan Bancino 50e599f1cd Implement a mostly-functional query language for JSON. 2023-03-08 19:49:06 +00:00
Jordan Bancino 7b22fb02a2 Implement pretty-printing option in Json.
Telodendria itself doesn't use it, but the json CLI tool does.
2023-03-08 17:15:43 +00:00
Jordan Bancino cb8c4fceb5 Begin working on JSON CLI tool.
This is a similar situation to the HTTP CLI tool: since we have the
Json API anyway, we might as well have a little command line tool to
replace jq.
2023-03-08 04:06:43 +00:00
Jordan Bancino 19e89110cd Bump version number to v0.3.0 2023-03-08 03:46:38 +00:00
Jordan Bancino 1a169d1a2e Fix usage message. 2023-03-08 03:31:34 +00:00
Jordan Bancino 8d75d8a023 Add simple HTTP CLI tool that uses the HttpClient API.
You might be asking why I would just write a simple curl replacement
when curl does the job just fine. Well, the most immediate reason is
to test the HttpClient API, but since Telodendria's goal is to not
be dependent on any third-party code if at all possible, it makes
sense to have a simple HTTP client to use not only for testing
Telodendria, but also for configuring it. When we move the
configuration to the database, we'll ship a script that uses this
tool to allow admins to easily submit API requests.

Do not be concerned that HttpClient does not support TLS yet. TLS
support is necessary for federation to work, so it is coming
eventually.
2023-03-08 03:30:36 +00:00
Jordan Bancino 7e144ae488 Clean up a few bugs in HttpClient and Uri 2023-03-08 02:53:47 +00:00
Jordan Bancino 3e42da279c Make improvements to HttpClient, add Uri 2023-03-08 01:54:33 +00:00
Jordan Bancino 313f0e2e73 Add HttpClient API 2023-03-07 23:10:06 +00:00
Jordan Bancino fc8fbc9a70 I forgot to set the date in the changelog. 2023-03-07 00:51:16 +00:00
Jordan Bancino 7e41251f07 Update README 2023-03-07 00:50:59 +00:00
Jordan Bancino cfba96edee Prepare for v0.2.0 release. 2023-03-07 00:44:53 +00:00
Jordan Bancino 04bf8ca1a1 Document Uia API. 2023-03-07 00:28:52 +00:00
Jordan Bancino 17734c90e8 This is unnecessary. 2023-03-06 23:54:55 +00:00
Jordan Bancino 154e5b82df Looks like CVS is being weird with Mdocdate strings. 2023-03-06 23:51:55 +00:00
Jordan Bancino 7750963c29 Something weird is going on here. 2023-03-06 23:49:30 +00:00
Jordan Bancino eae8fc5c79 Update Matrix documentation. 2023-03-06 22:49:21 +00:00
Jordan Bancino f2c2888c87 Document DbList() and DbListFree() 2023-03-06 22:29:46 +00:00
Jordan Bancino f1e565ef7b Update Json man page. 2023-03-06 22:21:56 +00:00
Jordan Bancino 0ac21d430a Document User API 2023-03-06 22:09:57 +00:00
Jordan Bancino e73cea64b7 Apply modified #60 2023-03-04 21:43:41 +00:00
Jordan Bancino c8c4c705a5 Convert proposals to man pages. 2023-03-04 20:26:16 +00:00
Jordan Bancino 8c2ed1c8f1 Apply #59 2023-03-04 15:34:44 +00:00
Jordan Bancino 0cbdb5f615 Multi-stage flows should theoretically work now. 2023-03-04 01:53:33 +00:00
Jordan Bancino e4ec250d8f Apply modified #57: Implement logout all. 2023-03-03 22:49:37 +00:00
Jordan Bancino 97990c5009 Get some more of my thoughts in the TODO file. 2023-03-03 18:56:36 +00:00
Jordan Bancino 5d590df83d Remove DB_MIN_CACHE because that's dumb.
You should be able to totally disable the cache if you so please. This
should ensure Telodendria uses less memory on constrained systems.
2023-03-03 14:26:10 +00:00
Jordan Bancino 1770789333 Fix leak in DbList() 2023-03-03 03:11:49 +00:00
Jordan Bancino 676d6f4c61 Basic formatting. 2023-03-02 22:32:47 +00:00
Jordan Bancino d899a836b6 Apply #54 2023-03-02 22:06:33 +00:00
Jordan Bancino f4838f8211 Remove non-standard use of d_type 2023-03-02 02:53:43 +00:00
Jordan Bancino d6f96757bc Apply #52: d_namlen is non-standard. 2023-03-01 21:39:22 +00:00
Jordan Bancino 7a951c980f Cleanup old user interactive auth sessions. 2023-03-01 19:52:44 +00:00
Jordan Bancino 8c4e6aa594 Implement DbList() 2023-03-01 19:33:25 +00:00
Jordan Bancino ce6d483135 Implement static login page. 2023-03-01 19:03:42 +00:00
Jordan Bancino 55b3728e7e Bake the platform name into the version number.
This is helpful as we're porting to different operating systems. I guess
it just makes the porting effort more satisfying. Seeing this header
makes me happy:

Server: Telodendria/0.2.0-Haiku
2023-03-01 03:19:21 +00:00
Jordan Bancino 2b77681763 Move getent check to common.sh 2023-03-01 03:11:10 +00:00
Jordan Bancino dd1b26bad8 Only use getent if the command can be found. 2023-03-01 03:09:46 +00:00
Jordan Bancino 755d08946a Add some more obscure platforms to td build script. 2023-03-01 03:08:53 +00:00
Jordan Bancino e7d1c0d951 Cross-platform code improvements. 2023-03-01 01:55:26 +00:00
Jordan Bancino 2e1220621c Use -pipe, supposedly this speeds things up.
The codebase is getting kind of big, we'll need all the help we can get.
2023-03-01 01:26:34 +00:00
Jordan Bancino 334a711b02 Fix compile error. 2023-03-01 01:23:52 +00:00
Jordan Bancino 2747660473 Add a TODO in Uia 2023-02-28 18:48:43 +00:00
Jordan Bancino 63634407d4 Update TODO.txt, add stub functions in Db. 2023-02-28 18:44:02 +00:00
Jordan Bancino 16c31b63d7 Begin work on static login page. 2023-02-28 17:52:09 +00:00
Jordan Bancino 36169181dd Build basic framework for creating static pages.
The login fallback, as well as the user-interactive authentication
fallbacks, are static HTML pages.
2023-02-28 16:51:40 +00:00
Jordan Bancino fae9eb4473 Implement password-based user interactive authentication. 2023-02-28 15:17:11 +00:00
Jordan Bancino e2806bc810 Add UserIdParse() and UserIdFree() functions.
The spec says that a username can be either just the localpart, or a
localpart and a server. This commit now ensures that the login endpoint
actually handles usernames properly by calling the proper parsing
functions.
2023-02-28 13:44:34 +00:00
Jordan Bancino 58dae3a0c9 Remove user create Admin endpoint, and the associated privilege.
If an admin wants to create a user, he or she should have the ISSUE_TOKEN
privilege. The admin can use that to create a one-time registration token,
and then just use the regular registration API with that token.
2023-02-27 18:02:35 +00:00
Jordan Bancino d89e3fd753 Update TODO 2023-02-27 17:25:39 +00:00
Jordan Bancino 6ce6cb4525 Implement flow handling in Uia API.
This commit should fix user interactive authentication for dummy flows,
but I still have to implement a few more flows, including passwords and
refresh token. I also have to fix the cleanup logic: when do we purge
UIA sessions?
2023-02-27 15:39:12 +00:00
Jordan Bancino 7703405c70 Fix bugs in Json API. 2023-02-26 15:07:46 +00:00
Jordan Bancino 31be432f7a Just discovered some bugs in Json. 2023-02-25 16:35:33 +00:00
Jordan Bancino adea499813 Add UiaFlowsFree() function, and clean up some memory issues. 2023-02-24 14:40:21 +00:00
Jordan Bancino 26b0f33f96 -not is not portable, grep -v should work good enough though. 2023-02-24 02:02:47 +00:00
Jordan Bancino d517b66316 Also delete refrsh token if present for device. 2023-02-24 01:06:02 +00:00
Jordan Bancino b60cac53e5 Make JsonValueString() call StrDuplicate(); refactor code to behave properly. 2023-02-24 00:17:56 +00:00
Jordan Bancino 8c96fd8d7d Begin the great StrDuplicate() refactor. 2023-02-23 23:19:23 +00:00
Jordan Bancino 41421557e0 Update TODO. 2023-02-23 16:46:27 +00:00
Jordan Bancino 4f562167f6 Update TODO 2023-02-23 16:20:15 +00:00
Jordan Bancino b99e8bd1cd Apply modified #50 and fix some misc. bugs. 2023-02-23 15:13:39 +00:00
Jordan Bancino 9e9b5c9cda Fix a write-out-of-bounds error in Json. 2023-02-23 03:46:05 +00:00
Jordan Bancino 3bbff5379f [WIP] Replace UserInteractiveAuth with a new Uia API.
Uia is a lot less characters to type. Do note that this API is far from
complete and this commit breaks user interactive authentication entirely.
2023-02-19 14:58:56 +00:00
Jordan Bancino d81e8f3a32 We're building up some tools; let's print them out in env.sh 2023-02-17 18:26:08 +00:00
Jordan Bancino 3faee8b432 Update TODO items. 2023-02-17 15:03:59 +00:00
Jordan Bancino fa88fc3323 Format source code. 2023-02-17 03:23:25 +00:00
Jordan Bancino ff879e715f Finish implementing token refresh.
This implementation just keeps the refresh token and only updates the
access token. The spec says that this is allowed. There's really no
reason to do this, other than the fact that it's easier.
2023-02-17 03:20:49 +00:00
Jordan Bancino 4b336de171 Build out the User API a bit more. 2023-02-17 03:18:24 +00:00
Jordan Bancino 46fe667988 Add HashMapGetKey() so we can free bucket keys before deleting them. 2023-02-17 03:14:43 +00:00
Jordan Bancino feb11de6b0 Fix some compile warnings. I'll get back to this eventually. 2023-02-16 18:51:10 +00:00
Jordan Bancino 72405a94f6 Add JsonGet(), JsonSet(), and JsonCreate() for convenience. 2023-02-16 18:49:19 +00:00
Jordan Bancino d255ce1a21 Begin working on refresh route. 2023-02-16 17:22:59 +00:00
Jordan Bancino b05e81776a Be smart about the type of man page we generate. 2023-02-16 17:22:32 +00:00
Jordan Bancino c78dc3bd31 Fix a memory bug in StrRandom() with RandIntN().
We're storing integers in this buffer, so we have to allocate enough
memory for them. An integer is usually more than one byte.
2023-02-16 13:10:09 +00:00
Jordan Bancino 38438c297e Looks like Matrix v1.6 is out. 2023-02-16 02:09:57 +00:00
Jordan Bancino 1f1cbc0461 Fix a bad jq parameter. 2023-02-16 02:01:25 +00:00
Jordan Bancino dc566a85b7 Ignore .patch files. 2023-02-16 02:01:14 +00:00
Jordan Bancino 85380efa3c [#48] Fix bug in MatrixGetAccessToken() 2023-02-16 01:33:46 +00:00
Jordan Bancino 2ce43ce457 Add a script for conveniently creating new library man pages. 2023-02-16 00:55:10 +00:00
Jordan Bancino 6735c101ec Add Rand man page. 2023-02-16 00:54:58 +00:00
Jordan Bancino 6a593ab8a0 [#48] Add Rand API and make StrRandom() use it. 2023-02-16 00:31:13 +00:00
Jordan Bancino fdecac576f Some browsers don't like this rule; making it !important seems to fix them. 2023-02-16 00:05:26 +00:00
Jordan Bancino cebc2959cc Fix a few things that were bothering me. 2023-02-15 21:53:39 +00:00
Jordan Bancino 50aa65617a Fix some bugs in the man page index generation. 2023-02-15 16:00:36 +00:00
Jordan Bancino de0ece10e2 Use generated tables instead of hand-crafted tables. 2023-02-15 03:30:52 +00:00
Jordan Bancino e3b8eaf941 Put description all on one line. 2023-02-15 03:30:40 +00:00
Jordan Bancino a72f75d6b5 Update some man page descriptions. 2023-02-15 02:37:44 +00:00
Jordan Bancino a7c1ccd3f1 Update lots of documentation. 2023-02-15 01:32:44 +00:00
Jordan Bancino f7d581538d Begin documenting Str. 2023-02-14 11:56:22 +00:00
Jordan Bancino 653d282bcd Document User API. 2023-02-14 02:02:22 +00:00
Jordan Bancino 5fef788053 Begin documenting User API 2023-02-12 02:31:14 +00:00
Jordan Bancino 2443c91bba Fix bug in HashMap that would allow iterating over deleted values. 2023-02-11 00:15:49 +00:00
Jordan Bancino 27c4713e5d I actually decided I don't like this. 2023-02-11 00:07:18 +00:00
Jordan Bancino 71a0181a6a Break out static options into an environment variable.
This allows users to turn off static compilation if the compiler or linker
throws a fit about it. Looking at you, Glibc. And PCC doesn't seem to like
static compilation either; it generates a binary that just segfaults
immediately on executation.
2023-02-05 14:46:20 +00:00
Jordan Bancino 8b8873103d Handle standard library quirks of non-conforming systems and compilers. 2023-02-05 14:20:12 +00:00
Jordan Bancino dc972385ea Don't shadow variables. 2023-02-05 14:19:07 +00:00
Jordan Bancino e1efac41fa Fix admin endpoint to be /_telodendria/admin instead of /admin.
This is how Synapse and Dendrite do it. It makes sense to me.
2023-02-04 00:24:17 +00:00
Jordan Bancino a4ec6cf6c7 Apply #45 2023-02-04 00:21:36 +00:00
Jordan Bancino 5b77236e82 Fix -v option 2023-01-17 21:38:39 +00:00
Jordan Bancino e0f7c133d1 Add a non-JSON landing page. This is the basis for other HTML pages. 2023-01-17 20:29:16 +00:00
Jordan Bancino 4e742d5179 Define project timeline a little more. 2023-01-17 19:05:26 +00:00
Jordan Bancino 70fd61cd23 Bring change log up to date-ish.
Next release I'll try to stay on top of this more.
2023-01-17 17:39:31 +00:00
Jordan Bancino 056804810f Fix man page links on website. 2023-01-17 13:53:31 +00:00
Jordan Bancino b0b2f11158 Refactor endpoint authentication flow.
Instead of one MatrixAuthenticate() function, we'll do
MatrixGetAccessToken(), and then UserAuthenticate(). This allows us to
give different error messages depending on what the user provided and what
the server state is.
2023-01-17 01:36:22 +00:00
Jordan Bancino 1e02971a7e Implement login route. 2023-01-17 00:02:50 +00:00
Jordan Bancino cc95c10f44 Move client well-known generation to MatrixClientWellKnown() function.
We'll be using this for client login.
2023-01-16 22:02:08 +00:00
Jordan Bancino 121682c006 Implement user login. 2023-01-16 21:17:44 +00:00
Jordan Bancino bd88c01c26 Don't read the timestamp after it's freed. 2023-01-16 03:57:01 +00:00
Jordan Bancino c0309c1ea8 Fix memory leak when log timestamp is not set in the config. 2023-01-16 03:21:59 +00:00
Jordan Bancino 9186cdb13d Verbose formatting on the terminal, because why not? 2023-01-10 01:21:35 +00:00
Jordan Bancino a4364dbb68 Fix use after free. 2023-01-10 00:38:47 +00:00
Jordan Bancino 599fa1a740 Document MemoryHexDump() and DbExists() 2023-01-09 21:39:59 +00:00
Jordan Bancino b8ce4c9239 Lock database in DbExists() to prevent race conditions. 2023-01-09 19:25:49 +00:00
Jordan Bancino c5bce0b14f Apply #43 with modifications. 2023-01-09 19:22:09 +00:00
Jordan Bancino 1421c478fd Fix bug where malformed requests cause Db locks that are never unlocked. 2023-01-09 19:16:12 +00:00
Jordan Bancino a7ab3e757a Apply #43 2023-01-09 18:20:19 +00:00
Jordan Bancino 02884b6bdb Fix typo. 2023-01-09 18:01:32 +00:00
Jordan Bancino a09e15f6bc Don't output server response. 2023-01-09 18:00:14 +00:00
Jordan Bancino cdb26b5223 Make Telodendria Bot retry fetching if it failed. 2023-01-09 17:56:24 +00:00
Jordan Bancino 90a74c3b0a Don't accept connections if the connection queue is full. 2023-01-09 17:44:12 +00:00
Jordan Bancino 1d9ed5dcbf Fix invalid read in HttpParamDecode(). 2023-01-09 15:48:56 +00:00
Jordan Bancino 9358264add Fix some invalid write errors. 2023-01-09 15:18:59 +00:00
Jordan Bancino 80ff232af9 Update TODO 2023-01-08 04:39:01 +00:00
Jordan Bancino 30c3690287 Renamed String.h to Str.h because Windows is dumb. 2023-01-08 04:38:06 +00:00
Jordan Bancino 148706b517 Use spaces instead of tabs. Tabs don't work over SSH? 2023-01-07 19:22:38 +00:00
Jordan Bancino 472a6ccbf7 Clean up build output on terminals.
If we're not writing out to a terminal, then echo each file out as it is
being compiled. Otherwise, if we are on a terminal, overwrite the previous
line.
2023-01-07 19:14:15 +00:00
Jordan Bancino 56105c8a61 Fix logic error 2023-01-07 18:24:16 +00:00
Jordan Bancino f365f94389 Define User struct. 2023-01-07 16:15:11 +00:00
Jordan Bancino 08b36c071c Begin prototyping User.h 2023-01-07 15:51:56 +00:00
Jordan Bancino 0f661f435f Whoops, forgot to update the header. 2023-01-07 04:40:12 +00:00
Jordan Bancino 0a29aa7f5a Move string related functions to a new String API.
I think we have accumulated enough string functions that they should
have their own API. This shortens the function names a bit too.
2023-01-07 04:33:32 +00:00
Jordan Bancino 2ce09f8632 Convert UtilStringConcat() into a varargs function.
This allows us to concatenate an arbitrary amount of strings without
having to maintain a bunch of pointers or leak memory when nesting calls.
2023-01-07 03:17:06 +00:00
Jordan Bancino 0c807d0f22 Cross an item of TODO 2023-01-07 00:42:11 +00:00
Jordan Bancino 8323eb38c9 Make UtilRandomString() more secure.
Two ways this is more secure:

1. The seed is only generated once, not every time the function is called.
2. All threads share the same seed, which means timing attacks aren't
   possible.

Because we are using a mutex, performance may suffer slightly.
2023-01-07 00:18:44 +00:00
Jordan Bancino 7cd9fe8bd7 Fix log output by changing __FILE__ 2023-01-06 23:00:18 +00:00
Jordan Bancino ea1828e95e Fix warning in RouteRegister.c 2023-01-06 21:54:33 +00:00
Jordan Bancino 7bda319f39 Format code 2023-01-06 21:18:44 +00:00
Jordan Bancino c63715c2e7 Update TODO 2023-01-06 21:17:55 +00:00
Jordan Bancino c48d666899 Add note on documenting patches. 2023-01-06 19:54:44 +00:00
Jordan Bancino 1282371680 Fix "bad pointer" warnings. 2023-01-06 18:50:31 +00:00
Jordan Bancino fe57f07fee Add onion location. We're on Tor now. 2023-01-04 01:07:48 +00:00
Jordan Bancino b753d58171 Apply #41 2023-01-03 13:01:22 +00:00
Jordan Bancino 6960743e43 Apply #40 2023-01-03 04:01:03 +00:00
Jordan Bancino 8e2399e648 Fix bug on some platforms that use unsigned characters. 2023-01-02 18:22:04 +00:00
Jordan Bancino d969f4d053 Fix visual bugs in MemoryHexDump() 2023-01-02 04:12:12 +00:00
Jordan Bancino 5c5aab71d8 Fix bug in UserInteractiveAuth() 2023-01-02 03:56:27 +00:00
Jordan Bancino e9aebab221 Move hex dump logic to Memory API. 2023-01-01 22:10:23 +00:00
Jordan Bancino 69862a1e6e Use -ansi in compiler flags. I think this more well supported. 2023-01-01 21:16:20 +00:00
Jordan Bancino d9d88eb028 Periodically purge old user interactive auth sessions.
Keeping them around is going to take up a lot of storage. If it takes more
than a half hour for a client to complete the auth, make him start over.
2022-12-28 15:52:19 +00:00
Jordan Bancino fbd7bf5944 Fix memory leak in DbDelete() 2022-12-28 15:44:21 +00:00
Jordan Bancino 1a43ea6470 Fix bugs in Cron 2022-12-26 16:28:58 +00:00
Jordan Bancino 3c8d89a52e Update copyright year 2022-12-26 15:52:52 +00:00
Jordan Bancino c9e42ff813 Break out UserInteractiveAuth into its own header.
We'll need this because user interactive auth will get complicated and
messy very soon.
2022-12-26 15:48:21 +00:00
Jordan Bancino dbecb28395 Update changelog 2022-12-24 23:41:50 +00:00
Jordan Bancino 4f48f9a5cc Document Cron 2022-12-24 23:07:46 +00:00
Jordan Bancino c18a9a96e6 Add job scheduler.
This will be used for expiring sessions and tokens, among other things that
need to happen periodically.
2022-12-24 21:49:37 +00:00
Jordan Bancino be2e267064 Turns out I don't need to do that. 2022-12-24 01:31:22 +00:00
Jordan Bancino 61e9cdbea3 Check INT_WIDTH and LONG_WIDTH... stupid GNU libc. 2022-12-24 01:00:36 +00:00
Jordan Bancino eeabe93e3f Revert using _POSIX_C_SOURCE.
It didn't fix the issue.
2022-12-24 00:51:49 +00:00
Jordan Bancino 2d1bfd8b74 Define _POSIX_C_SOURCE again. 2022-12-23 22:53:48 +00:00
Jordan Bancino 25dcff06a8 Fix type issues. 2022-12-19 23:25:37 +00:00
Jordan Bancino 72bfa34bcd Fix overflow on some platforms 2022-12-19 22:25:49 +00:00
Jordan Bancino b8027e1aaf Fix compiler warning on some platforms. 2022-12-19 22:21:24 +00:00
Jordan Bancino 94493e6ad8 Add Sha2 2022-12-19 22:15:41 +00:00
Jordan Bancino 88a11650cf Formatting. 2022-12-19 21:54:01 +00:00
Jordan Bancino c0bd70a391 Add MatrixUserValidate() and MatrixHistoricalUserValidate() 2022-12-18 20:20:08 +00:00
Jordan Bancino f32e5eafce This release is getting too big to be 0.1.1 2022-12-18 20:11:32 +00:00
Jordan Bancino c15895ae06 Username is technically optional. 2022-12-17 02:12:04 +00:00
Jordan Bancino 70c3d01125 Finish route param and request body validation 2022-12-17 00:08:31 +00:00
Jordan Bancino f837988156 Begin work on validating registration request. 2022-12-16 22:56:35 +00:00
Jordan Bancino 53846b0994 Document MatrixAuthenticate and MatrixRateLimit 2022-12-16 22:15:50 +00:00
Jordan Bancino 836b61dc42 Begin implementing a few helper functions 2022-12-16 22:06:01 +00:00
Jordan Bancino 9a31b1f350 Document user interactive authentication function 2022-12-16 20:52:55 +00:00
Jordan Bancino fb06d17b16 Abstract user interactive auth out to function. 2022-12-16 20:08:16 +00:00
Jordan Bancino 7ee31ad36b Copy diagram from scrap paper into code for clarity. 2022-12-15 22:14:16 +00:00
Jordan Bancino 1273d87df9 Finish user interactive logic (still need to abstract it out) 2022-12-15 22:04:30 +00:00
Jordan Bancino f475cac601 More validation in user-interactive API 2022-12-15 21:47:08 +00:00
Jordan Bancino 7d8cec8185 Persist session IDs for future calls. 2022-12-15 20:41:56 +00:00
Jordan Bancino 449851a67e Format code 2022-12-15 20:31:51 +00:00
Jordan Bancino bee07d90fc Fix memory leaks in database. 2022-12-15 20:29:19 +00:00
Jordan Bancino afa0d89e40 Fix links in download table 2022-12-15 20:28:42 +00:00
Jordan Bancino c3618488f2 Hexdump leaked memory to the log for debugging. 2022-12-15 20:18:12 +00:00
Jordan Bancino cc6ae2dbd3 Prevent directory traversals by replacing dots and slashes. 2022-12-15 16:07:45 +00:00
Jordan Bancino de6a857ce7 Format code. 2022-12-15 03:43:40 +00:00
Jordan Bancino 4f2f7cd966 Add random string generator 2022-12-15 03:41:59 +00:00
Jordan Bancino f0392044c2 Document DbDelete() 2022-12-15 02:48:42 +00:00
Jordan Bancino c8cd2cebb7 Implement DbDelete() 2022-12-15 02:39:58 +00:00
Jordan Bancino abb4a9cf20 More work on registration endpoint. 2022-12-15 01:48:49 +00:00
Jordan Bancino d48b6fc456 Add username check logic 2022-12-14 21:23:20 +00:00
Jordan Bancino 5080d066ab Remove some debug statements; make route error more specific. 2022-12-14 18:41:01 +00:00
Jordan Bancino e37e6f1bb8 Fix some memory bugs. 2022-12-14 18:19:20 +00:00
Jordan Bancino 5880cf3ea0 Check paths for /available and /(email|msisdn)/requestToken 2022-12-14 16:21:01 +00:00
Jordan Bancino 0305ef183d Begin work on RouteRegister 2022-12-14 15:40:23 +00:00
Jordan Bancino 07acad8573 Update changelog 2022-12-14 15:09:19 +00:00
Jordan Bancino e7e25cade5 Fix an instance of setting response headers after they were sent. 2022-12-14 15:02:10 +00:00
Jordan Bancino 708a840ec0 Fix memory leak when attempting to parse something that isn't JSON. 2022-12-14 14:54:06 +00:00
Jordan Bancino 5255c3f773 Fix whitespace issues in td 2022-12-14 01:00:52 +00:00
Jordan Bancino b63eeffb0f Implement variable substitution for site files.
It was such a pain to update the links in site/index.html for v0.1.0, so
this is necessary to prevent me from going insane manually updating all
these version numbers sprinkled everywhere.
2022-12-14 00:54:52 +00:00
Jordan Bancino 4eae5b771f Keep the change log relevant. 2022-12-13 21:27:17 +00:00
Jordan Bancino eb4b6b7704 Actually I don't want to release this yet. 2022-12-13 20:41:01 +00:00
Jordan Bancino 5d1451bf88 Bump version number 2022-12-13 20:36:03 +00:00
Jordan Bancino 6d5ef9d6ad Fix cvs tag command (for real this time) 2022-12-13 19:57:34 +00:00
162 changed files with 12721 additions and 11662 deletions

View File

@ -1,5 +0,0 @@
build
data
.env
*.log
vgcore.*

View File

@ -0,0 +1,49 @@
name: Bug Report
about: File a bug report regarding Telodendria, its website, or its documentation.
body:
- type: markdown
attributes:
value: |
You are submitting a bug report. Please be sure to fill out the
title with a brief description of the bug.
- type: dropdown
attributes:
label: Type
description: Select the type of issue.
options:
- Memory Leak
- Crash
- Unexpected Error Message
- Documentation
- Website
- Other
validations:
required: true
- type: textarea
attributes:
label: Description
description: |
Please give a thorough and detailed description of the bug you
are reporting. Provide all the information you have, and do
some investigating to ensure you are providing a legitimate
issue report that is well thought out. **Include details on
how to reproduce the issue, or explicitly state that you were
unable to reproduce it.**
validations:
required: true
- type: textarea
attributes:
label: Log Output
description: |
Please copy and paste the relevant sections of the log output,
or the entire log if it is not unreasonably large. The logs
will be automatically formatted, no code block is necessary.
render: shell
validations:
required: true
- type: markdown
attributes:
value: |
Please attach any additional files that may aid in our
investigation of this issue, including screenshots, debugging
session stack traces and dumps, etc.

View File

@ -0,0 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: General Matrix Room
url: "https://matrix.to/#/#telodendria-general:bancino.net"
about: |
General discussion on Telodendria happens in this Matrix room. You
may get quicker feedback from there.

View File

@ -0,0 +1,18 @@
name: Feature Request
about: Request a new feature or enhancement be added to Telodendria.
title: "[Feature] "
body:
- type: markdown
attributes:
value: |
You are submitting a feature request. Please be sure to fill
out the title with a brief description of the feature you are
requesting.
- type: textarea
attributes:
label: Description
description: |
Please give a thorough and detailed description of the feature
you are requesting.
validations:
required: true

View File

@ -0,0 +1,25 @@
---
Please review the developer certificate of origin:
1. The contribution was created in whole or in part by me, and I have
the right to submit it under the open source licenses of the
Telodendria project; or
1. The contribution is based upon a previous work that, to the best of
my knowledge, is covered under an appropriate open source license and
I have the right under that license to submit that work with
modifications, whether created in whole or in part by me, under the
Telodendria project license; or
1. The contribution was provided directly to me by some other person
who certified (1), (2), or (3), and I have not modified it.
1. I understand and agree that this project and the contribution are
made public and that a record of the contribution&mdash;including all
personal information I submit with it&mdash;is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.
- [ ] I have read the Telodendria Project development certificate of
origin, and I certify that I have permission to submit this patch
under the conditions specified in it.

View File

@ -0,0 +1,29 @@
name: Compile Telodendria
run-name: Compile Telodendria on ${{ gitea.actor }}
on: [push]
jobs:
"Compile Telodendria":
strategy:
matrix:
os: [debian-v12.4, alpine-v3.19, openbsd-v7.4, freebsd-v14.0, netbsd-v9.3]
arch: [x86, x86_64]
exclude:
# 32-bit OpenBSD does not behave well in QEMU. Even when using
# QEMU to emulate i386, it utilizes 100% of its CPU core and is
# still extremely sluggish. Thus, we don't have a working 32-bit
# OpenBSD runner, so exclude it from the matrix configuration.
- os: openbsd-v7.4
arch: x86
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
steps:
- name: Check out repository
uses: actions/checkout@v3
with:
submodules: true
- name: Configure Telodendria
run: ./configure
- name: Configure & Build Cytoplasm
run: make cytoplasm
- name: Build Telodendria
run: make

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
# Telodendria .gitignore
build
out
data
Makefile
*-leaked.txt
.env
*.patch
*.orig
*.log
vgcore.*
*.core
contrib/.vagrant
src/Schema
src/include/Schema
man/mandoc.db

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "Cytoplasm"]
path = Cytoplasm
url = https://git.telodendria.io/Telodendria/Cytoplasm.git

16
CONTRIBUTORS.txt Normal file
View File

@ -0,0 +1,16 @@
N: Jordan Bancino
E: jordan@bancino.net
M: @jordan:bancino.net
W: https://bancino.net
D: Project Lead
L: United States
N: LDA
E: marie@doskel.net
E: ldasta@tedomum.fr
E: lda@freetards.xyz
M: @lda:a.freetards.xyz
M: @lda:pain.agency
M: @fourier:ari.lt
D: Developer
L: France

1
Cytoplasm Submodule

@ -0,0 +1 @@
Subproject commit 5d87da31cda74e6808eebca72e9475aabde86532

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files

136
README.md
View File

@ -1,7 +1,135 @@
# [Telodendria](https://telodendria.io)
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
**Telodendria** is an open source Matrix homeserver implementation written from scratch in ANSI C and designed to be lightweight and simple, yet functional.
**Telodendria** is an extremely powerful, yet lightweight and portable
chat server designed to be easy to install and configure. Powered by
the [Matrix](https://matrix.org) protocol, Telodendria empowers
everyone to run their own chat server on ordinary hardware, including
old and embedded devices. Whether you want a simple chat server just
for you and your friends and family, or want to talk to users on other
Matrix homeservers but don't want to go through all the hastle of
hosting a complicated, high-maintenance homeserver or joining an
existing homeserver for privacy or other reasons, then Telodendria
might be for you.
**Important:** This project is not developed on GitHub, or even with Git. As such, GitHub Pull Requests are not accepted. But that doesn't mean we don't want your contribution! You're more than welcome to clone this repo and use Git to make changes to the project if you'd prefer it to CVS, but when it comes time to actually submit your changes to this project, use [git format-patch](https://git-scm.com/docs/git-format-patch) to generate patch files, then submit them to the official Matrix room: [#telodendria-patches:bancino.net](https://matrix.to/#/#telodendria-patches:bancino.net).
!!!! **Note:** Telodendria still in development. See **Status** below.
Please see the `README.txt` file for the actual project `README`, which simply details the repository structure a little bit. All of **Telodendria**'s user and developer documentation is available as `man` pages, or online.
## What is Matrix?
Matrix is an **open standard** for *interoperable*, *decentralized*,
*secure*, and *real-time* communication over the internet.
Matrix can be thought of as the successor to email, but it works
very similar to iMessage, Discord, or direct messaging on most
social media networks. The primary difference between Matrix and these
other services, however, is that Matrix doesn't depend on one central
authority, and is designed in such a way to respect your privacy.
Matrix has proven itself over the last few
years to be a reliable communication tool, and has only gotten more
user-friendly over the course of its development. Matrix is capable
enough that it can&mdash;and should&mdash;totally replace any other
means of digital communication, and it offers a much higher degree
of security, simplicity, and functionality.
Strictly speaking, Matrix itself is just the *protocol* by which
clients and servers communicate. In order to use Matrix, we need
implementations of both clients and servers. Telodendria is a server
implementation of the Matrix protocol.
## Why Telodendria?
- **Lightweight:** Written in the C programming language, Telodendria
is automatically lighter and faster than other self-hosted chat servers.
It has very few external dependencies and is as self-contained as
possible.
- **Fully-Featured:** Most lightweight chat solutions compromise on
features. Telodendria is built on the fully-featured Matrix protocol,
which provides a chat experience that most normal users are familiar
with.
- **Portable:** You can run Telodendria on just about everything,
including more traditional options like a personal home server or VPS,
but also more obscure platforms like Raspberry Pis or retro computers.
Telodendria can run on a broad number of operating systems, which means
that no matter which platform and OS you prefer, there is a good chance
you can add Telodendria without much difficulty. It is also extremely
easy to migrate a Telodendria instance between platforms; just copy the
data directory to a new device.
- **Simple:** Telodendria is designed to be a simple, no-frills
chat server. It is easy to install, easy to configure, and easy to
maintain.
- **Stable:** Other Matrix homeservers develop at the pace of the
Matrix specification itself, which is to say quite rapidly. Changes are
always being made, and a version shipped 6 months ago is already
incredibly outdated. Telodendria, on the other hand, aims to be stable.
It should *just work* for long periods of time between upgrades, and
you should never feel like Telodendria is going to change significantly
between upgrades.
- **Well-Documented:** Telodendria places as much emphasis on documentation as on code, which means you can be sure that the documentation will always remain up-to-date, accurate, and most importantly, reasonably exhaustive.
[Read Technical Rationale &rightarrow;](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/dev/rationale.md)
## Get Started
Check out the [Documentation](https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md) to get started with
Telodendria.
## Status
Telodendria is in the very early stages of development. As such, it may
not yet deliver on all of its promises. Currently, Telodendria is not
ready for end-users yet. While it features very basic user
authentication, it does not actually work as a chat server yet.
We are hoping to ship Telodendria `v1.7.0-alpha4` by January of 2025. This
release should be usable for communication between **local users**
only. Additional features, including federation with other Matrix
homeservers will be added in future releases.
You can help speed up development by **sponsoring**
Telodendria or [getting involved](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/CONTRIBUTING.md).
## Sponsorship
Telodendria is maintained by a loosely-knit band of volunteers. The
project currently has no sponsors and thus no source of income to
pay for infrastructure costs and developer time. To ensure
Telodendria's long-term success, please consider sponsoring the
project.
You can make a recurring donation to Telodendria using
[LiberaPay](https://liberapay.com/Telodendria/donate). You can also make
one-time donations using
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
like to make a recurring donation larger than that allowed by
LiberaPay, please contact Jordan Bancino over Matrix at
`@jordan:bancino.net` or email at `jordan@bancino.net`.
### Benefits
While there are no set sponsorship tiers at this time, sponsoring
Telodendria is a mutually beneficial relationship. Depending on the
amount you donate, you can get your name, logo, and website links
on the [Sponsors](../sponsors) page, the project `README`, or the
main website.
## License
All of the code and documentation for Telodendria is licensed under a
modified MIT license. The MIT license is an extremely permissive
license that has very few restrictions. Please consult the
[`LICENSE.txt`](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/LICENSE.txt) file for the actual license text. It is
important to note that the Telodendria license text differs from the
original MIT license in the following ways:
- Where the MIT license states that the copyright notice and permission
notice shall be included in all copies or *substantial* portions of the
software, the Telodendria requires the copyright notice and
permission notice be included with *all* portions, regardless of the
size, by omitting the word *substantial*.
The Telodendria logo in all its forms, including the ASCII
representation, belongs solely to the Telodendria project. It must be
used only to represent the official Telodendria project. You are free
to use the logo in any way as long as it represents or links to the
official project. If Telodendria is forked, the logo must be removed
completely from the project, and optionally replaced by a different
one.

View File

@ -1,62 +0,0 @@
.= -=-
:.:+ .=:.
.=+-==. :.
.+- =.
.+ :+.
==. -+:
=++==--:: =+.
.:::--=+=: :+=
:==. -=:
===----=-. ... :+.
:==+=======: .-+-::-+-=+=
.==*%#======= :+- ..
.:--=-===+=========-. :+:
.=++=::..:============-+=-=-
:+=: :=+-: .-=========-. .
=+++: .:=+-: .:--. .--:==:
::---:.. :=+: ==
++. .+-
=+ .+- ...:
+- -+-:-+=::+:
:=-....:-=: .--: =-
-++=:.:::..
=======================================================
|_ _|__| | ___ __| | ___ _ __ __| |_ __(_) __ _
| |/ _ \ |/ _ \ / _` |/ _ \ '_ \ / _` | '__| |/ _` |
| | __/ | (_) | (_| | __/ | | | (_| | | | | (_| |
|_|\___|_|\___/ \__,_|\___|_| |_|\__,_|_| |_|\__,_|
=======================================================
Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
This is the source code for Telodendria, a Matrix homeserver written
in C. All of the documentation is available as man pages in the
man/ directory, or online at https://telodendria.io
If information is missing from the documentation, please feel free
to reach out to #telodendria-general:bancino.net on Matrix.
This file documents the directory structure of the source code
repository.
Telodendria/
contrib/ - Supplemental files, such as example configs.
man/ - The official documentation as man pages.
site/ - The official website.
src/ - The C source code for Telodendria.
include/ - Header files for the source code.
Routes/ - Where Matrix API endpoints are implemented
tests/ - Unit and integration tests will eventually go here.
tools/ - Development environment and tools.
To cut a new release for Telodendria, perform the following
steps. This is just a reference for me so I don't mess it up.
- Update tools/bin/td to declare the next version number.
- Update site/index.html with links to the new version.
- Make sure man/man7/telodendria-changelog.7 is up to date
with the latest information.
- Commit any changes made in the previous steps.
- Run the release recipe: td release
- Deploy the site: td site

58
Schema/ClientEvent.json Normal file
View File

@ -0,0 +1,58 @@
{
"guard": "TELODENDRIA_SCHEMA_CLIENTEVENT_H",
"header": "Schema/ClientEvent.h",
"types": {
"ClientEventUnsignedData": {
"type": "struct",
"fields": {
"age": {
"type": "integer"
},
"prev_content": {
"type": "object"
},
"redacted_because": {
"type": "object"
},
"transaction_id": {
"type": "string"
}
}
},
"ClientEvent": {
"type": "struct",
"fields": {
"content": {
"type": "object",
"required": true
},
"event_id": {
"type": "string",
"required": true
},
"origin_server_ts": {
"type": "integer",
"required": true
},
"room_id": {
"type": "string",
"required": true
},
"sender": {
"type": "string",
"required": true
},
"state_key": {
"type": "string"
},
"type": {
"type": "string",
"required": true
},
"_unsigned": {
"type": "ClientEventUnsignedData"
}
}
}
}
}

88
Schema/Config.json Normal file
View File

@ -0,0 +1,88 @@
{
"guard": "TELODENDRIA_SCHEMA_CONFIG_H",
"header": "Schema\/Config.h",
"include": [ "Cytoplasm\/Db.h", "Cytoplasm/HttpServer.h" ],
"types": {
"ConfigTls": {
"fields": {
"cert": { "type": "string", "required": true },
"key": { "type": "string", "required": true }
},
"type": "struct"
},
"ConfigListener": {
"fields": {
"port": { "type": "integer", "required": true },
"threads": { "type": "integer", "required": false },
"maxConnections": { "type": "integer", "required": false },
"tls": { "type": "ConfigTls", "required": false }
},
"type": "struct"
},
"ConfigRunAs": {
"fields": {
"uid": { "type": "string", "required": false },
"gid": { "type": "string", "required": true }
},
"type": "struct"
},
"ConfigLogOutput": {
"fields": {
"stdout": { "name": "CONFIG_LOG_OUTPUT_STDOUT" },
"file": { "name": "CONFIG_LOG_OUTPUT_FILE" },
"syslog": { "name": "CONFIG_LOG_OUTPUT_SYSLOG" }
},
"type": "enum"
},
"ConfigLogLevel": {
"fields": {
"message": { "name": "CONFIG_LOG_LEVEL_MESSAGE" },
"debug": { "name": "CONFIG_LOG_LEVEL_DEBUG" },
"notice": { "name": "CONFIG_LOG_LEVEL_NOTICE" },
"warning": { "name": "CONFIG_LOG_LEVEL_WARNING" },
"error": { "name": "CONFIG_LOG_LEVEL_ERROR" }
},
"type": "enum"
},
"ConfigLogConfig": {
"fields": {
"output": { "type": "ConfigLogOutput", "required": true },
"level": { "type": "ConfigLogLevel", "required": false },
"timestampFormat":{ "type": "string", "required": false },
"color": { "type": "boolean", "required": false }
},
"type": "struct"
},
"Db *": { "type": "extern" },
"DbRef *": { "type": "extern" },
"char *": { "type": "extern" },
"Config": {
"fields": {
"db": { "type": "Db *", "ignore": true },
"ref": { "type": "DbRef *", "ignore": true },
"ok": { "type": "boolean", "ignore": true },
"err": { "type": "char *", "ignore": true },
"listen": { "type": "[ConfigListener]", "required": true },
"runAs": { "type": "ConfigRunAs", "required": false },
"log": { "type": "ConfigLogConfig", "required": true },
"serverName": { "type": "string", "required": true },
"baseUrl": { "type": "string", "required": false },
"identityServer": { "type": "string", "required": false },
"pid": { "type": "string", "required": false },
"maxCache": { "type": "integer", "required": false },
"federation": { "type": "boolean", "required": true },
"registration": { "type": "boolean", "required": true }
},
"type": "struct"
}
}
}

121
Schema/Filter.json Normal file
View File

@ -0,0 +1,121 @@
{
"guard": "TELODENDRIA_SCHEMA_FILTER_H",
"header": "Schema\/Filter.h",
"types": {
"FilterRoom": {
"fields": {
"not_rooms": {
"type": "[string]"
},
"state": {
"type": "FilterRoomEvent"
},
"include_leave": {
"type": "boolean"
},
"timeline": {
"type": "FilterRoomEvent"
},
"account_data": {
"type": "FilterRoomEvent"
},
"rooms": {
"type": "[string]"
},
"ephemeral": {
"type": "FilterRoomEvent"
}
},
"type": "struct"
},
"FilterEventFormat": {
"fields": {
"federation": {
"name": "FILTER_FORMAT_FEDERATION"
},
"client": {
"name": "FILTER_FORMANT_CLIENT"
}
},
"type": "enum"
},
"FilterEvent": {
"fields": {
"not_senders": {
"type": "[string]"
},
"limit": {
"type": "integer"
},
"senders": {
"type": "[string]"
},
"types": {
"type": "[string]"
},
"not_types": {
"type": "[string]"
}
},
"type": "struct"
},
"Filter": {
"fields": {
"event_format": {
"type": "FilterEventFormat"
},
"presence": {
"type": "FilterEvent"
},
"account_data": {
"type": "FilterEvent"
},
"room": {
"type": "FilterRoom"
},
"event_fields": {
"type": "[string]"
}
},
"type": "struct"
},
"FilterRoomEvent": {
"fields": {
"not_rooms": {
"type": "[string]"
},
"not_senders": {
"type": "[string]"
},
"limit": {
"type": "integer"
},
"senders": {
"type": "[string]"
},
"include_redundant_members": {
"type": "boolean"
},
"types": {
"type": "[string]"
},
"rooms": {
"type": "[string]"
},
"lazy_load_members": {
"type": "boolean"
},
"not_types": {
"type": "[string]"
},
"contains_url": {
"type": "boolean"
},
"unread_thread_notifications": {
"type": "boolean"
}
},
"type": "struct"
}
}
}

38
Schema/LoginRequest.json Normal file
View File

@ -0,0 +1,38 @@
{
"header": "Schema\/LoginRequest.h",
"types": {
"LoginRequestType": {
"fields": {
"m.login.password": { "name": "REQUEST_TYPE_PASSWORD" }
},
"type": "enum"
},
"LoginRequestUserIdentifier": {
"fields": {
"type": { "type": "string" },
"user": { "type": "string" }
},
"type": "struct"
},
"LoginRequest": {
"fields": {
"type": { "type": "LoginRequestType" },
"identifier": { "type": "object" },
"password": { "type": "string" },
"address": { "type": "string" },
"user": { "type": "string" },
"device_id": { "type": "string" },
"initial_device_display_name": { "type": "string" },
"medium": { "type": "string" },
"token": { "type": "string" },
"refresh_token": { "type": "boolean" }
},
"type": "struct"
}
},
"guard": "TELODENDRIA_SCHEMA_LOGIN_REQUEST_H"
}

81
Schema/PduV1.json Normal file
View File

@ -0,0 +1,81 @@
{
"guard": "TELODENDRIA_SCHEMA_PDUV1_H",
"header": "Schema/PduV1.h",
"types": {
"PduV1EventHash": {
"type": "struct",
"fields": {
"sha256": {
"type": "string",
"required": true
}
}
},
"PduV1UnsignedData": {
"type": "struct",
"fields": {
"age": {
"type": "integer"
}
}
},
"PduV1": {
"type": "struct",
"fields": {
"auth_events": {
"type": "array",
"required": true
},
"content": {
"type": "object",
"required": true
},
"depth": {
"type": "integer",
"required": true
},
"event_id": {
"type": "string",
"required": true
},
"hashes": {
"type": "PduV1EventHash",
"required": true
},
"origin_server_ts": {
"type": "integer",
"required": true
},
"prev_events": {
"type": "array",
"required": true
},
"redacts": {
"type": "string"
},
"room_id": {
"type": "string",
"required": true
},
"sender": {
"type": "string",
"required": true
},
"signatures": {
"type": "object",
"required": true
},
"state_key": {
"type": "string"
},
"type": {
"type": "string",
"required": true
},
"_unsigned": {
"type": "PduV1UnsignedData"
}
}
}
}
}

77
Schema/PduV3.json Normal file
View File

@ -0,0 +1,77 @@
{
"guard": "TELODENDRIA_SCHEMA_PDUV3_H",
"header": "Schema/PduV3.h",
"types": {
"PduV3EventHash": {
"type": "struct",
"fields": {
"sha256": {
"type": "string",
"required": true
}
}
},
"PduV3UnsignedData": {
"type": "struct",
"fields": {
"age": {
"type": "integer"
}
}
},
"PduV3": {
"type": "struct",
"fields": {
"auth_events": {
"type": "[string]",
"required": true
},
"content": {
"type": "object",
"required": true
},
"depth": {
"type": "integer",
"required": true
},
"hashes": {
"type": "PduV3EventHash",
"required": true
},
"origin_server_ts": {
"type": "integer",
"required": true
},
"prev_events": {
"type": "[string]",
"required": true
},
"redacts": {
"type": "string"
},
"room_id": {
"type": "string",
"required": true
},
"sender": {
"type": "string",
"required": true
},
"signatures": {
"type": "object",
"required": true
},
"state_key": {
"type": "string"
},
"type": {
"type": "string",
"required": true
},
"_unsigned": {
"type": "PduV3UnsignedData"
}
}
}
}
}

49
Schema/RegToken.json Normal file
View File

@ -0,0 +1,49 @@
{
"guard": "TELODENDRIA_SCHEMA_REGTOKEN_H",
"header": "Schema\/RegToken.h",
"include": [
"Cytoplasm\/Db.h"
],
"types": {
"Db *": {
"type": "extern"
},
"DbRef *": {
"type": "extern"
},
"RegTokenInfo": {
"fields": {
"db": {
"type": "Db *",
"ignore": true
},
"ref": {
"type": "DbRef *",
"ignore": true
},
"name": {
"type": "string"
},
"created_by": {
"type": "string"
},
"created_on": {
"type": "integer"
},
"expires_on": {
"type": "integer"
},
"used": {
"type": "integer"
},
"uses": {
"type": "integer"
},
"grants": {
"type": "array"
}
},
"type": "struct"
}
}
}

17
Schema/Registration.json Normal file
View File

@ -0,0 +1,17 @@
{
"header": "Schema\/Registration.h",
"types": {
"RegistrationRequest": {
"fields": {
"username": { "type": "string" },
"password": { "type": "string" },
"device_id": { "type": "string" },
"inhibit_login": { "type": "boolean" },
"initial_device_display_name": { "type": "string" },
"refresh_token": { "type": "boolean" }
},
"type": "struct"
}
},
"guard": "TELODENDRIA_SCHEMA_REGISTRATION_H"
}

21
Schema/RequestToken.json Normal file
View File

@ -0,0 +1,21 @@
{
"header": "Schema\/RequestToken.h",
"types": {
"RequestToken": {
"fields": {
"client_secret": { "type": "string" },
"send_attempt": { "type": "integer" },
"next_link": { "type": "string" },
"id_access_token": { "type": "string" },
"id_server": { "type": "string" },
"email": { "type": "string" },
"country": { "type": "string" },
"phone_number": { "type": "string" }
},
"type": "struct"
}
},
"guard": "TELODENDRIA_SCHEMA_REQUESTTOKEN_H"
}

View File

@ -0,0 +1,120 @@
{
"guard": "TELODENDRIA_SCHEMA_ROOMCREATE_H",
"header": "Schema/RoomCreateRequest.h",
"types": {
"RoomVisibility": {
"fields": {
"public": {
"name": "ROOM_PUBLIC"
},
"private": {
"name": "ROOM_PRIVATE"
}
},
"type": "enum"
},
"RoomCreateRequest": {
"fields": {
"invite": {
"type": "[string]"
},
"room_version": {
"type": "string"
},
"invite_3pid": {
"type": "[RoomInvite3Pid]"
},
"topic": {
"type": "string"
},
"visibility": {
"type": "RoomVisibility"
},
"creation_content": {
"type": "object"
},
"is_direct": {
"type": "boolean"
},
"name": {
"type": "string"
},
"initial_state": {
"type": "[RoomStateEvent]"
},
"power_level_content_override": {
"type": "object"
},
"room_alias_name": {
"type": "string"
},
"preset": {
"type": "RoomCreatePreset"
}
},
"type": "struct"
},
"RoomInvite3Pid": {
"fields": {
"id_access_token": {
"required": true,
"type": "string"
},
"address": {
"required": true,
"type": "string"
},
"medium": {
"required": true,
"type": "Room3PidMedium"
},
"id_server": {
"required": true,
"type": "string"
}
},
"type": "struct"
},
"Room3PidMedium": {
"fields": {
"msisdn": {
"name": "ROOM_3PID_MSISDN"
},
"email": {
"name": "ROOM_3PID_EMAIL"
}
},
"type": "enum"
},
"RoomCreatePreset": {
"fields": {
"public_chat": {
"name": "ROOM_CREATE_PUBLIC"
},
"trusted_private_chat": {
"name": "ROOM_CREATE_TRUSTED"
},
"private_chat": {
"name": "ROOM_CREATE_PRIVATE"
}
},
"type": "enum"
},
"RoomStateEvent": {
"fields": {
"content": {
"required": true,
"type": "object"
},
"state_key": {
"type": "string"
},
"type": {
"required": true,
"type": "string"
}
},
"type": "struct"
}
}
}

View File

@ -1,49 +0,0 @@
Telodendria To-Do List
======================
Key:
[ ] Not Started
[x] Done
[~] In Progress
[!] Won't Fix
Milestone: v1.0.0
-----------------
[ ] Database upgrades/migration path
[~] Client-Server API
[ ] API Standards
[x] Error responses
[x] Web Browser Clients
[x] /_matrix/client/versions
[x] Server Discovery
[x] Well-known URIs
[x] Make base-url optional in config
[x] Make identity-server optional in config
[ ] Client Authentication
[ ] Capabilities negotiation
[ ] Filtering
[ ] Events
[ ] Rooms
[ ] User Data
[ ] Security (Rate Limiting)
[ ] Modules
[ ] Instant Messaging
[ ] Voice over IP
[ ] Receipts
[ ] Fully Read Markers
[ ] Content Repository
[ ] Send-To-Device Messaging
[ ] Server-Server API
[ ] Application Service API
[ ] Identity Service API
[ ] Push Gateway API
[ ] Room Versions
[ ] Create a command line tool to manage Telodendria
[ ] User management
[ ] Room management
[ ] Migrate from Synapse or Dendrite, whichever is more mainstream by
the time we get here.

316
configure vendored Executable file
View File

@ -0,0 +1,316 @@
#!/usr/bin/env sh
#
# Argument Parsing
#
echo "Build Configuration"
echo "-------------------"
BUILD="build"
OUT="out"
SRC="src"
INCLUDE="src/include"
TOOLS="tools/src"
SCHEMA="Schema"
CYTOPLASM="Cytoplasm"
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
LIBS="-lm -pthread -lCytoplasm"
# Set default args for all platforms
SCRIPT_ARGS="--prefix=/usr/local --bin-name=telodendria --version=1.7.0-alpha4"
if [ -f "${CYTOPLASM}/configure" ]; then
SCRIPT_ARGS="${SCRIPT_ARGS} --cytoplasm=${CYTOPLASM}"
else
SCRIPT_ARGS="${SCRIPT_ARGS} --cytoplasm=" # No cytoplasm path.
fi
# Set compiler depending on the platform.
case "$(uname)" in
Linux|NetBSD)
# These systems typically use GCC.
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=gcc"
;;
OpenBSD|FreeBSD)
# These systems typically use Clang.
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=clang"
;;
*)
# Use default compiler which is required to be present on
# all POSIX-compliant systems.
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=c99"
;;
esac
# Append any additional args specified by user
SCRIPT_ARGS="${SCRIPT_ARGS} $@"
echo "Processing options..."
echo "Ran with arguments: $SCRIPT_ARGS"
# Process all arguments
for arg in $SCRIPT_ARGS; do
case "$arg" in
--cc=*)
CC=$(echo "$arg" | cut -d '=' -f 2-)
case "${CC}" in
gcc*|clang*)
# "Fancy" compilers that support a plethora of additional flags we
# want to enable if present.
CFLAGS="-Wall -Wextra -Werror -pedantic -std=c99 -O3 ${CFLAGS}"
LDFLAGS="${LDFLAGS} -flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections"
;;
esac
;;
--prefix=*)
PREFIX=$(echo "$arg" | cut -d '=' -f 2-)
;;
--bin-name=*)
BIN_NAME=$(echo "$arg" | cut -d '=' -f 2-)
;;
--version=*)
VERSION=$(echo "$arg" | cut -d '=' -f 2-)
;;
--enable-debug)
DEBUG="-O0 -g"
;;
--disable-debug)
DEBUG=""
;;
--cytoplasm=*)
CYTOPLASM=$(echo "$arg" | cut -d '=' -f 2-)
if [ -n "${CYTOPLASM}" ]; then
if [ ! -f "${CYTOPLASM}/configure" ]; then
echo "Path for Cytoplasm does not appear to actually contain Cytoplasm source:"
echo "${CYTOPLASM}"
exit 1
fi
CFLAGS="${CFLAGS} -I${CYTOPLASM}/include"
LDFLAGS="-L${CYTOPLASM}/out/lib ${LDFLAGS}"
fi
;;
*)
echo "Invalid argument: $arg"
exit 1
;;
esac
done
CFLAGS="${CFLAGS} '-DTELODENDRIA_VERSION=\"${VERSION}\"' ${DEBUG}"
LDFLAGS="${LDFLAGS} ${LIBS}"
#
# Makefile generation
#
collect() {
from="$1"
orig_ext="$2"
new_ext="$3"
prefix="$4"
exec="$5"
find "${from}" -name "*${orig_ext}" -type f | while IFS= read -r src; do
src=$(echo "$src" | sed -e "s|^${from}||g")
obj=$(echo "$src" | sed -e "s|${orig_ext}\$|${new_ext}|g")
obj="${prefix}${obj}"
src="${from}${src}"
"${exec}" "${src}" "${obj}"
done
}
prefix() {
prefix="$1"
shift
for thing in $@; do
printf "${prefix}${thing} "
done
}
cytoplasm_tool() {
tool="$1"
if [ -n "${CYTOPLASM}" ]; then
echo "LD_LIBRARY_PATH=${CYTOPLASM}/out/lib ${CYTOPLASM}/out/bin/$tool"
else
echo "$tool"
fi
}
print_src() {
printf '%s ' "$1"
}
print_obj() {
printf '%s ' "$2"
}
get_deps() {
src="$1"
${CC} -I${INCLUDE} -I${BUILD} $(if [ -n "${CYTOPLASM}" ]; then echo "-I${CYTOPLASM}/include"; fi) -E "$src" \
| grep '^#' \
| awk '{print $3}' \
| cut -d '"' -f 2 \
| sort \
| uniq \
| grep -v '^[/<]' \
| grep -e "^${SRC}/" -e "^${BUILD}/" \
| while IFS= read -r dep; do
printf "%s " "$dep"
done
}
compile_obj() {
src="$1"
obj="$2"
pref="${obj}: $(get_deps ${src})"
echo "$pref $(collect ${SCHEMA}/ .json .h ${BUILD}/Schema/ print_obj)"
echo "${TAB}@mkdir -p $(dirname ${obj})"
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
}
compile_bin() {
src="$1"
out="$2"
depObjs=$(prefix ${BUILD}/ CanonicalJson.o Telodendria.o)
echo "${out}: ${src}"
echo "${TAB}@mkdir -p ${OUT}/bin"
echo "${TAB}\$(CC) \$(CFLAGS) -o \"${out}\" \"${src}\" $depObjs \$(LDFLAGS)"
}
compile_doc() {
src="$1"
out="$2"
if echo "${src}" | grep "Schema" > /dev/null; then
return
fi
echo "${out}: ${src}"
echo "${TAB}@mkdir -p ${OUT}/man/man3"
echo "${TAB}$(cytoplasm_tool hdoc) -D \"Os=${BIN_NAME}\" -i \"${src}\" -o \"${out}\""
}
print_doc() {
if echo "${src}" | grep "Schema" > /dev/null; then
return
fi
printf '%s ' "$2"
}
compile_schema() {
src="$1"
out="$2"
obj="${BUILD}/Schema/${out}.o"
echo "${BUILD}/Schema/${out}.h:"
echo "${TAB}@mkdir -p ${BUILD}/Schema"
echo "${TAB}$(cytoplasm_tool j2s) -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
echo "${BUILD}/Schema/${out}.c:"
echo "${TAB}@mkdir -p ${BUILD}/Schema"
echo "${TAB}$(cytoplasm_tool j2s) -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
echo "${obj}: ${src} ${BUILD}/Schema/${out}.c"
echo "${TAB}@mkdir -p ${BUILD}/Schema"
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${BUILD}/Schema/${out}.c\""
}
install_out() {
src="$1"
out="$2"
echo "${TAB}install -D \"$src\" \"$out\""
}
install_man() {
src="${OUT}/man/man3/${BIN_NAME}-$(basename $1 .h).3"
out="$2"
echo "${TAB}install -D \"$src\" \"$out\""
}
uninstall_out() {
src="$1"
out="$2"
echo "${TAB}rm \"$out\""
}
echo "Generating Makefile..."
OBJS="$(collect ${SRC}/ .c .o ${BUILD}/ print_obj) $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
TAB=$(printf '\t')
cat << EOF > Makefile
.POSIX:
# Generated by '$0' on $(date).
# This file should generally not be manually edited.
CC = ${CC}
PREFIX = ${PREFIX}
CFLAGS = ${CFLAGS}
LDFLAGS = ${LDFLAGS}
all: ${BIN_NAME} docs tools
docs: $(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${BIN_NAME}- print_doc)
tools: $(collect ${TOOLS}/ .c '' ${OUT}/bin/ print_obj)
format:
${TAB}find . -name '*.c' | while IFS= read -r src; do \\
${TAB} if indent "\$\$src"; then \\
${TAB} rm \$\$(basename "\$\$src").BAK; \\
${TAB} fi \\
${TAB}done
license:
${TAB}find . -name '*.[ch]' | while IFS= read -r src; do \\
${TAB} srcHeader=\$\$(grep -n -m 1 '^ \*/' "\$\$src" | cut -d ':' -f 1); \\
${TAB} head -n\$\$srcHeader \$\$src | \\
${TAB} diff -u -p - "LICENSE.txt" | \\
${TAB} patch "\$\$src" | grep -v "^Hmm"; \\
${TAB}done
${BIN_NAME}: ${OUT}/bin/${BIN_NAME}
install: ${BIN_NAME}
${TAB}install -D ${OUT}/bin/${BIN_NAME} \$(PREFIX)/bin/${BIN_NAME}
uninstall:
${TAB}rm \$(PREFIX)/bin/${BIN_NAME}
clean:
${TAB}rm -r "${BUILD}" "${OUT}"
${OUT}/bin/${BIN_NAME}: ${OBJS}
${TAB}@mkdir -p "${OUT}/bin"
${TAB}\$(CC) -o "${OUT}/bin/${BIN_NAME}" ${OBJS} \$(CFLAGS) \$(LDFLAGS)
$(collect ${SCHEMA}/ .json '' '' compile_schema)
$(collect ${SRC}/ .c .o ${BUILD}/ compile_obj)
$(collect ${TOOLS}/ .c '' ${OUT}/bin/ compile_bin)
$(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${BIN_NAME}- compile_doc)
$(
if [ -n "${CYTOPLASM}" ]; then
echo "cytoplasm:"
echo "${TAB}cd ${CYTOPLASM} && ./configure && \$(MAKE)"
fi
)
EOF
echo "Done. Run 'make' to build ${BIN_NAME}."

View File

@ -3,6 +3,4 @@ all:
install:
install build/telodendria $(PREFIX)/bin/telodendria
mkdir -p $(PREFIX)/share/examples/telodendria
install contrib/production.conf $(PREFIX)/share/examples/telodendria/telodendria.conf
find man -name 'telodendria*\.[1-8]' -exec install {} $(PREFIX)/{} \;

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
@ -21,36 +22,44 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef TELODENDRIA_DB_H
#define TELODENDRIA_DB_H
#include <Tls.h>
#ifndef DB_MIN_CACHE
#define DB_MIN_CACHE 1024
#endif
#if TLS_IMPL == TLS_TEMPLATE /* Set your TLS_* implementation
* flag here */
#include <stddef.h>
/*
* #include statements and any implementation structures
* needed should go here.
*/
#include <HashMap.h>
void *
TlsInitClient(int fd, const char *serverName)
{
return NULL;
}
typedef struct Db Db;
typedef struct DbRef DbRef;
void *
TlsInitServer(int fd, const char *crt, const char *key)
{
return NULL;
}
extern Db *
DbOpen(char *, size_t);
ssize_t
TlsRead(void *cookie, void *buf, size_t nBytes)
{
return -1;
}
extern void
DbClose(Db *);
ssize_t
TlsWrite(void *cookie, void *buf, size_t nBytes)
{
return -1;
}
extern DbRef *
DbCreate(Db *, size_t,...);
extern DbRef *
DbLock(Db *, size_t,...);
extern int
DbUnlock(Db *, DbRef *);
extern HashMap *
DbJson(DbRef *);
int
TlsClose(void *cookie)
{
return -1;
}
#endif

34
contrib/Vagrantfile vendored Normal file
View File

@ -0,0 +1,34 @@
Vagrant.configure("2") do |config|
config.vm.box = "generic/openbsd7"
config.vm.network "forwarded_port", guest: 80, host: 80
config.vm.network "forwarded_port", guest: 443, host: 443
config.vm.network "forwarded_port", guest: 8008, host: 8008
# NOTE: This address is not within the allowed ranges.
# To allow this address, simply allow all ranges by specifying
# this in /etc/vbox/networks.conf (if you use the Virtualbox provider, or change the configured address):
# * 0.0.0.0/0 ::/0
config.vm.network "private_network", ip: "172.17.0.101"
# File watcher which syncs the project directory to /vagrant on the vm
config.vm.synced_folder "../", "/vagrant"
config.vm.provision "shell", inline: <<-EOF
cp /vagrant/contrib/relayd.conf /etc/relayd.conf
sed -i s/127.0.0.1/0.0.0.0/ /etc/relayd.conf
mkdir -p -m 0700 /etc/ssl/private
openssl req -x509 -newkey rsa:4096 \
-days 365 -nodes \
-subj '/CN=telodendria' \
-keyout /etc/ssl/private/telodendria.key \
-out /etc/ssl/telodendria.crt
relayd -n
rcctl enable relayd
rcctl restart relayd
cat /vagrant/tools/env.sh >> /home/vagrant/.bash_profile
sed -i 's#$(pwd)#/vagrant#' /home/vagrant/.bash_profile
sed -i 's#find tools/bin#find /vagrant/tools/bin#' /home/vagrant/.bash_profile
mkdir /vagrant/data
cp /vagrant/contrib/development.conf /vagrant/contrib/development.conf.bak
sed -i 's/"localhost"/"vagrant"/' /vagrant/contrib/development.conf
### If you changed the address in the config above you might want to change it here as well:
sed -i s#http://localhost:8008#https://172.17.0.101:443# /vagrant/contrib/development.conf
EOF
end

View File

@ -1,14 +1,18 @@
{
"serverName": "localhost",
"baseUrl": "http://localhost:8008",
"dataDir": "./data",
"federation": true,
"registration": true,
"threads": 2,
"log": {
"output": "stdout",
"level": "debug",
"timestampFormat": "none",
"color": true
}
"log": {
"output": "stdout",
"color": true,
"timestampFormat": "none",
"level": "debug"
},
"listen": [
{
"port": 8008,
"tls": false
}
],
"registration": true,
"serverName": "localhost",
"baseUrl": "http:\/\/localhost:8008",
"federation": true
}

View File

@ -1,13 +1,24 @@
{
"serverName": "example.com",
"baseUrl": "https://matrix.example.com",
"identityServer": "https://identity.example.com",
"dataDir": "/var/telodendria",
"federation": true,
"registration": false,
"threads": 4,
"maxCache": 512000000,
"log": {
"output": "file"
"log": {
"output": "file"
},
"listen": [
{
"port": 8008,
"tls": false
},
{
"port": 8448,
"tls": {
"cert": "telodendria.crt",
"key": "telodendria.key"
}
}
],
"serverName": "example.com",
"identityServer": "https://identity.example.com",
"baseUrl": "https://matrix.example.com",
"registration": false,
"federation": true,
"maxCache": 512000000
}

View File

@ -17,6 +17,6 @@ relay proxy {
protocol httpproxy
transparent forward to <telodendria> port 8008
forward to <telodendria> port 8008
}

291
docs/CHANGELOG.md Normal file
View File

@ -0,0 +1,291 @@
# Telodendria Change Log
This document contains the complete change log for every official release of Telodendria.
It is intended to be updated with every commit that makes a user-facing change worth
reporting in the change log. As such, it changes frequently between releases. Final
change log entries are published as [Releases](releases).
## v1.7.0-alpha4
**Not Released Yet.**
This release brings filters, rooms, and events! The core of the Matrix
protocol architecture is now in place.
Note that the versioning scheme has changed from `v0.X.0` to
`v1.7.0-alphaX`. This is so that Telodendria releases correspond to the
Matrix specification that they implement, in accordance with
[this blog post](https://telodendria.io/blog/on-matrixs-release-cadence-and-state-resolution-v1).
This versioning scheme change does not indicate a drastic leap forward
in Telodendria's development&mdash;the `-alpha4` suffix indicates that
this is the 4th pre-release, with the target being a stable `v1.7.0`.
Note also that we still have a *long* way to go before we reach that
stable release.
### Matrix Specification
The following endpoints were added:
- **POST** `/_matrix/client/v3/user/{userId}/filter`
- **GET** `/_matrix/client/v3/user/{userId}/filter/{filterId}`
### Bug Fixes & General Improvements
- Use `j2s` for parsing the configuration
- Fixed a double-free in `RouteUserProfile()` that would cause errors
with certain Matrix clients. (#35)
- Improved compatibility with NetBSD on various platforms.
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository. It
will now be maintained separately and have its own releases as well.
- Use a `configure` script and `make` to build Telodendria instead of
custom scripts.
- Greatly simplified some endpoint code by using Cytoplasm's `j2s` for
parsing request bodies.
- Create a parser API for grammars found in Matrix, and refactor the
User API to use it.
### New Features
- Implemented a `"pid"` option in the configuration, allowing Telodendria
to write its process ID to a specified file.
- Moved all administrator API endpoints to `/_telodendria/admin/v1`,
because later revisions of the administrator API may break clients, so
we want a way to give those breaking revisions new endpoints.
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins
to be able to deactivate users.
- Added a **PUT** option to `/_telodendria/admin/v1/config` that gives
the ability to change only a subset of the configuration.
- Implemented the following APIs for managing registration tokens:
- **GET** `/_telodendria/admin/tokens`
- **GET** `/_telodendria/admin/tokens/[token]`
- **POST** `/_telodendria/admin/tokens`
- **DELETE** `/_telodendria/admin/tokens/[token]`
- **GET** `/_matrix/client/v3/directory/room/[alias]`
- **PUT** `/_matrix/client/v3/directory/room/[alias]`
- **DELETE** `/_matrix/client/v3/directory/room/[alias]`
- **GET** `/_matrix/client/v3/rooms/[id]/aliases`
## v0.3.0
**Saturday, June 10, 2023**
Introducing a new configuration API and Cytoplasm, a general-purpose C library that
supports source/sink-agnostic I/O, TLS, an HTTP client, and more! The third major
release of Telodendria packs a lot of architectural improvements on top of supporting
more of the Matrix specification.
### Matrix Specification
Added support for the following endpoints:
- `/_matrix/client/v3/account/whoami`
- `/_matrix/client/v3/account/password`
- `/_matrix/client/v3/account/deactivate`
- `/_matrix/client/v3/profile/*`
- `/_matrix/client/v3/capabilities`
- `/_matrix/client/v3/auth/*/fallback/web`
There is also support for token-based user registration. Note that there is as of
yet no admin-facing way to create these registration tokens, but the APIs are in
place.
### New Features
- Added a new `HttpClient` API for making HTTP requests. This will eventually be
used for federating with other Matrix homeservers.
- Added support for pretty-printing JSON in `Json`. Telodendria itself does not
pretty-print JSON, but this is useful for debugging and building useful tools.
- Added a handful of new development tools built on the Telodendria APIs. New
tools include `http`, a command line tool for making HTTP requests, similar to
`curl`, `json`, a command line tool for working with JSON, similar to `jq`, and
`http-debug-server`, a simple HTTP server that just prints requests out to standard
output and returns an empty JSON object. `http` and `json` are replacements for
`curl` and `jq` that build on the `HttpClient` and `Json` APIs. They exist mainly
to test those APIs, but also to reduce the number of dependencies that Telodendria
has. `http-debug-server` exists to test the `HttpServer` and `HttpClient` APIs.
- Replaced all usage of `jq` with the new `json` tool. `jq` is no longer a development
dependency.
- Replaced all usage of `curl` with the new `http` tool. `curl` is no longer a
required development dependency.
- Added a new `tt` script for easily making Matrix requests against Telodendria
in development.
- Added TLS support to both the HTTP client and server. Currently, Telodendria
supports LibreSSL and OpenSSL, but other TLS libraries should be extremely easy
to add support for.
- Added support for spinning up multiple HTTP servers. This is useful for having
a TLS port and a non-TLS port, for example.
- Moved all program configuration to the data directory and added an administrator
API endpoint to manage it. It is now no longer recommended to manually update the
configuration file. Consult the [Administrator API](user/admin/README.md) documentation
and the [Configuration](user/config.md) documentation.
- Added an administrator API endpoint for process control. Telodendria can now be
restarted or shutdown via API endpoint.
- Added an administrator API endpoint for getting statistics about the running
Telodendria process.
- Added support for user privileges, a way to have fine-grained control over what
users are allowed to do with the administrator API. Administrator APIs for setting
and getting privileges is now supported, and registration tokens have privileges
associated with them so that users created with a token will automatically be given
the specified privileges.
### Fixes & General Improvements
- Fixed a few warnings that were generated on some obscure compilers.
- Moved the `main()` function to its own file to make it easier to link other
programs with the Telodendria APIs.
- Fixed the development tools environment script. Apparently using a hyphen as a bullet
point is not very portable, because some shell implementations of `printf` interpret it
as a flag. Switched to an asterisk.
- Fixed some intermittent I/O errors that would occur as a result of race conditions in
`JsonConsomeWhitespace()`. This function, and a few others, expect I/O to be blocking,
but the `HttpServer` sets up I/O to be non-blocking, leading to occasional failures in
JSON parsing.
- Abstracted all I/O into the new `Io` and `Stream` APIs, which provide an input- and
output- agnostic stream processing interface. This allows for a simple implementation of
proxies, TLS, and other stream filters without having to change any of the existing
code.
- Remove all non-POSIX function calls, including the call to `chroot()` and, on
OpenBSD, `pledge()` and `unveil()`. This may seem like a downgrade in security, but
these are platform-specific system calls that should be patched in by package maintainers
if they are desired. They also caused problems when implementing other features, because
some library calls need to be able to access files on the filesystem.
- Fixed the build script to supply `LDFLAGS` after the object files when linking.
Apparently the order in which libraries are passed matters to some compilers.
- Added the response status of a request to the log output. This means that requests are
logged after they have completed, not before they are started.
- Memory allocations, reallocations, and frees are no longer loged when the log level
is set to debug in the configuration file. To enable the logging of memory operations,
pass the `-v` flag.
- Implemented a proper HTTP request router with POSIX regular expression support.
Previously, a series of nested `if`-statements were used to route requests, but this
approach quickly becamse very messy. While the HTTP request router incurs a small memory
and runtime speed penalty, the code is now much more maintainable and easier to follow.
- Fixed some memory bugs in `Db` that were related to caching data. Caching should
now work as expected.
- Fixed a major design flaw in `Db` that would cause deadlock when multiple threads
request access to the same object. Database locking is now in a per-thread basis,
instead of a per-reference basis.
- Telodendria now shuts down cleanly in response to `SIGTERM`.
- Did some general refactoring to make the source code more readable and easier
to maintain.
Fixed a number of memory-related issues, including switching out some unsafe
functions for safer versions, per the recommendations of the OpenBSD linker.
- Moved all code documentation into the C header files to make it more likely
that it will get updated. A simple header file parser and documentation generator
have been added to the code base. See the `hdoc` man pages for documentation.
- Updated the build script to provide static and shared libraries containing
the code for Telodendria to make it easier to statically and dynamically link to
other programs. The idea is that these libraries should be shipped with Telodendria,
or as a separate package, and can be used to provide a high-level programming
environment.
- Updated the `Json` API to calculate the length of a JSON object. This is
used to set the `Content-Length` header in HTTP requests and reponses.
- Added some string functions, including `StrEquals()`, which replaced almost all
uses of `strcmp()`, since `strcmp()` is used almost exclusively for equality
checking. `StrEquals()` provides a standard way to do so, because previously,
multiple different conventions could be found throughout the code base (for example:
`!strcmp(str1, str2)` vs `strcmp(str1, str2) == 0`).
... And many more!
## v0.2.1
**Monday, March 6, 2023**
This is a patch release that fixes a few typos and other minor issues.
## v0.2.0
**Monday, March 6, 2023**
This release is focused on providing a decent amount of the client authentication
API. You can now create accounts on a Telodendria homeserver, and log in to
get access tokens.
### New
- Added the basic form of the user registration API. If registration is enabled
in the configuration file, clients can now register for Matrix accounts.
- Added the basic form of the user login API. Clients can now log in to
their accounts and generate access tokens to be used to authenticate requests.
- Added the basic form of the user interactive authentication API, which can be used
by endpoints that the spec says requires it. Currently, it only implements the
dummy and password stages, but more stages, such as the registration token stage,
will be added in future releases.
- Added a simple landing page that allows those setting up Telodendria to
quickly verify that it is accessible where it needs to be.
- Added the static login page for clients that don't support regular login.
### Changes
- Improved HTTP request logging by removing unnecessary log entries and making
errors more specific.
- Leaked memory is now hexdump-ed out to the log if the log level is set to debug.
This greatly simplifies debugging, because developers can now see exactly what the
contents of the leaked memory are. Note that in some circumstances, this memory
may contain sensitive data, such as access tokens, usernames, or passwords. However,
Telodendria should not be leaking memory at all, so if you encounter any leaks,
please report them.
- Refactored a lot of the code and accompanying documentation to be more readable and
maintainable.
### Bug Fixes
- Fixed a memory leak that would occur when parsing an invalid JSON object.
- Fixed an edge case where HTTP response headers were being sent before they were
properly set, causing the server to report a status of 200 even when that wasn't the
desired status.
- Fixed a few memory leaks in the HTTP parameter decoder that would occur in some
edge cases.
- Fixed an "off-by-one" error in the HTTP server request parser that would prevent
`GET` parameters from being parsed.
- Fixed the database file name descriptor to prevent directory traversal attacks
by replacing special characters with safer ones.
- Fixed a memory leak that would occur when closing a database that contains
cached objects.
- Fixed a memory leak that would occur when deleting database objects.
- Fixed a few non-fatal memory warnings that would show up as a result of passing a
constant string into certain functions.
### Misc.
- Fixed a bug in `td` that caused `cvs` to be invoked in the wrong directory when
tagging a new release.
- Added support for environment variable substitution in all site files. This
makes it easier to release Telodendria versions.
- Fix whitespace issues in various shell scripts.
- Fixed the debug log output so that it only shows the file name, not the
entire file path in the repository.
- Updated the copyright year in the source code and compiled output.
- Switched the `-std=c89` flag to `-ansi`, as `-ansi` might be more supported.
- Fixed the `-v` flag. It now sets the log level to debug as soon as possible to
allow debugging configuration file parsing if necessary.
... And many more bug fixes and feature additions! Too much has changed to make a
comprehensive change log. A lot of things have been done under the hood to make
Telodendria easier to develop in the future. Please test the current functionality,
and report bugs.
The following platforms have been known to compile and run Telodendria:
- OpenBSD
- Linux (GNU and non-GNU)
- Windows (via Cygwin)
- FreeBSD
- NetBSD
- DragonFlyBSD
- Haiku OS
- Android (via Termux)
Telodendria is about being portable; if you compile it on an obscure operating system,
do let us know about it!
## v0.1.0
**Tuesday, December 13, 2022**
This is the first public release of Telodendria so there are no changes to report.
Future releases will have a complete change log entry here.
This is a symbolic release targeted at developers, so there's nothing useful to
ordinary users yet. Stay tuned for future releases though!

256
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,256 @@
# Contributing
Telodendria is a fully open source project. As such, it welcomes
contributions. There are many ways you can contribute, and any way you
can is greatly appreciated. This document details the ways you can
contribute, and how to go about contributing.
## Sponsoring Telodendria
If you would like to sponsor Telodendria, see the
[Sponsorship](../README.md#sponsorship) section on the main project
page. Donations of any size are greatly appreciated.
## Reporting Issues
An important way to get involved is to just report issues you find with
Telodendria during experimentation or normal use. To report an issue,
go to [Issues](/Telodendria/telodendria/issues) &rightarrow;
[New Issue](/Telodendria/telodendria/issues/new/choose) and follow the
instructions.
> **Note:** GitHub issues are not accepted. Issues may only be
> submitted to the official [Gitea](https://git.telodendria.io)
> instance.
### Feature Requests
Feature requests are allowed, but note that they are low-priority in
comparison to existing issues and features. That being said, don't
hesitate to submit feature requests. Just select the "Feature Request"
option when submitting an issue.
## Developing
If you want to write code for Telodendria, either to fix an issue or
add a new feature, you're in the right place. Please follow all the
guidelines in this document to ensure the contribution workflow goes
as smoothly as possible.
### Who can develop Telodendria?
Everyone is welcome to contribute code to Telodendria, provided that
they are willing to license their contributions under the same license
as the project itself.
The primary language used to write Telodendria code is ANSI C. Other
languages you'll find in the Telodendria repository include shell
scripts, `mdoc`, a little bit of HTML and CSS, and `Makefiles`.
Experience with any of these is preferred, but if you want to use
Telodendria to learn, that's okay too! Telodendria's code base should
hopefully be a good learning tool, and if you are serious about
submitting quality work, we'll guide you through the process and
offer suggestions.
### What do I need?
You'll need a couple of things to develop Telodendria:
- A Unix-like operating system that provides standard POSIX behavior,
or the Windows Subsystem for Linux (WSL), Cygwin, or Msys2 if you are
running Windows.
- A C compiler capable of compiling ANSI C89 code (pretty much all of
them do&mdash;pick your favorite, and if you find it doesn't work,
open an issue!).
- `make` for building the project.
- `git` for managing your changes.
- [Cytoplasm](/Telodendria/Cytoplasm), a simple C library written by
the Telodendria developers for the purpose of supporting Telodendria
in a modular way.
Optionally, you may also find these tools helpful:
- `indent` for formatting code.
- `valgrind` for debugging particularly nasty issues.
### Getting The Code
Telodendria is developed using Git. The easiest way to contribute
changes is to fork the main repository, and then creating a pull
request to ask us to pull your changes into our repo.
1. If you don't have an account on the
[Gitea instance](https://git.telodendria.io), create one and sign in.
1. Fork this repository.
1. In your development environment, clone your fork:
```shell
git clone https://git.telodendria.io/[YOUR_USERNAME]/Telodendria.git
cd Telodendria
```
Please base your changes on the `master` branch. If you need help
getting started with Git, that is beyond the scope of this
document, but you can find many good tutorials on the web.
### Building &amp; Running
Telodendria uses the `make` build system. Because it aims at maximum
portability, it targets POSIX `make` and should thus run on any POSIX
system that provides a `make`, be it GNU, BSD, or something different
entirely. To facilitate this, Telodendria provides a `configure` script
which generates the `Makefile`, because the `Makefile` would be far too
verbose and tedious to maintain in a POSIX-compatible way otherwise.
This is similar to how other C programs and libraries are built, although
note that Telodendria's `configure` script is not nearly as advanced as
an `autoconf` script, for example.
Please follow the build and installation directions for
[Cytoplasm](/Telodendria/Cytoplasm) first before attempting to build
Telodendria, because Telodendria depends on Cytoplasm and assumes it is
installed in the standard location for your system. For the best results,
it is recommended to take the time to enable TLS, unless you plan on
running Telodendria behind a reverse proxy.
To build Telodendria, simply run `configure`, then `make`:
```
$ ./configure
$ make
```
You may find some of the following options for `configure` helpful:
- `--prefix=<path>`: Set the install prefix to set by default in the `Makefile`. This defaults to `/usr/local`, which should be appropriate for most Unix-like systems.
- `--(enable|disable)-ld-extra`: Control whether or not to enable additional linking flags that create a more optimized binary. For large compilers such as GCC and Clang, these flags should be enabled. However, if you are using a small or more obscure compiler, then these flags may not be supported, so you can disable them with this option.
- `--(enable|disable)-debug`: Control whether or not to enable debug mode. This sets the optimization level to 0 and builds with debug symbols. Useful for running with a debugger.
- `--static` and `--no-static`: Controls whether static binaries are built by default. On BSD systems, `--static` is perfectly acceptable, but on GNU systems, `--no-static` is often desirable to silence warnings about static binaries emitted by the GNU linker.
Telodendria can be customized with the following options:
- `--bin-name=<name>`: The output name of the server binary. This defaults to `telodendria`. Common alternatives are `matrix-telodendria` or `telodendria-server`.
- `--version=<version>`: The version string to embed in the binary. This can be used to indicate build customizations or non-release versions of Telodendria.
The following recipes are available in the generated `Makefile`:
- `all`: This is the default target. It builds everything.
- `telodendria`: Build the `telodendria` binary. If you specified an alternative `--bin-name`, then this target will be named after that.
- `docs`: Generate the header documentation as `man` pages.
- `tools`: Build the supplemental tools which may be useful for development.
- `clean`: Remove the build and output directories. Telodendria builds are out-of-tree, which greatly simplifies this recipe compared to in-tree builds.
If you're developing Telodendria, these recipes may also be helpful:
- `format`: Format the source code using `indent`. This may require a BSD `indent` because last time I tried GNU `indent`, it didn't like the flags in `indent.pro`. Your mileage may vary.
- `license`: Update the license headers in all source code files with the contents of the `LICENSE.txt`.
To install Telodendria to your system, the following recipes are available:
- `install`: This installs Telodendria under the prefix set with `./configure --prefix=<dir>` or with `make PREFIX=<dir>`. By default, the `make` `PREFIX` is set to whatever was set with `configure --prefix`.
- `uninstall`: Uninstall Telodendria from the same prefix as specified above.
After a build, you can find the object files in `build/` and the output binary in `out/bin/`.
### Pull Requests
> **Note:** Telodendria does not accept GitHub pull requests at this
> time. Please submit your pull requests via Gitea.
Telodendria follows the standard pull request procedures. Once you have
made your changes, committed them, and pushed to your fork, you should
be able to open a pull request on the main repository. When you do, you
will be prompted to write a description. Be sure to include the
related issue that you are closing in your description.
### Code Style
In general, these are the conventions used by the code base. This
guide may be slightly outdated or subject to change, but it should be
a good start. The source code itself is always the absolute source of
truth, so as long as you make your code look like the code surrounding
it, you should be fine.
- All function, enumeration, structure, and header names are
`CamelCase`. This is preferred to `snake_case` because it is more
compact.
- All variable names are `lowerCamelCase`. This is preferred to
`snake_case` because it is more compact. One exception to this rule is
if a variable name, such as a member of a struct, directly represents
a JSON key in an object specified by the Matrix specification, which
may be in `snake_case`.
- Enumerations and structures are always `typedef`-ed to their same
name. The `typedef` should occur in the public API header, and the
actual declaration should live in the implementation file, unless
the enumeration or structure is intended to be made fully public.
- A feature of the code base lives in a single C source file that has a
matching header. The header file should only export public symbols;
everything else in the C source should be static.
- Except where absolutely necessary, global variables are forbidden
to prevent problems with threads and whatnot. Every variable a
function needs should be passed to it either through a structure, or
as a separate argument.
- Anywhere that C allows curly braces to be optional, there still must
be curly braces. This makes it easier to read the code by making it
less ambiguous, and it makes it easier to add on to the code later.
As far as actually formatting the code goes, such as where to put
brackets, and whether or not to use tabs or spaces, use `indent` to
take care of that. The repository contains a `.indent.pro` that should
automatically be loaded by `indent` to set the correct rules. If you
don't have a working `indent`, then just indicate in your pull
request that I should run my `indent` on the code.
### Documentation
This project places a strong emphasis on documentation. Well-documented
code is fundamental to a successful project, so when you are writing
code, please also make sure that it is documented appropriately.
- If you are adding a header, make sure you add the necessary comments
detailing the header and the functions in it.
- If you are adding a function, make sure you add the necessary
comments to the appropriate header.
If your pull request does not also include proper documentation, it
will likely be rejected.
### Be Recognized!
If your pull request gets approved, you should be recognized for your
contributions to the project!
To have your work recognized, add your information to the `CONTRIBUTORS.txt`
file in the root of the Telodendria repository if it isn't there already.
You should do this as a part of your pull request so that when it is merged,
your information will be automatically added to the repository.
The `CONTRIBUTORS.txt` file loosely follows the Linux kernel's
[CREDITS](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/CREDITS)
file format. It is designed to be human-readable, but also parsable by
scripts.
The following fields are available:
```
(N) Name
(E) Email
(M) Matrix ID
(W) Website
(D) Description of contribution
(L) Physical location
```
Here are the rules:
* All fields are optional. If you don't want to include a field, that's
okay, simply omit it.
* All fields identify you however you wish. The goal is to recognize you for
your contribution, but if you wish to remain anonymous, you don't have to
use your real information.
* All fields can be specified multiple times. For example, if you have
multiple email addresses, websites, or Matrix IDs and you want to include
all of them, you absolutely may. Likewise, if you have made multiple
contributions, you can add multiple description entries.
* You can make up your own fields if you want. Just add their description
above.
* Leave exactly one blank like between entries in this file.

32
docs/README.md Normal file
View File

@ -0,0 +1,32 @@
# Telodendria Documentation
Here you will find all of the documentation for Telodendria. If you
find that some documentation is missing or incorrect, please open an
issue, and, even better, a pull request to fix the issue.
You are also welcome to join the
[`#telodendria-general:bancino.net`](https://matrix.to/#/#telodendria-general:bancino.net)
matrix room, where you can discuss Telodnedria with others and ask
questions.
## User Documentation
- [System Requirements](user/requirements.md)
- [Install](user/install.md)
- [Usage & Running](user/usage.md)
- [Initial Set Up](user/setup.md)
- [Configuration Options](user/config.md)
- [Administrator API](user/admin/README.md)
## Developer Documentation
- [Repository Structure](dev/repo.md)
- [Contributing Guidelines](CONTRIBUTING.md)
- [Porting Guidelines](dev/ports.md)
## Miscellaneous
- [Matrix Specification](https://spec.matrix.org) ([Mirror](https://telodendria.io/spec.matrix.org))
- [Change Log](CHANGELOG.md)
- [Project Road Map](ROADMAP.md)

54
docs/ROADMAP.md Normal file
View File

@ -0,0 +1,54 @@
# Telodendria Matrix Specification Roadmap
This document provides a high-level overview of Telodendria's roadmap as it pertains to implementing the Matrix specification. Essentially, the Matrix specification is divided up into manageable portions amongst Telodendria releases, so that each release up until the first stable release implements a small portion of it.
**Note:** The first stable release of Telodendria will implement Matrix v1.7, no newer version. The Matrix specification changes too frequently, so I had to just pick a version in order to make this project manageable. Once v1.7 is complete, then we can move on to later specs.
This document will be updated to include more implementation details as they come up. It contains the big picture for far-out releases, and more relevant implementation details for near releases.
## Milestone v0.4.0
- [ ] Client-Server API
- [ ] **7:** Events
- [x] Compute size of JSON object in `CanonicalJson`
- [x] Rename `Sha2.h` to just `Sha.h`; add `Sha1()` function
- [x] Make `Sha256()` just return raw bytes; add function to convert to hex string.
- [ ] **8:** Rooms
- [ ] **9:** User Data
- [x] Profiles
- [ ] Directory
## Milestone v0.5.0
- [ ] Client-Server API
- [ ] Modules
- [ ] Content Repository
## Milestone v0.6.0
- [ ] Client-Server API
- [ ] Modules
- [ ] Instant Messaging
- [ ] Voice over IP
- [ ] Receipts
- [ ] Fully Read Markers
- [ ] Send-To-Device Messaging
- [ ] Security (Rate Limiting)
## Milestone v0.7.0
- [ ] Server-Server API
## Milestone v0.8.0
- [ ] Application Service API
- [ ] YAML parser?
## Milestone v0.9.0
- [ ] Identity Service API
## Milestone v1.7.0 (Stable Release!)
- [ ] Push Gateway API
- [ ] Room Versions

94
docs/dev/hosting.md Normal file
View File

@ -0,0 +1,94 @@
# Hosting Telodendria
These are just my own personal notes for hosting Telodendria's code infrastructure. This document is not intended to be used by normal Telodendria users or developers. It may be useful if you are *forking* Telodendria, but I sincerely hope you'll contribute to the upstream project instead. I'm writing this document solely for my own reference, but I am placing it into Telodendria's code repository in the name of transparency.
## Runners
The general sequence of steps required for setting up a CI runner is as follows:
1. Install the runner OS with all the defaults. I typically install my runners in virtual machines with 1 vcpu and 512mb RAM. Only Debian complained about this configuration, but since I didn't install a desktop environment, it worked out fine.
2. Install the packages required to build and execute the runner. These are:
- Git for checking out the source code.
- NodeJS for running `actions/checkout`, I think. Not really sure, all I know is that the runner will fail all jobs without NodeJS.
- Go for compiling the runner itself.
Run these commands to install the packages:
- **OpenBSD:** `pkg_add git go node`
- **FreeBSD:** `pkg install git go node`
- **NetBSD:** `pkgin install git go nodejs openssl mozilla-rootcerts-openssl`
(Note that the `go` executable is `go121` or whatever version was installed. and that NetBSD has no root certificates installed by default)
- **Debian:** `apt install git golang nodejs`
- **Alpine:** `apk add git go nodejs`
3. Install any development packages required to build Telodendria. For the BSDs, all development tools are built in so no additional packages are necessary. For the Linux distributions I've messed with, install these additional packages:
- **Debian:** `apt install make gcc libssl-dev`
- **Alpine:** `apk add make gcc musl-dev openssl-dev`
4. Clone `https://git.telodendria.io/Telodendria/act_runner.git`.
5. Run `go build` in the `act_runner` directory. On NetBSD, you may have to `umount /tmp` first because `/tmp` is by default very small. Otherwise, make `/tmp` larger during installation. 2GB should be plenty.
6. Run `./act_runner register` to register the runner. When prompted for the tags, follow following convention:
- **Linux Distros:** `linux`, `<distro>-v<version>`, `<arch>`
- **BSD Derivatives:** `bsd`, `<osname>-v<version>`, `<arch>`
- **Windows:** `windows`, `windows-v<version>`, `<arch>`
- **MacOS:** `macos`, `macos-v<version>`, `<arch>`
- **Others:** `other`, `<osname>-v<version>`, `<arch>`
Where `<arch>` is one of `x86` or `x64` for now. ARM runners will be a future project.
7. Run `./act_runner daemon`.
### Startup Scripts
We will obviously want `act_runner` to execute on bootup. Here are the start scripts I used:
#### Alpine
In `/etc/init.d/act_runner`:
```shell
#!/sbin/openrc-run
directory="/home/runner/act_runner"
command="/home/runner/act_runner/act_runner"
command_args="daemon"
command_user="runner:runner"
command_background="true"
pidfile="/run/act_runner.pid"
```
Don't forget to `chmod +x /etc/init.d/act_runner`.
Then just `rc-update add act_runner` and `rc-service act_runner start`.
#### Debian
In `/etc/systemd/system/act_runner.service`:
```
[Unit]
Description=Gitea Actions runner
[Service]
ExecStart=/home/runner/act_runner/act_runner daemon
ExecReload=/bin/kill -s HUP $MAINPID
WorkingDirectory=/home/runner/act_runner
TimeoutSec=0
RestartSec=10
Restart=always
User=runner
[Install]
WantedBy=multi-user.target
```
Then just `systemctl enable act_runner` and `systemctl start act_runner`.
#### Other
Eventually I got sick of writing init scripts for all the various operating systems.
Just put this in `runner`'s `crontab`:
```
@reboot cd /home/runner/act_runner && ./act_runner daemon
```
That seems to do the job good enough, and it's cross platform.

92
docs/dev/ports.md Normal file
View File

@ -0,0 +1,92 @@
## Ports
Telodendria is distributed primarily as source code, and the project
itself does not offer a convenient install process such as in the form
of a shell script. This is intentional; the Telodendria project is
primarily concerned with developing Telodendria itself, not packaging
it for the hundreds of different operating systems and linux
distributions that exist. It is my firm belief that distributing an
open source project is not the job of the open source developer; that
is the reason software distributions exist: to collect and
*distribute* software.
It would be impossible to single-handedly package Telodendria for
every platform, because each platform has very different expectations
and conventions for software. Even different Linux distributions have
different conventions for where manual pages, binaries, and
configuration files go.
That being said, this document aims to assist those who want to
package Telodendria for their operating system or software
distribution.
---
Before attempting to package Telodendria, make sure that you can build
it and that it builds cleanly on your target platform. See
[Install &rightarrow; From Source](../user/install.md#from-source)
for general build instructions.
To package Telodendria, you should collect the following files, and
figure out where they should be installed on your system:
- The `telodendria` server binary itself.
- An init script. People that wish to install Telodendria on their
system using your package are going to expect it to be integrated
enough that Telodendria can easily be started at boot and otherwise
managed by the system's daemon tools, be it `systemd` or another
init system. Consult your system's documentation for writing an init
script. **Note:** Telodendria *does not* fork itself to the background;
the init script should do that.
- You may also wish to ship the `docs/` directory
so that the user can read the documentation offline, and ensure that
they are reading the correct documentation for the installed version.
You may wish to optionally create a dedicated user under which
Telodendria should run. Telodendria can be directly started as that
user, or start as root and be configured to automatically drop to that
user. Additionally, it might be helpful to provide a default
configuration, which can be placed in the samples directory on your
platform, or in a default location that Telodendria will load from.
A good default directory that you may wish to provide for configuration,
data, and logs could perhaps be `/var/telodendria` or `/var/db/telodendria` on Unix-like systems.
Once you have collected the necessary files and directories that need
to be installed, make sure your package performs the following tasks
on install:
- If necessary and depending on the configuration used, create a new
system user for the Telodnedria daemon to run as.
- If conventional for your system, enable the Telodendria init script
so that Telodendria is started on system boot.
- Instruct the user to carefully read the [Setup](../user/setup.md)
(`docs/user/setup.md`) instructions and the
[Configuration](../user/config.md) (`docs/user/config.md`) instructions
before starting Telodendria.
The goal of a package should be to get everything as ready-to-run as
possible. The user should be able to start Telodendria right away and
begin configuring it.
Remember to publicly document the setup of Telodendria on your platform
if there are additional steps required that are not mentioned in the
official Telodendria documentation. This ensures that users can get
up and running quickly and easily. If you're packaging Telodendria
for a container system such as Docker, you can omit the things that
containers typically do not have, such as the init scripts and
documentation.
Also remember that your port should feel like it belongs on your target
system. Follow all of your system's conventions when placing files
on the filesystem, so your users know what to expect. The goal is not
necessarily to have a unified experience across all operating systems,
rather, you should cater to the opinions of your operating system.
Telodendria is architected in such a way that it does not impose the
developer's opinions of where things should go, and since the
configuration lives in the database, it is fairly self contained.
If there are any changes necessary to the upstream code or build
system that would make your job in porting Telodendria easier, do not
hesitate to get involved by opening an issue and/or submitting a pull
request.

109
docs/dev/rationale.md Normal file
View File

@ -0,0 +1,109 @@
# Rationale
This document seeks to answer the question of "why Telodendria?" from
a technical perspective by comparing it to existing Matrix homservers.
Telodendria is written entirely from scratch in ANSI C. It is designed
to be lightweight, simple, and functional. Telodendria differentiates
itself from other homeserver implementations because it:
- Is written C, a stable, low-level programming language with a long
history, low build and runtime overhead, and wide compatibility.
- Is written with minimalism as a primary design goal. Whenever possible
and practical, no third-party libraries are pulled into the code.
Everything Telodnedria needs is custom written. As a result, Telodendria
depends only on a standard C compiler and a POSIX C library to be
built, both of which should come with any good Unix-style operating
system already, which means you shouldn't have to install anything
additional to use Telodendria.
- Uses a flat-file directory structure to store data instead of a
real database. This has a number of advantages:
- It make setup and mainenance much easier.
- It allows Telodendria to run on systems with fewer resources.
- Is packaged as a single small, statically-linked and highly-optimized
binary that can be run just about anywhere. It is designed to be
extremely easy to set up and consume as few resources as possible.
- Is permissively licensed. Telodendria is licensed under a modified
MIT license, which imposes very few restrictions on what you can do
with it.
## What about [Conduit](https://conduit.rs)?
At this point, you may be wondering why one would prefer Telodendria
over Conduit, a Matrix homeserver that could also say pretty much
everything this document has said so far. After all, Conduit is older
and thus better established, and written in Rust, a Memory Safe&trade;
programming language.
In this section, we will discuss some additional advantages of
Telodendria that Conduit lacks.
### Small Dependency Chain
Conduit's dependency chain is quite large. What this means is that
Conduit depends on a lot of code that it does not control, making it
vulnerable to supply chain attacks. A problem with Rust Crates
is that they are developer-published, so they don't go through any sort
of auditing process like a Debian package would, for example.
If any one of the dependencies is
hijacked or otherwise compromised, then Conduit itself is compromised
and it is likely that this would go unnoticed for quite a while. While
one could argue that this is extremely unlikely to happen, sometimes you
just don't want to take that risk, especially not if you're deploying a
Matrix homeserver, likely for the purpose of secure, private chat.
Telodendria doesn't pull in any packages from developer repositories, so
the risk of supply chain attacks is much lower. It
only uses its own code and code provided by the operating system it is running
on, which has been vetted by a large number of developers and can be trusted
due to the sheer scope of an operating system. A supply chain attack against
Telodendria would be a supply chain attack against the entire operating system;
at that point, end users have much bigger problems.
Minimal dependencies doesn't only mitigate supply chain attacks. It also makes
maintenance much easier. Telodendria can spend more time writing code than
Conduit because Conduit developers have to ensure dependencies stay up to date and
when they inevitably break things, Conduit must pause development to fix those.
Telodendria doesn't suffer from this problem: because most of the code is developed
along side of Telodendria, it can remain as stable or become as volatile as the
developers choose. Additionally, because Telodendria is so low-level, the code on
which it depends is extremely unlikely to be changed in any significant way,
since so many other programs depend on that code.
### Standardized
Conduit is written in Rust, which has no formal standard. This makes it less than
ideal for long-lived software projects, because it changes frequently and often
breaks existing code. Telodendria is written in C, a stable, mature, and standardized
language that will always compile the same code the same way, making it more
portable and sustainable for the future because we don't ever have to worry about
upgrading our toolchain&mdash;using standard tools built into most operating systems
will suffice.
Because the language in which Telodendria is written never changes, Telodendria can
continually optimize and improve the code, instead of having to fix breaking changes.
This ensures that Telodendria's code will last. Rust code becomes obsolete with in a
few years at best&mdash;programs written in Rust last year probably won't compile or run
properly on the latest Rust toolchain. Telodendria, on the other hand, is written in C89,
which compiled and ran the same way in 1989 as it does today and will continue to for the
foreseeable future.
### Fast Compile Times
Rust is well-known for taking an extremely long time to compile moderately-sized
programs. Since a Matrix homeserver is such a large project, the compile times would
be prohibitively large for rapid development. By writing Telodendria in C, we can take
advantage of decades worth of compiler optimizations and speed improvements, resulting
in extremely fast builds.
### Portable
One does not typically think of C as more portable than something like Rust, but
Telodendria is written in such a way that it is. Rust relies on LLVM, which doesn't
support some strange or exotic architectures in the same way that a specialized C
compiler for those architectures will. This allows users to run Telodendria on the
hardware of their choice, even if that hardware is so strange that the modern world
has totally left it behind.
Telodendria doesn't just aim at being lightweight and portable, it aims to empower
people to use common hardware that they already have, even if it is typically thought
of as underpowered.

17
docs/dev/repo.md Normal file
View File

@ -0,0 +1,17 @@
# Repository Structure
This document describes the filesystem layout of the Telodendria source
code repository.
- `Telodendria/`
- `Cytoplasm/`: The source code for Cytoplasm, Telodendria's
general-purpose support library that provides core functionality.
- `contrib/`: Supplemental files, such as example configurations.
- `docs/`: All user and developer documentation as Markdown.
- `site/`: The official website source code as HTML.
- `src/': The C source code and headers for Telodendria.
- `Routes/`: Where the Matrix API endpoints are implemented.
- `Static/`: Endpoints that just generate static HTML pages.
- `include/`: Header files.
- `tools/`: Development environment and tools.

37
docs/user/admin/README.md Normal file
View File

@ -0,0 +1,37 @@
# Administrator API
Telodendria provides an administrator API as an extension to the
Matrix specification that allows for administrator control over the
homeserver. This includes profiling and examining the state of
running instances, as well as managing users and media.
Like Synapse, Telodendria supports designating specific local users as
administrators. However, unlike Synapse, Telodendria uses a more
fine-grained privilege model that allows a server administrator to
delegate specific administration tasks to other users while not
compromising and granting them full administrative access to the server.
To authenticate with the administrator API, simply use your login
access token just like you would authenticate any other Matrix client
request.
- [Privileges](privileges.md)
- [Configuration](config.md)
- [Server Statistics](stats.md)
- [Process Control](proc.md)
- [Registration Tokens](tokens.md)
## API Conventions
Unless otherwise indicated, HTTP response codes that are not `200 Ok`
will be accompanied by a standard Matrix API error. Consult the Matrix
specification for the format of these errors. The following error
conditions are assumed to be possible for all API endpoints listed
in the Administrator API documentation:
| Response Code | Description |
|---------------|-------------|
| 400 | The user is not authenticated, did not provide a valid JSON object, or provided a JSON object with invalid or missing parameters.|
| 403 | The user does not have the privileges necessary to carry out the requested action.|
| 500 | A fatal server error occurred. Check the logs for more information.|

62
docs/user/admin/config.md Normal file
View File

@ -0,0 +1,62 @@
# Administrator API: Configuration
As mentioned in [Setup](../setup.md), Telodendria's configuration is
intended to be managed via the configuration API. Consult the
[Configuration](../config.md) document for a complete list of supported
configuration options. This document simply describes the API used to
update the configuration described in that document.
## API Endpoints
### **GET** `/_telodendria/admin/v1/config`
Retrieve the current configuration.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The current configuration was successfully retrieved.|
### **POST** `/_telodendria/admin/v1/config`
Installs a new configuration. This endpoint validates the request body,
ensuring it is a proper configuration, then it replaces the existing
configuration with the new one.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The new configuration was successfully installed.|
#### 200 Response Format
| Field | Type | Description |
|-------|------|-------------|
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
### **PUT** `/_telodendria/admin/v1/config`
Update the currently installed configuration instead of completely replacing it. This endpoint
validates the request body, merges it on top of the current configuration, validates the resulting
configuration, then updates it in the database. This is useful when only one or two properties
in the configuration needs to be changed.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The new configuration was successfully installed.|
#### 200 Response Format
| Field | Type | Description |
|-------|------|-------------|
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.

View File

@ -0,0 +1,141 @@
# Administrator API: Privileges
This document describes the privilege model and the API endpoints that
allow administrators to modify privileges for users.
## List Of Privileges
A local user can have any of the following privileges. Unless otherwise
indicated, these privileges only grant access to certain parts of the
administrator API; the regular Matrix API is unaffected.
- **DEACTIVATE:** Allows a user to deactivate any other local users.
- **ISSUE_TOKENS:** Allows a user to create, modify, and delete
registration tokens.
- **CONFIG:** Allows a user to modify the Telodendria server daemon's
configuration.
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own
privileges or the privileges of other local users.
- **ALIAS:** Allows a user to modify and see room aliases created by
other users. By default, users can only manage their own room aliases,
but an administrator may wish to take over an alias or remove an
offensive alias.
- **PROC_CONTROL:** Allows a user to get statistics on the running
process, as well as shutdown and resetart the Telodendria daemon
itself. Typically this will pair well with **CONFIG**, because there
are certain configuration options that require the process to be
restarted to take full effect.
There is also a special "pseudo-privilege":
- **ALL:** Grants a user all of the aforementioned privileges, as well
as privileges that do not yet exist. That is, if an update to
Telodendria adds more privileges, users with this privilege will
automatically gain those new privileges in addition to having all the
existing privileges. This privilege should only be used with
fully-trusted users. It is typical for a server administrator to not
fully trust anyone else, and be the only one that holds an account with
this privilege level.
## API Endpoints
The following API endpoints are implemented for managing privileges.
### **GET** `/_telodendria/admin/v1/privileges/[localpart]`
Retrieve the permissions for a user. If the localpart is omitted, then
retrieve the privileges for the user that owns the access token being
used. Note that the owner of the access token must have the
**GRANT_PRIVILEGES** privilege to use this endpoint.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The privileges were successfully retrieved.|
#### 200 Response Format
| Field | Type | Description |
|-------|------|-------------|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
### **POST** `/_telodendria/admin/v1/privileges/[localpart]`
Update the privileges of a local user by replacing the privileges array
with the one specified in the request. Like the **GET** version of this
endpoint, the localpart can be omitted to operate on the user that
owns the access token.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The privileges were successfully replaced.|
#### Request Format
| Field | Type | Description |
|-------|------|-------------|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
#### 200 Response Format
| Field | Type | Description |
|-------|------|-------------|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
### **PUT** `/_telodendria/admin/v1/privileges/[localpart]`
Update the privileges of a local user by adding the privileges
specified in the request to the users existing privileges.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The privileges were successfully added.|
#### Request Format
| Field | Type | Description |
|-------|------|-------------|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
#### 200 Response Format
| Field | Type | Description |
|-------|------|-------------|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
### **DELETE** `/_telodendria/admin/v1/privileges/[localpart]`
Update the privileges of a local user by removing the privileges
specified in the request from the user's existing privileges.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The privileges were successfully removed.|
#### Request Format
| Field | Type | Description |
|-------|------|-------------|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
#### 200 Response Format
| Field | Type | Description |
|-------|------|-------------|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|

49
docs/user/admin/proc.md Normal file
View File

@ -0,0 +1,49 @@
# Administrator API: Process Control
This document describes the administrator APIs that allow a server
administrator to manage the Telodendria process itself.
## API Endpoints
### **POST** `/_telodendria/admin/v1/restart`
Restart the Telodendria daemon cleanly. This endpoint will respond
immediately after signaling to the daemon that it should be restarted
as soon as possible. Note that the restart wmay not happen
instantaneously, as Telodendria will finish processing all current
requests before restarting. Also note that this is not a true restart;
the process does not exit and restart, rather, Telodendria simply tears
down all its state and then jumps back to the beginning of its code and
starts over.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The restart request was successfully sent.|
On success, this endpoint simply returns an empty JSON object.
### **POST** `/_telodendria/admin/v1/shutdown`
Shut down the Telodendria process cleanly. This endpoint will respond
immediately after signalling to the daemon that it should be shut
down as soon as possible. Note that the shutdown may not happen
instantaneously, as Telodendria will finish processing all current
requests before shutting down. Also note that once shut down, Telodendria
may be automatically restarted by the system's service manager.
Otherwise, it will have to be manually restarted. This is a true
shutdown; the Telodendria process exits as soon as possible.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The shutdown request was successfully sent.|
On success, this endpoint simply returns an empty JSON object.

26
docs/user/admin/stats.md Normal file
View File

@ -0,0 +1,26 @@
# Administrator API: Server Statistics
The administrator API allows users with the proper privileges to get
information about how the server process is performing.
## API Endpoints
### **GET** `/_telodendria/admin/v1/stats`
Retrieve basic statistics about the currently running Telodendria
process.
| Requires Token | Rate Limited |
|----------------|--------------|
| Yes | Yes |
| Response Code | Description |
|---------------|-------------|
| 200 | The server statistics were successfully retrieved.|
#### 200 Response Format
| Field | Type | Description |
|-------|------|-------------|
| `memory_allocated` | `Integer` | The total amount of memory allocated, measured in bytes.|
| `version` | `String` | The current version of Telodendria.|

106
docs/user/admin/tokens.md Normal file
View File

@ -0,0 +1,106 @@
# Administrator API: Registration Tokens
Telodendria implements registration tokens as specified by the Matrix
specification. These tokens can be used for registration using the
`m.login.registration_token` login type. This API provides a Telodendria
administrator with a mechanism for generating and managing these tokens,
which allows controlled registration on the homeserver.
It is generally safer than completely open registration to use
registration tokens that either expire after a short period of time, or
have a limited number of uses.
## Registration Token
A registration token is represented by the following `RegToken` JSON
object:
| Field | Type | Description |
|-------|------|-------------|
| `name` | `String` | The token identifier; what is used when registering. |
| `created_by` | `String` | The localpart of the user that created this token. |
| `created_on` | `Integer` | A timestamp of when the token was created. |
| `expires_on` | `Integer` | An expiration stamp, or 0 if the token never expires. |
| `used` | `Integer` | The number of times the token has been used. |
| `uses` | `Integer` | The total number of allowed uses, or -1 for unlimited. |
| `grants` | `[String]` | An array of privileges to grant users that register with this token as described in [Privileges](privileges.md). |
All endpoints in this API will operate on some variation of this
structure. The remaining number of uses can be computed by performing
the subtraction: `uses - used`. `used` should never be greater than
`uses` or less than `0`.
Example:
```json
{
"name": "q34jgapo8uq34hg",
"created_by": "admin",
"created_on": 1699467640000,
"expires_on": 0,
"used": 3,
"uses": 5
}
```
## API Endpoints
### **GET** `/_telodendria/admin/v1/tokens`
Get a list of all registration tokens and information about them.
#### 200 Response Format
| Field | Type | Description |
|-------|------|-------------|
| `tokens` | `[RegToken]` | An array of registration tokens. |
### **GET** `/_telodendria/admin/v1/tokens/[name]`
Get information about the specified registration token.
#### Request Parameters
| Field | Type | Description |
|-------|------|-------------|
| `name` | `String` | The name of the token, as it would be used to register a user. |
#### 200 Response Format
This endpoint returns a `RegToken` object that represents the server's
record of the registration token.
### **POST** `/_telodendria/admin/v1/tokens`
Create a new registration token.
#### Request Format
This endpoint accepts a `RegToken` object, as described above. If no
`name` is provided, one will be randomly generated. Note that the fields
`created_by`, `created_on`, and `used` are ignored and set by the server
when this request is made. All other fields may be set by the request
body.
#### 200 Response Format
If the creation of the registration token was successful, a `RegToken`
that represents the server's record of it is returned.
### **DELETE** `/_telodendria/admin/v1/tokens/[name]`
Delete the specified registration token. It will no longer be usable for
the registration of users. Any users that have completed the
`m.login.registration_token` step but have not yet created their account
should still be able to do so until their user-interactive auth session
expires.
#### Request Parameters
| Field | Type | Description |
|-------|------|-------------|
| `name` | `String` | The name of the token, as it would be used to register a user. |
#### 200 Response Format
On success, this endpoint returns an empty JSON object.

248
docs/user/config.md Normal file
View File

@ -0,0 +1,248 @@
# Configuration
Telodendria is designed to be configurable. It is configured using
JSON, which is intended to be submitted to the [Administrator
API](admin/README.md). This document details Telodendria's configuration
JSON format, which is used in both the administrator API and on-disk
in the database. The configuration file on the disk in the databsae
is `config.json`, though that file should not be edited by hand.
Use the API described in
[Administrator API &rightarrow; Configuration](admin/config.md).
## JSON Format
Telodendria's configuration is just a JSON object in the standard
key-value form:
```json
{
"serverName": "telodendria.io",
"listen": [
{
"port": 8008
}
]
/* ... */
}
```
Some keys, called *directives* in this document, have values that are
objects themselves.
## Directives
Here are the top-level directives:
- **listen:** `Array`
An array of listener description objects. Telodendria supports
listening on multiple ports, and each port is configured
independently of the others. A listener object looks like this:
- **port:** `integer`
The port to listen on. Telodendria will bind to all interfaces,
so it is recommended to configure your firewall to only allow
access on the desired interfaces. Note that Telodendria offers all
APIs over each port, including the administrator APIs; there is no
way to control which APIs are made available over which ports. If
this is a concern, a reverse-proxy such as `relayd` can be placed
in front of Telodendria to block access to undesired APIs.
- **tls:** `Object`
Telodendria can be compiled with TLS support. If it is, then a
particular listener can be set to use TLS for connections. If
**tls** is not `null` or `false`, then it can be an object with
the following directives:
- **cert:** `String`
The full path&mdash;or path relative to the data
directory&mdash;of the certificate file to load. The certificate
file should be in the format expected by the platform's TLS
library.
- **key:** `String`
Same as **cert**, but this should be the private key that matches
the certificate being used.
- **threads:** `Integer`
How many worker threads to spin up to handle requests for this
listener. This should generally be less than the total CPU core
count, to prevent overloading the system. The most efficient number
of threads ultimately depends on the configuration of the machine
running Telodendria, so you may just have to play around with
different values here to see which gives the best performance.
Note that this can be set as low as 0; in that case, the listener
will never respond to requests. Each listener needs to have at
least one thread to be useful. Also note that Telodendria may spin
up additional threads for background work, so the actual total
thread count at any given time may exceed the sum of threads
specified in the configuration.
This directive is optional. The default value is `4` in the upstream
code, but your software distribution may have patched this to be
different.
- **maxConnections:** `Integer`
The maximum number of simultanious connections to allow to the
daemon. This option prevents the daemon from allocating large
amounts of memory in the event that it undergoes a denial of
service attack. It is optional, defaults to `32`, and typically
does not need to be adjusted.
- **serverName:** `String`
Configure the domain name of your homeserver. Note that Matrix
servers cannot be migrated to other domains, so once this is set,
it should never change unless you want unexpected things to happen
or you want to start over. **serverName** should be a DNS name that
can be publicly resolved. This directive is required.
- **pid:** `String`
Configure the file Telodendria writes its PID to.
- **baseUrl:** `String`
Set the server's base URL. **baseUrl** should be a valid URL,
complete with the protocol. It does not need to be the same as the
server name; in fact, it is common for a subdomain of the server name
to be the base URL for the Matrix homeserver.
This URL is the URL at which Matrix clients will connect to the
server, and is thus served as a part of the `.well-known`
manifest.
This directive is optional. If unspecified, it is automatically
deduced from the server name.
- **identityServer:** `String`
The identity server that clients should use to perform identity
lookups. **identityServer** folows the same rules as **baseUrl**.
It also is optional, and is set to be the same as the **baseUrl**
if left unspecified.
- **runAs:** `Object`
The effective Unix user and group to drop to after binding to the
socket and completing any setup that may potentially require
elevated privileges. This directive only takes effect if
Telodendria is started as the root user, and is used as a security
mechanism. If this option is set and Telodendria is started as a
non-privileged user, then a warning is printed to the log if that
user and group do not match what's specified here. This directive
is optional, but should be used as a sanity check even if not
running as `root`, just to make sure you have your permissions
working properly.
This directive takes an object with the following directives:
- **uid:** `String`
The Unix username to switch to. If **runAs** is specified, this
directive is required.
- **gid:** `String`
The Unix group to switch to. This directive is optional; if left
unspecified, then the value of **uid** is copied.
- **federation:** `Boolean`
Whether or not to enable federation with other Matrix homeservers.
Matrix by its very nature is a federated protocol, but if you just
want to rn your own internal chat server with no contact with the
outside, then you can use this option to disable federation. It is
highly recommended to set this to `true`, however, if you wish to
be able to communicate with users on other Matrix servers. This
directive is required.
- **registration:** `Boolean`
Whether or not to enable new user registration or not. For security
and anti-spam reasons, you can set this to `false`. If you do, you
can still allow only certain users to be registered using
registration tokens, which can be managed via the administrator API.
This directive is required.
In an ideal world, everyone would run their own Matrix homeserver,
so no public registration would ever be required. Unfortunately,
not everyone has the means to run their own homeserver, especially
because of the fact that IPv4 addresses are becoming increasingly
hard to come by. If you would like to provide a service to those
that are unable to run their homeserver, then set this to `true`,
thereby allowing anyone to create an account.
Telodendria *should* be capable of handling a large amount of users
without difficulty, but it is targetted at smaller deployments.
- **log:** `Object`
The logging configuration. Telodendria uses its own logging
facility, which can output logs to standard output, a file, or the
syslog. This directive is required, and it takes an object with the
following directives:
- **output:** `Enum`
The log output destination. This can either be `stdout`, `file`,
or `syslog`. If set to `file`, Telodendria will log to
`telodendria.log` inside the data directory.
- **level:** `Enum`
The level of messages to log. Each level shows all the levels above
it. The levels are as follows:
- `error`
- `warning`
- `notice`
- `message`
- `debug`
For example, setting the level to `error` will show only errors,
while setting the level to `warning` will show both warnings
*and* errors. The `debug` level shows all messages.
- **timestampFormat:** `Enum`
If you want to customize the timestamp format shown in the log,
or disable it altogether, you can do so via this option. Acceptable
values are `none`, `default`, or a formatter string as described
by your system's `strftime()` documentation. This option only
applies if **log** is `stdout` or `file`.
- **color:** `Boolean`
Whether or not to enable colored output on TTYs. Note that ANSI
color sequences will not be written to a log file, only a real
terminal, so this option only applies if the log is being written
to a standard output which is connected to a terminal.
- **maxCache:** `Integer`
The maximum size of the cache. Telodendria relies heavily on caching
for performance reasons. The cache grows as data is loaded from the
data directory. All cache is stored in memory. This option limits the
size of the memory cache. If you have a system with a lot of memory
to spare, you'll get better performance if this option is set higher.
Otherwise, this value should be lowered on systems that have a
minimal amount of memory available.
## Examples
A number of example configuration files are shipped with Telodendria's
source code. They can be found in the `contrib/` directory if you are
viewing the source code directly. Otherwise, if you installed
Telodendria from a package, it is possible that the example
configurations were placed in the default locations for such files on
your operating system.

56
docs/user/install.md Normal file
View File

@ -0,0 +1,56 @@
# Installation
There are multiple methods of installing Telodendria. Choose the one
best suited to your use case.
## Package Manager Or System Ports
This is the recommended way to install Telodendria. If your operating
system has an official package or port of Telodendria, you should
prefer to use that instead of the other methods documented here,
because your operating system or software distribution will have
already figured out how to best integrate Telodendria with your system.
Consult your operating system or software distribution's system
manual for instructions on how to install packages. Also consult the
official repository of your distribution to see if a package is
available. If a package exists but it is too out of date for your
tastes, please contact the package's maintainer to notify them, or
offer to update the package yourself using the
[porting instructions](../dev/ports.md).
If you are maintaining a port or package for an operating system or
software distribution, open a pull request to include your
platform-specific instructions as a subheader of this section.
Eventually, this section should contain basic instructions for the
operating systems that have packages or ports.
See [Ports](../dev/ports.md) for the project's distribution
philosophy.
## Container
At this time, Telodendria does not have any officially recommended
procedure for running in a container such as Docker or Vagrant. You
may find helpful files in the [`contrib/`](../../contrib) directory,
however.
If you are publishing container images, please open a pull request to
add your source files to `contrib/`, as well as to add documentation
under this section explaining how to get set started.
## Release Binary
At this time, Telodendria does not publish any official binaries that
can be downloaded. The tentative plan is to eventually provide binaries
with each release for a number of supported platforms. When that
happens, instructions will be provided here for dealing with the
binaries.
## From Source
If you would like to build Telodendria from source, you can download
the latest release code from the
[Releases](/Telodendria/telodendria/releases) page. After extracting
the tarball, read
[Contributing &rightarrow; Developing &rightarrow; Building &amp; Running](../CONTRIBUTING.md#building-amp-running)
for details on how to build Telodendria.

60
docs/user/setup.md Normal file
View File

@ -0,0 +1,60 @@
# Initial Set Up
While Telodendria strives to be extremely simple to deploy and run,
in most circumstances a few basic setup steps will be necessary.
Telodendria does not have a traditional configuration file like most
daemons. Instead, its configuration lives in its database; as such,
all configuration happens through the administrator API. This design
decision makes Telodendria extremely flexible, because it is possible
to re-configure Telodendria without having to manually edit files on
the filesystem, thus allowing administrators to secure their server
better.
Please follow the instructions followed here carefully in the order
they are presented for the best results.
This document assumes that you have installed Telodendria using any
of the instructions found in [Install](install.md). After installation,
follow these steps:
1. Start Telodendria. If you installed it via a package or container,
consult your operating system or container system's documentation. If
you are running Telodendria from a release binary or have built it from
source, execute the binary directly. If needed, consult the
[Usage](usage.md) page for details on how to run Telodendria.
1. Assuming that Telodendria started properly, it will spin up and
initialize its database directly with a simple&mdash;and, importantly,
safe&mdash;default configuration, as well as a randomly generated,
single-use registration token that grants a user all privileges
documented in the [Administrator API](admin/README.md) documentation.
Consult the log file for this administrator registration token. By
default, the log file is located in the data directory, and is named
`telodendria.log`.
1. Use the registration token to register for an account on the
server. This account will be the administrator account. You can do this
using the client of your choice, or using tools such as `curl` or
`http`, following the Matrix specification for registering accounts.
The administrator account behaves just like a normal local account
that an ordinary user would have registered on the server, except that
it also has all privileges granted to it, so it can make full use of
the Administrator API.
1. Using the access token granted for the administrator account via
the login process, configure Telodendria as descibed in
[Configuration](config.md). See the [Administrator API](admin/README.md)
documentation for the configuration endpoint details.
This is the recommended way to set up Telodendria. However, if you
wish to bypass the account creation step and want to configure
Telodendria by directly writing a configuration file instead of using
the administrator API, you can manually create the configuration file
in the database before starting Telodendria. Simply create `config.json`
following the description in [Configuration](config.md), then start
Telodendria.
While this alternative method may seem simpler and more convenient
to some administrators, it is only so in the short-term. Note that this
method is not supported, because it gives no access to the
administrator API whatsoever, unless you manually modify the database
further to give a user admin privileges, which is error-prone and
bypasses some of Telodendria's safety mechanisms.

64
docs/user/usage.md Normal file
View File

@ -0,0 +1,64 @@
# Usage
This document provides general documentation on how to use the
`telodendria` server binary, as well as details on how it behaves.
The details here will be useful for setting up init systems, running
Telodendria in a container, or manually executing the binary for
testing or debugging purposes.
## Command Line Options
Typically, Telodendria is controlled via the
[Administrator API](admin/README.md), but the Telodendria binary does include
a few command line options, which can be used in init scripts or for
debugging purposes.
The command line arguments are as follows:
- **`-d <dir>`** Specify the data directory to use. All persistent
storage that Telodendria requires is saved to and loaded from here.
- **`-V`** Only print the version information header and then quit
with a success exit code.
- **`-v`** Verbose mode. This overrides the configuration and sets the
log level to `debug`. It also enables additional logging of memory
operations, which can be useful for debugging.
Before proposing additional command line arguments, consider whether or
not the functionality requested can be provided via a (potentially new
and as of yet uncreated) administrator API endpoint.
## Environment
Telodendria does not read any environment variables. All configuration
should be done via the [Configuration API](config.md).
## Signals
Telodendria recognizes and responds to a number of signals:
- **`PIPE`:** This signal is ignored, because all I/O errors should
already be handled properly.
- **`USR1`:** Perform a soft restart by shutting down the HTTP servers
and resetting the program state. Note that the daemon process does
not exit.
- **`TERM`:** Perform a clean shutdown after all existing connections
are closed.
- **`INT`:** Same as `TERM`.
Any other signals are not explicitly handled, so they have the
default behavior as defind by the operating system.
## Exit Status
Telodendria exits with a non-0 exit code if the configuration file is
invalid, or one or more of required paths or files is inaccessible.
Telodendria will print an error to the log and then terminate
abnormally.
Telodendria exits with a code of 0 if the configuration file is valid,
all paths and files required are accessible, and the HTTP listener
starts as intended. If Telodendria is sent a signal that it catches
after it begins servicing requests, it will still exit normally after
it safely shuts down, because the bootstrap process completed
successfully, and by all accounts, it ran normally and exitted
normally.

View File

@ -1 +0,0 @@
mandoc.db

View File

@ -0,0 +1,18 @@
.Dd $Mdocdate: May 6 2023 $
.Dt HTTP-DEBUG-SERVER 1
.Os Telodendria Project
.Sh NAME
.Nm http-debug-server
.Nd A simple HTTP server that logs requests to the standard output.
.Sh DESCRIPTION
.Pp
.Nm
spins up an HTTP server, listening on port 8008, in the exact same
manner as Telodendria itself. Any request it receives is written to
the standard output, and an empty JSON object is returned to the
client.
.Pp
This command exists just to test the HTTP server API during
development. It probably serves no other practical purpose.
.Sh SEE ALSO
.Xr HttpServer 3

111
man/man1/json.1 Normal file
View File

@ -0,0 +1,111 @@
.Dd $Mdocdate: March 12 2023 $
.Dt JSON 1
.Os Telodendria Project
.Sh NAME
.Nm json
.Nd A simple command line utility for parsing and generating JSON.
.Sh SYNOPSIS
.Nm
.Op Fl s Ar query
.Op Fl e Ar str
.Sh DESCRIPTION
.Nm
is a simple command line utility for dealing with JSON. It is
somewhat inspired by
.Xr jq 1 ,
but is not compatible in any way with it.
.Nm
is designed to be much simpler than
.Xr jq 1 ,
and is built on Telodendria's own
.Xr Json 3
API. It primarily exists to ease development of Telodendria, and
to make development possible without having to install any external
tools.
.Pp
The options are as follows. Unless stated otherwise, these options
are mutually exclusive, and the last one specified takes precedence.
All positional parameters are ignored.
.Bl -tag -width Ds
.It Fl s Ar query
Use
.Va query
to query a field from a JSON object given on the standard input.
The query syntax very vaguely resembles C code, but it is much
more primitive. Multiple queries are separated by an arrow
(``->''). This makes it trivial to drill down into nested
objects and arrays.
.Pp
To select a value from an object, just specify the key. To select
an element from an array specify the key whose value is the array,
and then use the C square bracket syntax to select an element.
.Pp
A number of ``functions'' exist to make
.Nm
more versatile. Functions are called by prefacing the key with
a ``@'' symbol. Functions can appear anywhere in the query, provided
they make sense within the context of the JSON object being processed.
The available functions are as follows:
.Bl -tag -width Ds
.It keys
When applied to an object, outputs an array of keys.
.It length
When applied to an array, outputs the number of elements in the
array. When applied to a string, returns the number of bytes
needed to store the decoded version of the string.
.It decode
When applied to a string, outputs the decoded version of the
string.
.El
.Pp
When a key is prefaced with the ``^'' symbol, then instead of getting
the value at that key, it is removed from the object, and the new
object is passed along.
.It Fl e Ar str
Encode
.Va str
as a JSON string and print it to standard output. This is useful for
generating JSON with shell scripts.
.El
.Pp
If no options are specified, then the default behavior of
.Nm
is to read a JSON object given on the standard input and pretty-print
it to the standard output, or print an error to standard error if
the given input is invalid.
.Sh EXAMPLES
.Pp
Get the error string of an error returned by a Matrix API endpoint:
.Bd -literal -offset indent
json -s 'error->@decode'
.Ed
.Pp
Get the number of stages in the first flow listed in a list
of user-interactive authentication flows:
.Bd -literal -offset indent
json -s 'flows[0]->stages->@length'
.Ed
.Pp
Get the first stage of the first flow listed in a list
of user-interactive authentication flows:
.Bd -literal -offset indent
json -s 'flows[0]->stages[0]->@decode'
.Ed
.Pp
List the keys in a JSON object:
.Bd -literal -offset indent
json -s '@keys'
.Ed
.Pp
Get the number of keys in a JSON object:
.Bd -literal -offset indent
json -s '@keys->@length'
.Ed
.Sh EXIT STATUS
.Nm
exits with
.Va EXIT_SUCCESS
if all command line options were valid and the given JSON object
parses successfully. It exits with
.Va EXIT_FAILURE
in all other scenarios.

34
man/man1/tt.1 Normal file
View File

@ -0,0 +1,34 @@
.Dd $Mdocdate: April 29 2023 $
.Dt TT 1
.Os Telodendria Project
.Sh NAME
.Nm tt
.Nd Make authenticated Matrix requests.
.Sh SYNPOSIS
.Nm
.Op request-path
.Sh DESCRIPTION
.Nm
is an extremely simple wrapper over
.Xr http 1
and
.Xr json 1
that automatically registers a user and continues to log in as that
user. It obtains an access token that it uses to authenticate the
given request, and then logs out when the request has completed. It
also doesn't require the whole URL to be typed; only the path name
is needed.
.Sh ENVIRONMENT
.Bl -tag
.It Ev METH
The request method to use for the user-specified request.
.It Ev DATA
The data to pass to
.Xr http 1 .
This can either be a string, or a file. Consult the
.Xr http 1
page for details.
.El
.Sh SEE ALSO
.Xr http 1 ,
.Xr json 1

View File

@ -1,132 +0,0 @@
.Dd $Mdocdate: November 24 2022 $
.Dt ARRAY 3
.Os Telodendria Project
.Sh NAME
.Nm Array
.Nd A simple dynamic array data structure.
.Sh SYNOPSIS
.In Array.h
.Ft Array *
.Fn ArrayCreate "void"
.Ft void
.Fn ArrayFree "Array *"
.Ft int
.Fn ArrayTrim "Array *"
.Ft size_t
.Fn ArraySize "Array *"
.Ft void *
.Fn ArrayGet "Array *" "size_t"
.Ft int
.Fn ArrayInsert "Array *" "void *" "size_t"
.Ft int
.Fn ArrayAdd "Array *" "void *"
.Ft void *
.Fn ArrayDelete "Array *" "size_t"
.Ft void
.Fn ArraySort "Array *" "int (*) (void *, void *)"
.Ft Array *
.Fn ArrayFromVarArgs "size_t" "va_list"
.Ft Array *
.Fn ArrayDuplicate "Array *"
.Sh DESCRIPTION
These functions implement a simple array data structure that
is automatically resized as necessary when new values are added.
This implementation does not actually store the values of the
items in it; it only stores pointers to the data. As such, you will
still have to manually maintain all your data. The advantage of this
is that these functions don't have to copy data, and thus don't care
how big the data is. Furthermore, arbitrary data can be stored in the
array.
.Pp
This array implementation is optimized for storage space and appending.
Deletions are expensive in that all the items of the list above a deletion
are moved down to fill the hole where the deletion occurred. Insertions are
also expensive in that all the elements above the given index must be shifted
up to make room for the new element.
.Pp
Due to these design choices, this array implementation is best suited to
linear writing, and then linear or random reading.
.Pp
These functions operate on an array structure which is opaque to the
caller.
.Pp
.Fn ArrayCreate
and
.Fn ArrayFree
allocate and deallocate an array, respectively.
Note that
.Fn ArrayFree
does not free any of the values stored in the array; it is the caller's
job to manage the memory for each item. Typically, the caller would
iterate over all the items in the array and free them before freeing
the array.
.Fn ArrayTrim
reduces the amount of unused memory by calling
.Xr realloc 3
on the internal structure to perfectly fit the elements in the array. It
is intended to be used by functions that return relatively read-only arrays
that will be long-lived.
.Pp
.Fn ArrayInsert
and
.Fn ArrayDelete
are the main functions used to modify the array.
.Fn ArrayAdd
is a convenience method that simply appends a value to the end of the
array. It uses
.Fn ArrayInsert .
The array can also be sorted by using
.Fn ArraySort ,
which takes a pointer to a function that compares elements. The function
should take two
.Dv void
pointers as parameters, and return an integer. The return value indicates
to the algorithm how the elements relate to each other. A return value of
0 indicates the elements are identical. A return value greater than 0
indicates that the first item is "bigger" than the second item and should
thus appear after it in the array, and a value less than zero indicates
the opposite: the second element should appear after the first in the array.
.Pp
.Fn ArrayGet
is used to get the element at the specified index.
.Pp
.Fn ArrayFromVarArgs
is used to convert a variadic arguments list into an Array. In many
cases, the Array API is much easier to work with than
.Fn va_arg
and friends.
.Pp
.Fn ArrayDuplicate
duplicates an existing array. Note that Arrays only hold
pointers to data, not the data itself, so the duplicated array will
point to the same places in memory as the original array.
.Sh RETURN VALUES
.Fn ArrayCreate ,
.Fn ArrayFromVarArgs ,
and
.Fn ArrayDuplicate
return a pointer on the heap to a newly allocated array structure, or
.Dv NULL
if the allocation fails.
.Pp
.Fn ArrayGet
and
.Fn ArrayDelete
return pointers to values that were put into the array, or
.Dv NULL
if the provided array is
.Dv NULL
or the provided index was out of bounds.
.Fn ArrayDelete
returns the element at the specified index after removing it so that
it can be properly handled by the caller.
.Pp
.Fn ArrayTrim ,
.Fn ArrayInsert ,
and
.Fn ArrayAdd
return a boolean value indicating their status. They return a value of zero
on failure, and a non-zero value on success.
.Sh SEE ALSO
.Xr HashMap 3 ,
.Xr Queue 3

View File

@ -1,81 +0,0 @@
.Dd $Mdocdate: September 30 2022 $
.Dt BASE64 3
.Os Telodendria Project
.Sh NAME
.Nm Base64
.Nd A simple base64 encoder/decoder with "unpadded base64" support.
.Sh SYNOPSIS
.In Base64.h
.Ft size_t
.Fn Base64EncodedSize "size_t"
.Ft size_t
.Fn Base64DecodedSize "const char *" "size_t"
.Ft char *
.Fn Base64Encode "const char *" "size_t"
.Ft char *
.Fn Base64Decode "const char *" "size_t"
.Ft void
.Fn Base64Unpad "char *" "size_t"
.Ft int
.Fn Base64Pad "char **" "size_t"
.Sh DESCRIPTION
This is an efficient yet simple base64 encoding and decoding
library that supports regular base64, as well as the Matrix
specification's extension to base64, called "unpadded base64."
.Nm provides the ability to convert between the two, instead of
just implementing "unpadded base64."
.Pp
.Fn Base64EncodedSize
and
.Fn Base64DecodedSize
compute the amount of characters needed to store an encoded or
decoded message, respectively. Both functions take the size of
the message being encoded or decoded, but
.Fn Base64DecodedSize
also takes a pointer to an encoded string, because a few bytes of
the string need to be read in order to compute the size.
.Pp
.Fn Base64Encode
and
.Fn Base64Decode
do the actual work of encoding and decoding base64 data. They both
take a string and its length.
.Pp
.Fn Base64Unpad
and
.Fn Base64Pad
are used to implement Matrix unpadded base64.
.Fn Base64Unpad
takes a valid base64 string and strips off the trailing equal signs,
as per the specification.
.Fn Base64Pad
does the opposite; it takes an unpadded base64 input string, and pads
it with equal signs as necessary, so that it can be properly decoded
with
.Fn Base64Decode
if necessary. However, the Matrix specification envisons unpadded base64
as opaque; that is, once it's encoded, it never needs to be decoded.
In practice, a homeserver might need to decode an unpadded base64 string.
.Sh RETURN VALUES
.Fn Base64EncodedSize
and
.Fn Base64DecodedSize
simply return unsigned integers representing a number of bytes generated
from a simple computation.
.Pp
.Fn Base64Encode
and
.Fn Base64Decode
return pointers to new strings, allocated on the heap, or
.Dv NULL
if a heap allocation fails. These pointers must be
.Xr free 3 -ed
at some point when they are no longer needed.
.Pp
.Fn Base64Unpad
modifies the passed string in-place. It thus has no return value, because
it cannot fail. If the passed pointer is invalid, the behavior is undefined.
.Fn Base64Pad
returns a boolean value indicating whether the pad operation was successful.
In practice, this function will only fail if a bigger string is necessary, but
could not be automatically allocated on the heap.

View File

@ -1,69 +0,0 @@
.Dd $Mdocdate: November 30 2022 $
.Dt CANONICALJSON 3
.Os Telodendria Project
.Sh NAME
.Nm CanonicalJson
.Nd An extension of JSON that produces the Matrix spec's "canonical" JSON.
.Sh SYNOPSIS
.In CanonicalJson.h
.Ft int
.Fn CanonicalJsonEncode "HashMap *" "FILE *"
.Ft char *
.Fn CanonicalJsonEncodeToString "HashMap *"
.Sh DESCRIPTION
.Pp
.Nm
is an extension of
.Xr Json 3
that is specifically designed to produce the Matrix specification's
"canonical" JSON.
.Pp
Canonical JSON is defined as JSON that:
.Bl -bullet -offset indent
.It
Does not have any unecessary whitespace.
.It
Has all object keys lexicographically sorted.
.It
Does not contain any floating point numerical values.
.El
.Pp
The regular JSON encoder has no such rules, because normally they are
not needed. However, Canonical JSON is needed to consistently sign JSON
objects.
.Pp
.Fn CanonicalJsonEncode
encodes a JSON object following the rules of Canonical Json. See the
documentation for
.Fn JsonEncode ,
documented in
.Xr Json 3
for more details on how JSON encoding operates. This function exists
as an alternative to
.Fn JsonEncode ,
but should not be preferred to it in most circumstances. It is a lot
more costly, as it must lexicographically sort all keys and strip out
float values. If at all possible, use
.Fn JsonEncode
because it is much cheaper both in terms of memory and CPU time.
.Pp
.Fn CanonicalJsonEncodeToString
encodes a JSON object to a string.
.Xr Json 3
doesn't have any way to send JSON to a string, because there's
absolutely no reason to handle JSON strings in most cases. However,
the sole reason Canonical JSON exists is so that JSON objects can
be signed in a consistent way. Thus, you need a string to pass to
the signing function.
.Sh RETURN VALUES
.Pp
.Fn CanonicalJsonEncode
returns whether or not the JSON encoding operation was sucessful.
This function will fail only if NULL was given for any parameter.
Otherwise, if an invalid pointer is given, undefined behavior results.
.Pp
.Fn CanonicalJsonEncodeToString
returns a C string containing the canonical JSON representation of
the given object, or NULL if the encoding failed.
.Sh SEE ALSO
.Xr Json 3

View File

@ -1,80 +0,0 @@
.Dd $Mdocdate: November 26 2022 $
.Dt DB 3
.Os Telodendria Project
.Sh NAME
.Nm Db
.Nd A minimal flat-file database with object locking and an efficient cache.
.Sh SYNOPSIS
.In Db.h
.Ft Db *
.Fn DbOpen "char *" "size_t"
.Ft void
.Fn DbClose "Db *"
.Ft DbRef *
.Fn DbCreate "Db *" "size_t" "..."
.Ft DbRef *
.Fn DbLock "Db *" "size_t" "..."
.Ft int
.Fn DbUnlock "Db *" "DbRef *"
.Ft HashMap *
.Fn DbJson "DbRef *"
.Sh DESCRIPTION
.Pp
Telodendria operates on a flat-file database instead of a traditional relational
database. This greatly simplifies the persistent storage code, and creates a
relatively basic API, described by this page.
.Pp
.Fn DbOpen
and
.Fn DbClose
open and close a data directory.
.Fn DbOpen
takes the path to open, and a cache size in bytes. This API relies heavily on
caching, so the cache must be greater than the DB_MIN_CACHE preprocessor
constant.
.Pp
.Fn DbCreate
and
.Fn DbLock
load data objects from the database, with the notable difference being that
.Fn DbCreate
will fail if an object already exists, and
.Fn DbLock
will fail if an object does not exist. These are both variadic functions that
take a variable number of C strings, with the exact number being specified by
the second paramter. These C strings are used to generate a filesystem path from
which an object is loaded, unless it is already in the cache.
.Pp
Objects, when loaded, are locked both in memory and on disk, so that other threads
or processes cannot access them while they are locked. This is to ensure data
integrity.
.Pp
.Fn DbUnlock
unlocks an object and returns it back to the database, which syncs it to the
filesystem and caches it, if it isn't in the cache already.
.Pp
.Fn DbJson
converts a database reference into JSON. At this time, the database actually
stores objects as JSON, so this just returns an internal pointer, but in the
future it may have to be generated by decompressing a binary blob, or something
of that nature.
.Sh RETURN VALUES
.Pp
.Fn DbOpen
returns a reference to a database pointer, or NULL if there was an error
allocating memory, or opening the given directory with the given cache size.
.Pp
.Fn DbCreate
and
.Fn DbLock
return a database reference, or NULL if there was an error obtaining a reference
to the specified object.
.Pp
.Fn DbUnlock
returns a boolean value indicating whether or not the reference was successfully
unlocked. If the unlock is successful, then a non-zero value is returned. If it
isn't, 0 is returned, and it is up to the caller to decide how to proceed.
.Fn DbJson
returns a JSON object. Consult
.Xr Json 3
for the API used to manipulate this object.

View File

@ -1,146 +0,0 @@
.Dd $Mdocdate: October 11 2022 $
.Dt HASHMAP 3
.Os Telodendria Project
.Sh NAME
.Nm HashMap
.Nd A simple hash map implementation.
.Sh SYNOPSIS
.In HashMap.h
.Ft HashMap *
.Fn HashMapCreate "void"
.Ft void
.Fn HashMapFree "HashMap *"
.Ft void
.Fn HashMapLoadSet "HashMap *" "float"
.Ft void
.Fn HashMapFunctionSet "HashMap *" "unsigned long (*) (const char *)"
.Ft void *
.Fn HashMapSet "HashMap *" "char *" "void *"
.Ft void *
.Fn HashMapGet "HashMap *" "char *"
.Ft void *
.Fn HashMapDelete "HashMap *" "char *"
.Ft int
.Fn HashMapIterate "HashMap *" "char **" "void **"
.Sh DESCRIPTION
This is the public interface for Telodendria's hash map implementation.
This hash map is designed to be simple, well documented, and generally
readable and understandable, yet also performant enough to be useful,
because it is used extensively in Telodendria.
.Pp
Fundamentally, this is an entirely generic map implementation. It can
be used for many general purposes, but it is designed to only implement
the features that Telodendria needs to function well. One example of a
Telodendria-specific feature is that keys are
.Dv NULL -terminated
strings, not any arbitrary data.
.Pp
These functions operate on an opaque
.Dv HashMap
structure, which the caller has no knowledge about.
.Pp
.Fn HashMapCreate
creates a new hash map that is ready to be used with the rest of the
hash map functions.
.Fn HashMapFree
frees this hash map, such that it becomes invalid and any future use
with the functions in this API results in undefined behavior. Note that
it does not free the keys or values stored in the hash map, since this
implementation has no way of knowing what is actually stored in it, and
where it is located. You should use
.Fn HashMapIterate
to free the values appropriately.
.Pp
.Fn HashMapMaxLoadSet
controls the maximum load of the hash map before it is expanded.
When the hash map reaches the given capacity, it is grown. You don't
want to only grow hash maps when they are full, because that makes
them perform very poorly. The maximum load value is a percentage of how
full the hash map is, and it should be between 0
and 1, where 0 means that no elements will cause the map to be expanded,
and 1 means that the hash map must be completely full before it is
expanded. The default maximum load on a new
.Dv HashMap
object is 0.75, which should be good enough for most purposes, but
if you need finer tuning, feel free to play with the max load with
this function. The changes take effect on the next insert.
.Pp
.Fn HashMapFunctionSet
allows the given hash map to use a custom hashing function. New hash
maps have a sane hashing function that should work okay for most use
cases, but if you have a better hash function, it can be specified with
this function. Do not change the hash function after keys have been
added; doing so results in undefined behavior. Only set the hash
function immediately after constructing a new hash map.
.Pp
.Fn HashMapSet
sets the given string key to the given value. Note neither the key nor the
value is copied into the hash map's own memory space; only pointers are
stored. It is the caller's job to ensure that the key and value memory
remains valid for the life of the HashMap, and are freed when they are no
longer needed.
.Fn HashMapGet
retrieves the value for the given key and .Fn HashMapDelete
removes a value from a given key.
.Pp
.Fn HashMapIterate
iterates over all the keys and values of a hash map. This function works
very similarly to
.Xr getopt 3 ,
where calls are repeatedly made in a
.Dv while
loop until there are no more items to go over. The difference is that this
function does not rely on globals; it takes pointer pointers, and stores all
necessary state inside the hash map structure itself. Note that this does not
make this function thread-safe; two threads cannot be iterating over any given
hash map at the same time, though they can be iterating over different hash
maps at the same time.
.Pp
This function can be tricky to use in some scenarios, as it continues where
it left off on each call, until there are no more elements to go through in
the hash map. If you are not iterating over the entire map in one go, and
happen to break the loop, then the next time you attempt to iterate the
hash map, you'll start somewhere in the middle. Thus, it's recommended to
always iterate over the entire hash map if you're going to use this
function. Also note that the behavior of this function is undefined if
insertions or deletions occur during the iteration. It may work fine; it may
not. That functionality has not been tested.
.Pp
.Fn HashMapIterate
takes a pointer to a string ponter, and a pointer to a value pointer. When
iterating over the keys and values of the hash map, it sets these pointers
to the current position in the map so that the caller can use them for his
own purposes.
.Sh RETURN VALUES
.Fn HashMapCreate
may return
.Dv NULL
if memory could not be allocated on the heap. Otherwise, it returns a
valid pointer to a
.Dv HashMap
structure which can be used with the other functions in this API.
.Pp
.Fn HashMapSet
returns the previous value at the passed key, or
.Dv NULL
if no such value exists. All keys must have values; you can't set a key
to
.Dv NULL .
To delete a key, use the
.Fn HashMapDelete
function.
.Pp
.Fn HashMapDelete
returns the value that was deleted from the hash map at the given key,
or
.Dv NULL
if no such value exists.
.Pp
.Fn HashMapIterate
returns 1 if there are still elements left in the current iteration of the
hash map, or 0 if no valid hash map was provided, or there are no more elements
in it for the current iteration. Note that as soon as this function returns 0
on a hash map, subsequent iterations will start from the beginning.
.Sh SEE ALSO
.Xr HashMap 3 ,
.Xr Queue 3

View File

@ -1,171 +0,0 @@
.Dd $Mdocdate: December 13 2022 $
.Dt HTTP 3
.Os Telodendria Project
.Sh NAME
.Nm Http
.Nd Encode and decode various parts of the HTTP protocol.
.Sh SYNOPSIS
.In Http.h
.Ft const char *
.Fn HttpStatusToString "const HttpStatus"
.Ft HttpRequestMethod
.Fn HttpRequestMethodFromString "const char *"
.Ft const char *
.Fn HttpRequestMethodToString "const HttpRequestMethod"
.Ft char *
.Fn HttpUrlEncode "char *"
.Ft char *
.Fn HttpUrlDecode "char *"
.Ft HashMap *
.Fn HttpParamDecode "char *"
.Ft char *
.Fn HttpParamEncode "HashMap *"
.Sh DESCRIPTION
.Pp
.Nm
is a collection of utility functions that are useful for dealing with
HTTP. HTTP is not a complex protocol, but this API makes it a lot easier
to work with.
.Pp
.Fn HttpStatusToString
takes an HttpStatus and converts it into a string description of the
status, which is to be used in an HTTP response. For example, calling
.Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT"
produces the string "Gateway Timeout".
.Pp
HttpStatus is an enumeration that corresponds to the actual integer
values of the valid HTTP response codes. For example, calling
.Fn HttpStatusToString "504"
produces the same output. HttpStatus is defined as follows:
.Bd -literal -offset indent
typedef enum HttpStatus
{
/* Informational responses */
HTTP_CONTINUE = 100,
HTTP_SWITCHING_PROTOCOLS = 101,
HTTP_EARLY_HINTS = 103,
/* Successful responses */
HTTP_OK = 200,
HTTP_CREATED = 201,
HTTP_ACCEPTED = 202,
HTTP_NON_AUTHORITATIVE_INFORMATION = 203,
HTTP_NO_CONTENT = 204,
HTTP_RESET_CONTENT = 205,
HTTP_PARTIAL_CONTENT = 206,
/* Redirection messages */
HTTP_MULTIPLE_CHOICES = 300,
HTTP_MOVED_PERMANENTLY = 301,
HTTP_FOUND = 302,
HTTP_SEE_OTHER = 303,
HTTP_NOT_MODIFIED = 304,
HTTP_TEMPORARY_REDIRECT = 307,
HTTP_PERMANENT_REDIRECT = 308,
/* Client error messages */
HTTP_BAD_REQUEST = 400,
HTTP_UNAUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_METHOD_NOT_ALLOWED = 405,
HTTP_NOT_ACCEPTABLE = 406,
HTTP_PROXY_AUTH_REQUIRED = 407,
HTTP_REQUEST_TIMEOUT = 408,
HTTP_CONFLICT = 409,
HTTP_GONE = 410,
HTTP_LENGTH_REQUIRED = 411,
HTTP_PRECONDITION_FAILED = 412,
HTTP_PAYLOAD_TOO_LARGE = 413,
HTTP_URI_TOO_LONG = 414,
HTTP_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_RANGE_NOT_SATISFIABLE = 416,
HTTP_EXPECTATION_FAILED = 417,
HTTP_TEAPOT = 418,
HTTP_UPGRADE_REQUIRED = 426,
HTTP_PRECONDITION_REQUIRED = 428,
HTTP_TOO_MANY_REQUESTS = 429,
HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
/* Server error responses */
HTTP_INTERNAL_SERVER_ERROR = 500,
HTTP_NOT_IMPLEMENTED = 501,
HTTP_BAD_GATEWAY = 502,
HTTP_SERVICE_UNAVAILABLE = 503,
HTTP_GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
HTTP_VARIANT_ALSO_NEGOTIATES = 506,
HTTP_NOT_EXTENDED = 510,
HTTP_NETWORK_AUTH_REQUIRED = 511
} HttpStatus;
.Ed
.Pp
.Fn HttpRequestMethodFromString
and
.Fn HttpRequestMethodToString
convert an HttpRequestMethod enumeration value from and to a
string, respectively. The HttpRequestMethod enumeration is
defined as follows:
.Bd -literal -offset indent
typedef enum HttpRequestMethod
{
HTTP_METHOD_UNKNOWN,
HTTP_GET,
HTTP_HEAD,
HTTP_POST,
HTTP_PUT,
HTTP_DELETE,
HTTP_CONNECT,
HTTP_OPTIONS,
HTTP_TRACE,
HTTP_PATCH
} HttpRequestMethod;
.Ed
.Pp
These can be used for parsing a request method and then storing
it efficiently; it doesn't have be stored as a string, and it's
much nicer to work with enumeration values than it is with
strings in C. The very first enumeration value is not to be
passed to
.Fn HttpRequestMethodToString ,
rather it may be returned by
.Fn HttpRequestMethodFromString
if it cannot identify the request method string passed to it.
.Pp
.Fn HttpUrlEncode
and
.Fn HttpUrlDecode
deal with URL-safe strings.
.Fn HttpUrlEncode
encodes a C string such that it can appear in a URL, and
.Fn HttpUrlDecode
does the opposite; it decodes a URL string into the actual
bytes it is supposed to represent.
.Pp
.Fn HttpParamDecode
and
.Fn HttpParamEncode
convert HTTP parameters in the form of "param=value&param2=val2"
to and from a hash map for easy parsing, manipulation, and encoding.
.Sh RETURN VALUES
.Pp
.Fn HttpStatusToString
and
.Fn HttpRequestMethodToString
both return constant strings; they are not to be manipulated because
doing so would result in a segmentation fault, as these strings
are stored in the data segment of the program.
.Pp
.Fn HttpUrlEncode ,
.Fn HttpUrlDecode ,
.Fn HttpParamDecode ,
and
.Fn HttpParamEncode
all return strings that were allocated on the heap using the
Memory API, or NULL if there was an error allocating memory.
Thee strings returned can be manipulated at will, and must be
freed using the Memory API when they're no longer needed.
.Sh SEE ALSO
.Xr HashMap 3 ,
.Xr Memory 3

View File

@ -1,157 +0,0 @@
.Dd $Mdocdate: December 13 2022 $
.Dt HTTPSERVER 3
.Os Telodendria Project
.Sh NAME
.Nm HttpServer
.Nd Extremely simple HTTP server.
.Sh SYNOPSIS
.In HttpServer.h
.Ft HttpServer *
.Fn HttpServerCreate "unsigned short" "unsigned int" "unsigned int" "HttpHandler *" "void *"
.Ft void
.Fn HttpServerFree "HttpServer *"
.Ft int
.Fn HttpServerStart "HttpServer *"
.Ft void
.Fn HttpServerJoin "HttpServer *"
.Ft void
.Fn HttpServerStop "HttpServer *"
.Ft HashMap *
.Fn HttpRequestHeaders "HttpServerContext *"
.Ft HttpRequestMethod
.Fn HttpRequestMethodGet "HttpServerContext *"
.Ft char *
.Fn HttpRequestPath "HttpServerContext *"
.Ft HashMap *
.Fn HttpRequestParams "HttpServerContext *"
.Ft char *
.Fn HttpResponseHeader "HttpServerContext *" "char *" "char *"
.Ft void
.Fn HttpResponseStatus "HttpServerContext *" HttpStatus"
.Ft FILE *
.Fn HttpStream "HttpServerContext *"
.Ft void
.Fn HttpSendHeaders "HttpServerContext *"
.Sh DESCRIPTION
.Nm
builds on the
.Xr Http 3
API, and provides a very simple, yet very functional API for
creating an HTTP server. It aims at being easy to use and minimal,
yet also efficient. It uses non-blocking I/O, is fully multi-threaded,
very configurable, yet also able to be set up in just two function calls.
.Pp
This API should be familiar to those that have dealt with the HTTP server
libraries of other programming languages, particularly Java. In fact,
much of the terminology used in this code came from Java, and you'll
notice that the way responses are sent and received very closely resemble
the way it's done in Java.
.Pp
An HTTP server itself is created with
.Fn HttpServerCreate ,
which takes the port number to create the server on, the number of threads to
use, the maximum number of connections, a request handler function, and the
arguments to that function, in that order. The request handler function is
of the following type:
.Bd -literal -offset indent
typedef void (HttpHandler) (HttpServerContext *, void *)
.Ed
.Pp
Where the void pointer received is the same pointer that was passed into
.Fn HttpServerCreate
as the last parameter.
.Pp
The returned HttpServer pointer is then managed by
.Fn HttpServerStart ,
.Fn HttpServerStop ,
.Fn HttpServerJoin ,
and
.Fn HttpServerFree .
.Fn HttpServerStart
attempts to start the HTTP server, and returns immediately with the status.
This API is fully threaded and asyncronous, so the caller can continue working
while the HTTP server is running in a separate thread, and managing a pool
of threads to handle responses. Typically at some point after calling
.Fn HttpServerStart ,
the program will have no more work to do, and so it will want to wait for
the HTTP server to finish. This is accomplished with
.Fn HttpServerJoin ,
which joins the HTTP worker thread to the calling thread, making the
calling thread wait until the HTTP server has stopped.
.Pp
The only condition that will cause the HTTP server to stop is when
.Fn HttpServerStop
is invoked. This will typically happen in a signal handler that catches
signals instructing the program to shut down. Only after the server has
been stopped can it be freed with
.Fn HttpServerFree .
Note that calling
.Fn HttpServerFree
while the server is running results in undefined behavior.
.Pp
The remainder of the functions in this API are used inside of the
HTTP handler function passed by the caller of
.Fn HttpServerCreate .
They allow the handler to figure out the context of an HTTP request,
which includes the path that was requested, any parameters, and the
headers used to make the request. They also allow the handler
to respond with a status code, headers, and a body.
.Pp
.Fn HttpRequestHeaders ,
.Fn HttpRequestMethodGet ,
.Fn HttpRequestPath ,
and
.Fn HttpRequestParams
get the information about the request. They should all be passed the
server context pointer that was passed into the handler function.
The data returned by these functions should be treated as read-only,
and should not be freed; their memory is handled outside of the HTTP
server handler function.
.Pp
.Fn HttpResponseHeader
and
.Fn HttpResponseStatus
are used to set response headers, and the response status of the
request, respectively.
.Pp
.Fn HttpStream
returns a stream that is both readable and writable. Reading from
the stream reads the request body that the client sent, if there is
one. Note that the request headers have already been read, so the stream
is correctly positioned at the beginning of the body of the request.
.Fn HttpSendHeaders
must be called before the stream is written to, otherwise a malformed
HTTP response will be sent. An HTTP handler should properly set all
the headers it intends to send, send those headers, and then write the
response body to the stream. Finally, note that the stream does not
need to be closed by the HTTP handler; in fact, doing so results in
undefined behavior. The stream is managed by the server itself.
.Sh RETURN VALUES
.Pp
.Fn HttpRequestHeaders
and
.Fn HttpRequestParams
return a hash map that can be used to access the request headers and
parameters, if necessary. Note that the request parameters would be
GET parameters, attached to the path that was requested. To get POST
parameters, read the stream returned by
.Fn HttpStream
and pass the contents into
.Fn HttpParamDecode
to get a hash map.
.Pp
.Fn HttpRequestPath
returns a string that represents the path that the client requested. Note
that it is not normalized; it is exactly what the client sent, so it should
be checked for path traversal attacks and other malformed paths that the
client may sent.
.Pp
.Fn HttpResponseHeader
returns the previous value of the given header, or NULL if there was no
previous value.
.Pp
.Fn HttpStream
returns a FILE pointer that can be read and written using the C standard
I/O functions.
.Sh SEE ALSO
.Xr Http 3

View File

@ -1,228 +0,0 @@
.Dd $Mdocdate: November 30 2022 $
.Dt JSON 3
.Os Telodendria Project
.Sh NAME
.Nm Json
.Nd A fully-featured JSON API.
.Sh SYNOPSIS
.In Json.h
.Ft JsonType
.Fn JsonValueType "JsonValue *"
.Ft JsonValue *
.Fn JsonValueObject "HashMap *"
.Ft HashMap *
.Fn JsonValueAsObject "JsonValue *"
.Ft JsonValue *
.Fn JsonValueArray "Array *"
.Ft Array *
.Fn JsonValueAsArray "
.Ft JsonValue *
.Fn JsonValueString "char *"
.Ft char *
.Fn JsonValueAsString "JsonValue *"
.Ft JsonValue *
.Fn JsonValueInteger "long"
.Ft long
.Fn JsonValueAsInteger "JsonValue *"
.Ft JsonValue *
.Fn JsonValueFloat "double"
.Ft double
.Fn JsonValueAsFloat "JsonValue *"
.Ft JsonValue *
.Fn JsonValueBoolean "int"
.Ft int
.Fn JsonValueAsBoolean "JsonValue *"
.Ft JsonValue *
.Fn JsonValueNull "void"
.Ft void
.Fn JsonValueFree "JsonValue *"
.Ft void
.Fn JsonFree "HashMap *"
.Ft void
.Fn JsonEncodeString "const char *" "FILE *"
.Ft void
.Fn JsonEncodeValue "JsonValue *" "FILE *"
.Ft int
.Fn JsonEncode "HashMap *" "FILE *"
.Ft HashMap *
.Fn JsonDecode "FILE *"
.Sh DESCRIPTION
.Nm
is a fully-featured JSON API for C using
.Xr Array 3
and
.Xr HashMap 3
that can parse JSON, and serialize an in-memory structure
to JSON.
It builds on the foundation set up by those APIs because
that's all JSON really is, just maps and arrays.
.Nm
also provides a structure for encapsulating an arbitrary
value and identifying its type, making it easy for a program
to work with JSON data.
.Nm
is very strict and tries to adhere as closely as possible to
the proper defintion of JSON. It will fail on syntax errors
of any kind, which is fine for a Matrix homeserver because we can
just return M_BAD_JSON if anything here fails, but it may not
be suitable for other purposes.
.Pp
This JSON implementation focuses primarily on serialization and
deserialization to and from streams. It does not provide facilities
for handling JSON strings; it only writes JSON to output streams, and
reads them from input streams. If course, you can use the POSIX
.Xr fmemopen 3
and
.Xr open_memstream 3
if you want to deal with JSON strings, but JSON is intended to be an
exchange format. Data should be converted to JSON right before it is
leaving the program, and converted from JSON as soon as it is coming
in.
.Pp
The
.Nm
header defines the following enumeration for identifying types of
values:
.Bd -literal -offset indent
typedef enum JsonType
{
JSON_NULL, /* Maps to C NULL */
JSON_OBJECT, /* Maps to a HashMap of JsonValues */
JSON_ARRAY, /* Maps to an Array of JsonValues */
JSON_STRING, /* Maps to a NULL-terminated C string */
JSON_INTEGER, /* Maps to a C long */
JSON_FLOAT, /* Maps to a C double */
JSON_BOOLEAN /* Maps to a C boolean, 1 or 0 */
} JsonType;
.Ed
.Pp
A JsonValue encapsulates all the possible types that can be stored in
JSON. It is an opaque structure that can be managed entirely by the
functions defined in this API. It is important to note that in the case
of objects, arrays, and strings, this structure only stores pointers to
the allocated data, it doesn't store the data itself, but the data IS
freed when using
.Fn JsonFree .
.Pp
Objects are represented as hash maps consisting entirely of JsonValue
structures, and arrays are represented as arrays consisting entirely
of JsonValue structures. When generating a JSON object, any
attempt to stuff a value into a hash map or array without encoding it
as a JsonValue first will result in undefined behavior.
.Pp
.Fn JsonValueType
determines the type of a given JsonValue.
.Pp
The
.Fn JsonValue*
functions wrap their input in a JsonValue that represents the given
value. The
.Fn JsonValueAs*
functions do the opposite; they unwrap a JsonValue and return the
actual usable value itself. They all closely resemble each other and
they all behave the same way, so to save on time and effort, they're
not explicitly documented individually. If something is unclear about
how these functions work, consult the source code, and feel free
to write the documentation yourself for clarification. Otherwise,
reach out to the official Matrix rooms, and someone should be able
to help you.
.Pp
.Fn JsonValueNull
is a special case that represents a JSON null. Because
.Xr Array 3
and
.Xr HashMap 3
do not accept NULL values, this function should be used to represent
NULL in JSON. Even though a small amount of memory is allocated just
to point to NULL, this keeps the APIs clean.
.Pp
.Fn JsonValueFree
frees the memory being used by a JSON value. Note that this will
recursively free all Arrays, HashMaps, and other JsonValues that
are reachable from the given value. It also invokes
.Fn Free
(documented in
.Xr Memory )
on all strings, so make sure passed string pointers point to strings
on the heap, not the stack. This will be the case for all strings
returned by
.Fn JsonDecode ,
but if you are manually creating JSON objects and stitching them
together, you should be aware that calling this function on a value
that contains a pointer to a stack string will result in undefined
behavior.
.Pp
.Fn JsonFree
recursively frees a JSON object, iterating over all the values and
freeing them using
.Fn JsonValueFree .
.Pp
.Fn JsonEncodeString
encodes the given string in such a way that it can be embedded in a
JSON stream. This entails:
.Bl -bullet -offset indent
.It
Escaping quotes, backslashes, and other special characters using
their backslash escape.
.It
Encoding bytes that are not UTF-8 using escapes.
.It
Wrapping the entire string in double quotes.
.El
.Pp
This function is provided via the public
.Nm
API so that it is accessible to custom JSON encoders, such as
.Xr CanonicalJson 3 .
This will typically be used for encoding JSON keys; for encoding
values, just use
.Fn JsonEncodeValue .
.Pp
.Fn JsonEncodeValue
serializes a JsonValue as it would appear in JSON output. This is
a recursive function that also encodes all child values reachable
from the given value. This function is exposed via the public
.Nm
API so that it is accessible to custom JSON encoders. Normal users
that are not writing custom encoders should in most cases just use
.Fn JsonEncode
to encode an entire object.
.Pp
.Fn JsonEncode
encodes a JSON object as minimized JSON and writes it to the given
output stream. This function is recursive; it will serialize
everything accessible from the passed object.
.Fn JsonDecode
does the opposite; it reads from a JSON stream and decodes it
into a hash map of JsonValues.
.Sh RETURN VALUES
.Pp
.Fn JsonValueType
returns a JsonType, documented above, that tells what the given
value actually is, or JSON_NULL if the passed value is NULL.
Note that even a fully valid JsonValue may actually be of type
JSON_NULL, so this function should not be used to determine
whether or not a given value is valid.
.Pp
The
.Fn JsonValue*
functions return a JsonValue that holds a pointer to the passed
value, or NULL if there was an error allocating memory. The
.Fn JsonValueAs*
functions return the actual value represented by the given
JsonValue so that it can be manipulated by the program, or
NULL if no value was provided, or the value is not of the
correct type expected by the function.
.Pp
.Fn JsonEncode
returns whether or not the encoding operation was successful.
This function will fail if any passed parameters are NULL,
otherwise it will assume all pointers are valid and return a
success value.
.Pp
.Fn JsonDecode
returns a hash map of JsonValues that can be manipulated by
this API, or NULL if there was an error parsing the JSON.
.Sh SEE ALSO
.Xr HashMap 3 ,
.Xr Array 3

View File

@ -1,156 +0,0 @@
.Dd $Mdocdate: November 25 2022 $
.Dt LOG 3
.Os Telodendria Project
.Sh NAME
.Nm Log
.Nd A simple logging framework for logging to files, standard output,
or the system log.
.Sh SYNOPSIS
.In Log.h
.Ft LogConfig *
.Fn LogConfigCreate "void"
.Ft void
.Fn LogConfigFree "LogConfig *"
.Ft void
.Fn LogConfigLevelSet "LogConfig *" "int"
.Ft void
.Fn LogConfigIndent "LogConfig *"
.Ft void
.Fn LogConfigUnindent "LogConfig *"
.Ft void
.Fn LogConfigIndentSet "LogConfig *" "size_t"
.Ft void
.Fn LogConfigOutputSet "LogConfig *" "FILE *"
.Ft void
.Fn LogConfigFlagSet "LogConfig *" "int"
.Ft void
.Fn LogConfigFlagClear "LogConfig *" "int"
.Ft void
.Fn LogConfigTimeStampFormatSet "LogConfig *" "char *"
.Ft void
.Fn Log "LogConfig *" "int" "const char *" "..."
.Sh DESCRIPTION
.Pp
A heavily-modifed version of Shlog, a simple C logging library facility
that allows for colorful outputs, timestamps, and custom log levels.
This library differs from Shlog in that the naming conventions have
been updated to be consistent with Telodendria, and system log support
has been added.
.Pp
Shlog was originally a learning project. It worked well and produced
elegant logging output, so it was chosen to be the main logging
mechanism of Telodendria. The original Shlog project is now dead; Shlog
lives on now only as Telodendria's logging mechanism.
.Pp
One of the design choices made in this library, which unfortunately
makes code using it a little more verbose, is that multiple logging
configurations can exist in a program. No global variables are used,
and all functions are thread-safe.
.Pp
.Fn LogConfigCreate
creates a new log configuration with sane defaults that can be used
immediately. Note that every call to
.Fn Log
requires a valid configuration pointer.
.Fn LogConfigFree
frees all memory associated with that configuration, invalidating
it. Passing an invalid configuration pointer into any of the
functions defined here result in undefined behavior. The
.Fn LogConfig*Set
functions manipulate the data pointed to by the pointer returned
by
.Fn LogConfigCreate .
.Pp
.Fn LogConfigLevelSet
sets the current log level on the specified log configuration. This
indicates that only messages at or above this level should be
logged; all other messages are silently discarded by the
.Fn Log
function. The passed log level should be one of the log levels
defined by
.Xr syslog 3 .
Refer to that page for a complete list of acceptable log levels,
and note that passing in an invalid or unknown log level will
result in undefined behavior.
.Pp
.Fn LogConfigIndent
causes the output of
.Fn Log
to be indented two more spaces than it was previously. This can be
helpful when generating stack traces, or otherwise producing
hierarchical output. After calling this function, all future
messages using the given config will be indented two more spaces
than they were before. This is just a wrapper function around
.Fn LogConfigIndentSet ,
which allows the caller to specify an arbitrary indentation in
spaces.
.Fn LogConfigUnindent
does the exact opposite of
.Fn LogConfigIndent ;
it subtracts two spaces from the indentation level, unless there
is no indent, then it does nothing.
.Pp
.Fn LogConfigOutputSet
sets the file stream that logging output should be written to. This
defaults to standard output, but it can be standard error, or some
other file stream. Passing a NULL value for the file pointer sets
the log output to standard output. Note that the output file stream
is only used if FLAG_OUTPUT_SYSLOG is not set.
.Pp
.Fn LogConfigFlagSet
and
.Fn LogConfigFlagClear
are used for setting a number of boolean options on a log
configuration. They utilize bitwise operators, so multiple options
can be set or cleared with a single function call using bitwise OR
operators. The flags defined as preprocessor macros, and are as
follows:
.Bl -tag -width Ds
.It LOG_FLAG_COLOR
When set, enable color-coded output on TTYs. Note that colors are
implemented as ANSI escape sequences, and are not written to file
streams that are not actually connected to a TTY, to prevent those
sequences from being written to a file.
.Xr isatty 3
is checked before writing ANSI sequences.
.It LOG_FLAG_SYSLOG
When enabled, log output to the syslog using
.Xr syslog 3 ,
instead of logging to the file set by
.Fn LogConfigOutputSet .
This flag always overrides the file stream set by that function,
regardless of when it was set.
.El
.Pp
.Fn LogConfigTimeStampFormatSet
allows a custom timestamp to be prepended to each message
if the output is not going to the system log. Consult your
system's documentation for
.Xr strftime 3 .
A value of NULL disables outputting a timestamp before messages.
.Pp
The
.Fn Log
function actually writes a log message to a console, file, system
log, or other supported output device using the given configuration.
This function is thread-safe; it locks a mutex before writing a
message, and then unlocks it after the message was written. Each
log configuration has its own mutex, so this function can be used
with mutiple active log configurations.
.Pp
This function only logs messages if their level is above or equal to
the currently configured log level, making it easy to turn some
messages on or off. The function has the same usage as
.Xr printf 3 .
.Sh RETURN VALUES
.Pp
.Fn LogConfigCreate
returns a pointer to a configuration structure on the heap, or NULL
if there was an error allocating memory for it. The returned
structure is opaque to the caller; the
.Fn LogConfig*
functions should be used to manipulate it.
.Pp
All other functions do not return anything. They are simply
assumed to always succeed. If any arguments are NULL, then the
functions do nothing, unless otherwise specifically noted.

View File

@ -1,89 +0,0 @@
.Dd $Mdocdate: December 12 2022 $
.Dt MATRIX 3
.Os Telodendria Project
.Sh NAME
.Nm Matrix
.Nd Functions for writing Matrix API endpoints.
.Sh SYNOPSIS
.In Matrix.h
.Ft void
.Fn MatrixHttpHandler "HttpServerContext *" "void *"
.Ft void
.Fn MatrixErrorCreate "MatrixError"
.Sh DESCRIPTION
.Nm
provides some helper functions that bind to the
.Xr HttpServer 3
interface and add basic Matrix functionality, turning an
HTTP server into a Matrix homeserver.
.Pp
.Xr MatrixHttpHandler
is the HTTP handler function that handles all Matrix homeserver
functionality. It should be passed into
.Fn HttpServerCreate ,
and it expects that an instance of MatrixHttpHandlerArgs will also
be provided, because that's what the void pointer is cast to.
That structure is defined as follows:
.Bd -literal -offset indent
typedef struct MatrixHttpHandlerArgs
{
LogConfig *lc;
TelodendriaConfig *config;
Db *db;
} MatrixHttpHandlerArgs;
.Ed
.Pp
This structure should be populated once and then never modified again
for the duration of the HTTP server.
.Pp
.Fn MatrixErrorCreate
is a convenience function that constructs an error payload, including
the error code and message, given just a MatrixError. MatrixErrors
exactly follow the errors in the Matrix specification, and are
defined as follows:
.Bd -literal -offset indent
typedef enum MatrixError
{
M_FORBIDDEN,
M_UNKNOWN_TOKEN,
M_MISSING_TOKEN,
M_BAD_JSON,
M_NOT_JSON,
M_NOT_FOUND,
M_LIMIT_EXCEEDED,
M_UNKNOWN,
M_UNRECOGNIZED,
M_UNAUTHORIZED,
M_USER_DEACTIVATED,
M_USER_IN_USE,
M_INVALID_USERNAME,
M_ROOM_IN_USE,
M_IVALID_ROOM_STATE,
M_THREEPID_IN_USE,
M_THREEPID_NOT_FOUND,
M_THREEPID_AUTH_FAILED,
M_THREEPID_DENIED,
M_SERVER_NOT_TRUSTED,
M_UNSUPPORTED_ROOM_VERSION,
M_BAD_STATE,
M_GUEST_ACCESS_FORBIDDEN,
M_CAPTCHA_NEEDED,
M_CAPTCHA_INVALID,
M_MISSING_PARAM,
M_INVALID_PARAM,
M_TOO_LARGE,
M_EXCLUSIVE,
M_RESOURCE_LIMIT_EXCEEDED,
M_CANNOT_LEAVE_SERVER_NOTICE_ROOM
} MatrixError;
.Ed
.Sh RETURN VALUES
.Pp
.Fn MatrixErrorCreate
returns a JSON object that represents the given error code. It can be
immediately returned as the HTTP response body, or modified as needed.
.Sh SEE ALSO
.Xr HttpServer 3 ,
.Xr Log 3 ,
.Xr TelodendriaConfig 3 ,
.Xr Db 3

View File

@ -1,135 +0,0 @@
.Dd $Mdocdate: November 29 2022 $
.Dt MEMORY 3
.Os Telodendria Project
.Sh NAME
.Nm Memory
.Nd Smart memory management.
.Sh SYNOPSIS
.In Memory.h
.Ft void *
.Fn MemoryAllocate "size_t" "const char *" "int"
.Ft void *
.Fn MemoryReallocate "void *" "size_t" "const char *" "int"
.Ft void
.Fn MemoryFree "void *" "const char *" "int"
.Ft size_t
.Fn MemoryAllocated "void"
.Ft void
.Fn MemoryFreeAll "void"
.Ft MemoryInfo *
.Fn MemoryInfoGet "void *"
.Ft size_t
.Fn MemoryInfoGetSize "MemoryInfo *"
.Ft const char *
.Fn MemoryInfoGetFile "MemoryInfo *"
.Ft int
.Fn MemoryInfoGetLine "MemoryInfo *"
.Ft void *
.Fn MemoryInfoGetPointer "MemoryInfo *"
.Ft void
.Fn MemoryIterate "void (*) (MemoryInfo *, void *)" "void *"
.Ft void
.Fn MemoryHook "void (*) (MemoryAction, MemoryInfo *, void *" "void *"
.Sh DESCRIPTION
.Nm
is an API that allows for smart memory management and profiling. It wraps
the standard library functions
.Xr malloc 3 ,
.Xr realloc 3 ,
and
.Xr free 3 ,
and offers identical semantics, while providing functionality that the
standard library doesn't have, such as getting statistics on the total
memory allocated on the heap, and getting the size of a block of memory
given a pointer. Additionally, thanks to preprocessor macros, the exact
file and line number at which an allocation, reallocation, or free occured
can be obtained given a pointer. Finally, all the blocks allocated on the
heap can be iterated and evaluated, and a callback function can be executed
every time a memory operation occurs.
.Pp
A number of macros are available, which make using the
.Nm
API much more useful.
.Fn Malloc
expands to
.Fn MemoryAllocate
with the __FILE__ and __LINE__ constants for the second and third
arguments respectively. Likewise,
.Fn Realloc
and
.Fn Free
expand to
.Fn MemoryReallocate
and
.Fn MemoryFree
with __FILE__ and __LINE__ as the second and third parameters.
This allows the API to be used exactly how the standard library
would be used. In fact, the functions which these macros expand to
are not intended to be called directly; only use the macros for the
best results.
.Pp
If all memory used in the program is managed by this API, there are some
helpful functions that allow the program to probe the state of the heap.
These functions are described here.
.Pp
.Fn MemoryAllocated
gets the total memory that the program has on the heap. This operation
iterates over all the heap allocations made with
.Fn MemoryAllocate
and then returns a total count, in bytes.
.Pp
.Fn MemoryFreeAll
iterates over all the heap allocations made with
.Fn MemoryAllocate
and calls
.Fn MemoryFree
on them. It immediately invalidates all pointers, and any subsequent
reads or writes to heap memory result in undefined behavior. This
is typically used at the end of the program.
.Pp
.Fn MemoryInfoGet
takes a pointer and fetches information about it, storing it in a
structure that can then be queried.
.Pp
.Fn MemoryInfoGetSize ,
.Fn MemoryInfoGetFile ,
.Fn MemoryInfoGetLine ,
and
.Fn MemoryInfoGetPointer
all take in the structure returned by
.Fn MemoryInfoGet ,
and return the respective property about the given property. These are
especially useful for logging and debugging with
.Fn MemoryIterate
and
.Fn MemoryHook .
.Pp
.Fn MemoryIterate
takes a pointer to a function that takes the memory information structure,
as well as a void pointer for caller-provided arguments. It iterates over
all the heap memory currently allocated at the time of calling.
.Fn MemoryHook
has a similar prototype, although the function pointer it takes is slightly
different. It also takes a memory action as the first argument. The
.Nm
API stores the pointer to this function, and executes it every time memory
is allocated, reallocated, or freed. This allows a program to execute code
whenever memory is allocated.
.Sh RETURN VALUES
.Pp
.Fn MemoryAllocate
and
.Fn MemoryReallocate
return the same as their standard library counterparts. That is, a pointer
to memory on the heap, or NULL if there was an error allocating it.
.Pp
.Fn MemoryInfoGet
returns a pointer to information about a block on the heap, or NULL if the
passed pointer was not allocated by the
.Nm
API, or is no longer allocated.
.Pp
.Fn MemoryAllocated
returns an unsigned integer that indicates the number of bytes currently
allocated on the heap.

View File

@ -1,100 +0,0 @@
.Dd $Mdocdate: November 25 2022 $
.Dt QUEUE 3
.Os Telodendria Project
.Sh NAME
.Nm Array
.Nd A simple static queue data structure.
.Sh SYNOPSIS
.In Queue.h
.Ft Queue *
.Fn QueueCreate "size_t"
.Ft void
.Fn QueueFree "Array *"
.Ft int
.Fn QueuePush "Queue *" "void *"
.Ft void *
.Fn QueuePop "Queue *"
.Ft void *
.Fn QueuePeek "Queue *"
.Ft int
.Fn QueueFull "Queue *"
.Ft int
.Fn QueueEmpty "Queue *"
.Sh DESCRIPTION
These functions implement a simple queue data structure that
is statically sized.
This implementation does not actually store the values of the
items in it; it only stores pointers to the data. As such, you will
still have to manually maintain all your data. The advantage of this
is that these functions don't have to copy data, and thus don't care
how big the data is. Furthermore, arbitrary data can be stored in the
queue.
.Pp
This queue implementation operates on the heap. It is a circular
queue, and it does not grow as it is used. Once the size is set, the
queue never gets any bigger.
.Pp
These functions operate on a queue structure which is opaque to the
caller.
.Pp
.Fn QueueCreate
and
.Fn QueueFree
allocate and deallocate a queue, respectively.
Note that
.Fn QueueFree
does not free any of the values stored in the queue; it is the caller's
job to manage the memory for each item. Typically, the caller would
dequeue all the items in the queue and free them before freeing
the queue itself.
.Pp
.Fn QueuePush
and
.Fn QueuePop
are the main functions used to modify the array. They enqueue and dequeue
elements from the queue structure, respectively.
.Pp
.Fn QueuePeek
simply returns the pointer that is next up in the queue without actually
discarding it, such that the next call to
.Fn QueuePeek
or
.Fn QueuePop
return the same pointer.
.Pp
.Fn QueueFull
and
.Fn QueueEmpty
return a boolean value that indicates whether or not the queue is full
or empty, respectively.
.Sh RETURN VALUES
.Pp
.Fn QueueCreate
returns a queue structure, or
.Dv NULL
if there was a memory allocation error.
.Pp
.Fn QueuePush
as well as
.Fn QueueFull
and
.Fn QueueEmpty
all return boolean values. In the case of
.Fn QueuePush
whether or not the push was actually successful is returned. This will
only happen if the queue is already full, or a
.Dv NULL
pointer is passed.
.Pp
.Fn QueuePop
and
.Fn QueuePeek
both return caller-managed pointers that would have been at some point
pushed into the queue with the
.Fn QueuePush
function. They may also return
.Dv NULL
if the queue is empty.
.Sh SEE ALSO
.Xr Array 3 ,
.Xr HashMap 3

View File

@ -1,87 +0,0 @@
.Dd $Mdocdate: December 12 2022 $
.Dt ROUTES 3
.Os Telodendria Project
.Sh NAME
.Nm Routes
.Nd Matrix API endpoint abstractions.
.Sh SYNOPSIS
.In Routes.h
.Ft char *
.Fn MATRIX_PATH_POP "MATRIX_PATH"
.Ft size_t
.Fn MATRIX_PATH_PARTS "MATRIX_PATH"
.Ft int
.Fn MATRIX_PATH_EQUALS "char *" "char *"
.Sh DESCRIPTION
.Pp
.Nm
provides all of the Matrix API route functions, as well as a few
helpful macros to be used to declare those route functions, and some
macros that are intended to be used inside them.
.Pp
The route macros are intended to increase the readability of the header,
so the individual routes are not documented here; only the helper
macros and structures are documented here. Consult the
.Pa Routes.h
file for a list of the registered route functions.
.Pp
.Fn MATRIX_PATH_POP
and
.Fn MATRIX_PATH_PARTS
are macros that abstract away the underlying data structure of the
path so that that routes don't have to care what it is. The reason
this design choice was made was so that the data structure can be
switched out without breaking all the routes. These macros should
be preferred to the actual underlying data structure functions,
because the data structure may change in the future.
.Pp
At the moment, the path data structure is just an array, but it would
be much more efficient to switch to a queue (which can be easily done
with the current Queue implementation if we just add a function that
computes how many elements are in the queue.)
.Pp
.Fn MATRIX_PATH_POP
returns the next available part of the path, and removes it from
the path such that the next call to
.Fn MATRIX_PATH_POP
returns the part after.
.Fn MATRIX_PATH_PARTS
returns the number of path parts remaining.
.Pp
.Fn MATRIX_PATH_EQUALS
is just a simple string comparison macro. It takes two strings and
returns a boolean value indicating whether or not they're equal.
.Pp
.Nm
also defines
.Fn ROUTE
and
.Fn ROUTE_IMPL .
.Fn ROUTE
is intended to be used only inside the route header, and should be
invoked to declare a new route function prototype. It takes the
route function name, which by convention starts with "Route".
.Fn ROUTE_IMPL
may be used to actually implement a route function. It takes the
route function name, and the name of the variable to put the
RouteArgs in.
.Pp
Every route function takes a RouteArgs structure, which is defined
as follows:
.Bd -literal -offset indent
typedef struct RouteArgs
{
MatrixHttpHandlerArgs *matrixArgs;
HttpServerContext *context;
MATRIX_PATH *path;
} RouteArgs;
.Ed
.Sh RETURN VALUES
.Pp
Each route returns a JSON hash map that contains the response it
intends to return to the client making the request. Routes
should NOT return NULL, because then no body will be returned to
the client, and that is almost always a bug. The Matrix specification
usually mandates that at least an empty JSON object is returned.
.Sh SEE ALSO
.Xr Matrix 3

View File

@ -1,95 +0,0 @@
.Dd $Mdocdate: December 10 2022 $
.Dt TELODENDRIACONFIG 3
.Os Telodendria Project
.Sh NAME
.Nm TelodendriaConfig
.Nd Parse the configuration file into a structure.
.Sh SYNOPSIS
.In TelodendriaConfig.h
.Ft TelodendriaConfig *
.Fn TelodendriaConfigParse "HashMap *" "LogConfig *"
.Ft void
.Fn TelodendriaConfigFree "TelodendriaConfig *"
.Sh DESCRIPTION
.Pp
Validate and maintain the Telodendria server's configuration data. This API
builds on the JSON API to add Telodendria-specific parsing. It takes a
fully-parsed JSON object and converts it into a TelodendriaConfig, which is
much more structured and easier to work with than the JSON. The config
structure is not opaque like many other structures in Telodendria. This is
intentional; defining functions for all of the fields would just add a lot
of unecessary overhead. The structure is defined as follows:
.Bd -literal -offset indent
typedef struct TelodendriaConfig
{
char *serverName;
char *baseUrl;
char *identityServer;
char *uid;
char *gid;
char *dataDir;
unsigned short listenPort;
unsigned int flags;
unsigned int threads;
unsigned int maxConnections;
size_t maxCache;
char *logTimestamp;
int logLevel;
} TelodendriaConfig;
.Ed
.Pp
Since the configuration will live in memory for a long time, it is important
that unused values are freed as soon as possible. Therefore, the Telodendria
structure is not opaque; values are accessed directly, and they can be
freed as the program wishes. Do note that if you're going to free a value, you
should set it to NULL, because
.Fn TelodendriaConfigFree
will unconditionally call
.Fn Free
on all values.
.Pp
The flags variable in this structure is a bit field that contains the OR-ed values
of any of the given flags:
.Bd -literal -offset indent
typedef enum TelodendriaConfigFlag
{
TELODENDRIA_FEDERATION,
TELODENDRIA_REGISTRATION,
TELODENDRIA_LOG_COLOR,
TELODENDRIA_LOG_FILE,
TELODENDRIA_LOG_STDOUT,
TELODENDRIA_LOG_SYSLOG
} TelodendriaConfigFlag;
.Ed
.Pp
Do note that the actual values of these enums are omitted, but they can be
OR-ed together and added to flags.
.Pp
.Fn TelodendriaConfigParse
parses a JSON map, extracting the necessary values, validating them, and then
adding them to a new TelodendriaConfig for future use by the program. All values
are copied, so the JSON hash map can be safely freed if this function
succeeds. It takes a working log configuration so that messages can be written
to the log as the parsing progresses, to warn users about default values and
report errors, for example.
.Pp
.Fn TelodendriaConfigFree
frees all of the memory allocated for the given configuration. This function
unconditionally calls
.Fn Free
on all items in the structure, so make sure that items that were already freed
are NULL.
.Sh RETURN VALUES
.Pp
.Fn TelodendriaConfigParse
returns a TelodendriaConfig that is completely independent of the passed
configuration hash map, or NULL if one or more required values is missing, or
there was some other error while parsing the configuration.
.Sh SEE ALSO
.Xr Json 3

View File

@ -1,133 +0,0 @@
.Dd $Mdocdate: November 26 2022 $
.Dt UTIL 3
.Os Telodendria Project
.Sh NAME
.Nm Util
.Nd Some misc. helper functions that don't need their own headers.
.Sh SYNOPSIS
.In Util.h
.Ft unsigned long
.Fn UtilServerTs "void"
.Ft unsigned long
.Fn UtilLastModified "char *"
.Ft int
.Fn UtilMkdir "const char *" "const mode_t"
.Ft char *
.Fn UtilUtf8Encode "unsigned long"
.Ft char *
.Fn UtilStringDuplicate "char *"
.Ft char *
.Fn UtilStringConcat "char *" "char *"
.Ft int
.Fn UtilSleepMillis "long"
.Ft size_t
.Fn UtilParseBytes "char *"
.Ft ssize_t
.Fn UtilGetDelim "char **" "size_t *" "int" "FILE *"
.Ft ssize_t
.Fn UtilGetLine "char **" "size_t *" "FILE *"
.Sh DESCRIPTION
.Pp
This header holds a number of random functions related to strings,
time, and other tasks that don't require a full API, just one or
two functions. For the most part, the functions here are entirely
standalone, depending only on POSIX functions, however there are a
few that specifically utilize Telodendria APIs. Those are noted.
.Pp
.Fn UtilServerTs
gets the current time in milliseconds since the Unix epoch. This
uses
.Xr gettimeofday 2
and time_t, and converts it to a single number, which is then
returned to the caller. A note on the 2038 problem: as long as
sizeof(long) >= 8, that is, as long as the long datatype is 64 bits
or more, which it is on all modern 64-bit Unix-like operating
systems, then everything should be fine. Expect Telodendria on 32 bit
machines to break in 2038. I didn't want to try to hack together
some system to store larger numbers than the architecture supports.
We can always re-evaluate things over the next decade.
.Pp
.Fn UtilMkdir
behaves just like the system call
.Xr mkdir 2 ,
but it creates any intermediate directories if necessary, unlike
.Xr mkdir 2 .
.Pp
.Fn UtilUtf8Encode
takes a UTF-8 codepoint and encodes it into a string buffer
containing between 1 and 4 bytes. The string buffer is allocated
on the heap, so it should be freed when it is no longer needed.
.Pp
.Fn UtilStringDuplicate
duplicates a NULL-terminated string, and returns a new string on the
heap. This is useful when a function takes in a string that it needs
to store for for long amounts of time, even perhaps after the
original string is long gone.
.Pp
.Fn UtilSleepMillis
sleeps the calling thread for the given number of milliseconds. It
occurred to me that POSIX does not specify a super friendly way to
sleep, so this is a wrapper around the POSIX
.Xr nanosleep 2
designed to make its usage much, much simpler.
.Pp
.Fn UtilLastModified
uses
.Xr stat 2
to get the last modified time of the given file. This is used
primarily for caching file data.
.Pp
.Fn UtilStringConcat
takes in two NULL-terminated strings and returns their concatenation.
It works a lot like
.Xr strcat 3 ,
but it takes care of allocating memory big enough to hold both
strings. One or both strings may be NULL. If a string is NULL, it
is treated like an empty string.
.Pp
.Fn UtilParseBytes
is a highly specialized function used in parsing the configuration file.
It takes in a string which is supposed to represent a number of bytes.
It must consist of an integer, followed by an optional suffix of k, K, m, M,
g, or G, indicating the value is kilobytes, megabytes, or gigabytes.
.Pp
.Fn UtilGetDelim
and
.Fn UtilGetLine
work identically to the POSIX equivalents, documented in
.Xr getdelim 3 ,
except it assumes pointers were allocated using the Memory API, and it
uses the Memory API itself to reallocate necessary pointers.
.Sh RETURN VALUES
.Pp
.Fn UtilServerTs
and
.Fn UtilLastModified
return timestamps in the form of milliseconds since the Unix epoch as an unsigned
long. The Matrix specification requires timestamps be in milliseconds, so these
functions are designed to make that easy and convenient.
.Pp
.Fn UtilMkdir
returns 0 on success, and -1 on failure, just like
.Xr mkdir 2 .
It also sets errno as appropriate.
.Pp
.Fn UtilSleepMillis
returns the result of calling
.Xr nanosleep 2 .
.Pp
.Fn UtilUtf8Encode ,
.Fn UtilStringDuplicate ,
and
.Fn UtilStringConcat
return a NULL-terminated string on the heap if they succeed, or NULL on failure.
Typically a failure in these functions indicate an error allocating memory.
.Pp
.Fn UtilParseBytes
returns a number of bytes, or 0 if there was an error parsing the byte string.
.Pp
.Fn UtilGetDelim
and
.Fn UtilGetLine
return the same value as their POSIX equivalents, documented in
.Xr getdelim 3 .

View File

@ -1,209 +0,0 @@
.Dd $Mdocdate: December 9 2022 $
.Dt TELODENDRIA.CONF 5
.Os Telodendria Project
.Sh NAME
.Nm telodendria.conf
.Nd Configuration file for Telodendria
.Sh DESCRIPTION
.Nm
is the configuration file for the Telodendria homeserver,
.Xr telodendria 8 .
Telodendria is designed to be extremely configurable. As such,
it has a fairly extensive configuration file. The configuration
file can be passed to the Telodendria binary with the
.Sy -f
option, and is typically located at
.Pa /etc/telodendria.conf
.sp
.Nm
uses JSON for its configuration file syntax, which should be
familiar. Very early versions of
.Nm
used a custom OpenBSD-style configuration file, but this was
not as versatile or familiar as JSON.
.Sh DIRECTIVES
Here are the top-level directives:
.Bl -tag -width Ds
.It Ic listen Ar port
The port to listen on. Telodendria will bind to all interfaces, but it
is recommended to configure your firewall so that it only listens on
localhost, and then configure a reverse proxy such as
.Xr relayd 8
in front of it, because Telodendria does not implement TLS. Note that
Telodendria doesn't provide multiple ports for the various services it
offers. ALl APIs are made available over the same port, so care should
be taken in
.Xr relayd.conf 5
to ensure that only the public Matrix API paths are made available over
the internet.
.Ar port
should be a decimal port number. This directive is entirely optional. If
it is omitted, then Telodendria will listen on port 8008 by default.
.It Ic serverName Ar name
Configure the domain name of your homeserver. Note that Matrix servers
cannot be migrated to other domains, so once this is set, it should never
change unless you want unexpected things to happen, or you want to start
over.
.Ar name
should be a DNS name that can be publically resolved. This directive
is required.
.It Ic baseUrl Ar url
Set the server's base URL.
.Ar url
should be a valid URL, complete with the protocol. It does not need to
be the same as the server name; in fact, it is common for a subdomain of
the server name to be the base URL for the Matrix homeserver.
.Pp
This URL is the URL at which Matrix clients will connect to the server,
and is thus served as a part of the
.Pa .well-known
manifest.
.Pp
This directive is optional. If it is not specified, it is automatically
deduced from the server name.
.It Ic identityServer Ar url
The identity server that clients should use to perform identity lookups.
.Pp
.Ar url
follows the same rules as
.Ic baseUrl .
.Pp
This directive is optional. If it is not specified, it is automatically
set to be the same as the base URL.
.It Ic runAs Ar uidObj
The effective UNIX user and group to drop to after binding to the socket
and changing the filesystem root for the process. This only works if
Telodendria is running as the root user, and is used as a security mechanism.
If this option is set and Telodendria is started as a non-priviledged user,
then a warning is printed to the log if that user does not match what's
specified here. This directive is optional, but should be used as a sanity
check, if nothing more, to make sure the permissions are working properly.
.Pp
This directive takes an object with the following directives:
.Bl -tag -width Ds
.It Ic uid Ar user
The UNIX username to drop to. If
.Ic runAs
is specified, this directive is required.
.It Ic gid Ar group
The UNIX group to drop to. This directive is optional; if it is not
specified, then the value of
.Ic uid
is copied.
.El
.It Ic dataDir Ar directory
The data directory into which Telodendria will write all user and event
information. Telodendria doesn't use a database like other Matrix homeserver
implementations; it uses a flat-file directory structure, similar to how an
SMTP server uses Maildirs to deliver email. This directive is required.
.Pp
Telodendria will
.Xr chroot 2
into this directory as soon as possible for security reasons. If the
.Ic log
directive is configured to write to a file, the log file will be written
in the data directory.
.Ar directory
should be an absolute path, under which all Telodendria data will live.
.It Ic federation Ar true|false
Whether to enable federation with other Matrix homeservers or not. Matrix is
by its very nature a federated protocol, but if you just want to run your
own internal chat server with no contact with the outside, then you can use
this option to disable federation. It is highly recommended to set this to
.Ar true ,
however, if you wish to be able to communicate with users on other Matrix
servers. This directive is required.
.It Ic registration Ar true|false
Whether or not to enable new user registration or not. For security and anti-spam
reasons, you can set this to
.Ar false .
If you do, you can still add users via the administrator API. In an ideal world,
everyone would run their own homeserver, so no public registration would ever
be required. Unfortunately, not everyone has the means to run their own homeserver,
especially because of the fact that public IPv4 addresses are becoming increasingly
harder to come by. If you would like to provide a service to those that are unable
to run their own homeserver, you can aset this to
.Ar true ,
which will allow anyone to create an account. Telodendria should be capable of handling
a large amount of users without difficulty or security issues. This directive is
required.
.It Ic log Ar logObj
The log file configuration. Telodendria uses its own logging facility, which can
output logs to standard output, a file, or the syslog. This directive is required,
and it takes an object with the following directives:
.Bl -tag -width Ds
.It Ic output Ar stdout|file|syslog
The lot output destination. If set to
.Ar file ,
Telodendria will log to
.Pa telodendria.log
inside the
.Ic dataDir .
.It Ic level Ar error|warning|notice|message|debug
The level of messages to log at. Each level shows all the levels above it. For
example, setting the level to
.Ar error
will show only errors, while setting the level to
.Ar warning
will show warnings and errors.
.Ar notice
shows notices, warnings, and errors, and so on. The
.Ar debug
level shows all messages.
.It Ic timestampFormat Ar format|none|default
If you want to customize the timestamp format shown in the log, or disable it
altogether, you can do so via this option. Acceptable values are
.Ar none ,
.Ar default ,
or a formatter string as described by your system's
.Xr strftime 3 .
This option only applies if
.Ic log
is "stdout" or "file".
.It Ic color Ar true|false
Whether or not to enable colored output on TTYs. Note that ANSI color sequences
will not be written to a log file, only a real terminal, so this option only
applies if the log is being written to a standard output which is connected to
a terminal.
.Pp
This option only applies if
.Ic log
is "stdout".
.El
.It Ic threads Ar count
How many worker threads to spin up to handle requests. This should generally be
less than the total CPU core count, to prevent overloading the system. The most
efficient number of threads ultimately depends on the configuration of the
machine running Telodendria, so you may just have to play around with different
values here to see which gives the best performance.
.It Ic maxConnections Ar count
The maximum number of simultanious connections to allow to the daemon. This option
prevents the daemon from allocating large amounts of memory in the even that it
undergoes a denial of service attack. It typically does not need to be adjusted.
.It Ic maxCache Ar bytes
The maximum size of the cache. Telodendria relies heavily on caching to speed
things up. The cache grows as data is loaded from the data directory. All cache
is stored in memory. This option limits the size of the memory cache. If you have
a system that has a lot of memory, you'll get better performance if this option
is set higher. Otherwise, this value should be lowered on systems that have
minimal memory available.
.El
.Sh FILES
.Bl -tag -width Ds
.It Pa /etc/telodendria.conf
The default
.Xr telodendria 8
configuration file.
.It Pa /var/telodendria
The recommended data directory.
.El
.Sh EXAMPLES
Please consult the default
.Nm
that ships with Telodendria for a complete example. If you installed Telodendria
as a package, then the example should be located at the default location. If you
are building from source, you can find the default config in the
.Pa contrib/
directory.
.Sh SEE ALSO
.Xr telodendria 8

View File

@ -1,159 +0,0 @@
.Dd $Mdocdate: November 20 2022 $
.Dt PORTING 7
.Os Telodendria Project
.Sh NAME
.Nm porting
.Nd Some guidelines for packaging Telodendria for your operating system.
.Sh DESCRIPTION
.Pp
Telodendria is distributed at source code, and does not offer a convenient
install process. This is intentional; the Telodendria project is primarily
concerned with developing Telodendria itself, not packaging it for the
hundreds of different operating systems and Linux distributions that exist.
It is my firm belief that distributing an open source project is not the
job of the open source developer; that's the reason software distributions
exist, to collect and distribute software.
.Pp
It would be impossible to single-handedly package Telodendria for every
platform, because each platform has very different expectations for
software. Even different Linux distributions have different conventions
for where manual pages, binaries, and configuration files go.
.Pp
That being said, this page aims to assist those who want to package
Telodendria for their operating system or software distribution.
.Pp
See
.Xr td 8
for instructions on how to build Telodendria. Only proceed with packaging
Telodendria after you have successfully built it on your operating system.
.Pp
To package Telodendria, you should collect the following files, and figure
out where they should be installed for your system:
.Bl -bullet
.It
The telodendria server binary itself:
.Pa build/telodendria
.It
All manual pages in the
.Pa man/
directory that are prefixed with "telodendria". These are the user documentation
pages. All pages that do not have the "telodendria" prefix are intended only
for developers, and so do not need to be installed to the system.
.It
An init script. People that wish to install Telodendria to their system
expect it to be integrated enough that Telodendria can be easily started at
boot, and otherwise managed by the system's daemon tools, be it systemd, or
another init system. Consult your system's documentation for writing an
init script. Do note that Telodendria does not fork itself to the background;
the init script should do that. Also note that Telodendria responds to SIGINT,
so a SIGINT should be sent to stop Telodendria instead of a SIGTERM or SIGKILL.
.It
A sample
.Pa telodendria.conf
file. Whether this file is placed at the actual configuration file location,
or a directory containing configuration file samples is entirely up to the
packager. You can use or adapt any of the configuration files in
.Pa contrib/ ,
or write your own specifically for your package.
.El
.Pp
Once you have collected the files that need to be installed, make sure your
package performs the following tasks on install:
.Bl -bullet
.It
If necessary, depending on the config used, create a new system user for
the Telodendria daemon to run as.
.It
If conventional for your system, enable the Telodendria init script so
that Telodendria is started on system boot.
.It
Insruct the user to carefully read the sample
.Pa telodendria.conf
as well as the
.Xr telodendria.conf 5
manual page before starting Telodendria.
.El
.Pp
The goal of a package should be to get everything as ready-to-run as possible.
The user should only have to change one or two default options in the configuration
file before Telodendria can be started.
.Pp
Remember to publicly document the setup of Telodendria on your platform so
that users can easily get Telodendria running. If you're packaging Telodendria
for a containerization system such as Docker, you can omit the things that
containers typically do not have, such as the init script and man pages.
.Pp
Also remember that your port should feel like it belongs on your target system.
Follow all of your system's conventions when placing files on the filesystem,
so your users know what to expect. The goal is not to have a unified experience
across all operating systems, rather, you should cater to the opinions of your
operating system. Telodendria is architected in such a way that it does not
impose the developers opinions of where things should go.
.Sh PORTS REPOSITORY
.Pp
The Telodendria project provides a
.Pa Telodendria-Ports
repository that is intended to serve as the official staging environment for
ports and packages of Telodendria to various operating systems. You can
most likely take inspiration from the files stored in this repository, or even
straight up copy and modify files for your own port if you'd like.
Telodendria-Ports is a convenient resource for new porters. You can grab
a copy of the ports repository like this:
.Bd -literal -offset indent
$ cvs -d anoncvs@bancino.net:/cvs checkout -P Telodendria-Ports
.Ed
.Pp
(It is assumed that you have read
.Xr telodendria-contributing 7 ,
so you already have the proper tools for getting the ports repository.)
.Pp
The repository is structured in such a way that each operating system or
software distribution has a directory. For example, the OpenBSD port has an
.Pa OpenBSD/
directory. If you make a HaikuOS port, then make a
.Pa HaikuOS/
directory.
.Pp
The structure of the operating system directories themselves is really defined
by the conventions of the packaging system you're working with. There's no standard
structure, as each system does things differently. Just use the directory as a
working space that stores all the files your packaging system needs to build
a package for Telodendria.
.Pp
The exact procedure for interacting with this repository is also defined by how
your packaging system works. For OpenBSD, one is required to copy the
.Pa OpenBSD/
directory to
.Pa /usr/ports/net/telodendria ,
and then copy files back and forth when modifications are made. You may be able
to get away with building your package in place, without having to copy files
anywhere. Otherwise, you can try symlinking directories, but OpenBSD ports did
not like this at all.
.Pp
Submitting your port files to Telodendria-Ports is by no means required,
but it may be helpful to have a public record that you're working on a port,
and it's definitely helpful to have a consolidated list of all the ports out
there, making it much easier to determine whether or not a given platform
has a port, especially if you're unfamilier with that platform's port system.
If you are capable of managing your port entirely within your packaging system,
then go for it! I just wanted a staging environment that I have commit access to
for my ports, allowing me to prototype and test my port before submitting it
to the actual ports tree.
.Pp
It is important to note that I only maintain the OpenBSD port, because that's
the operating system I use. But, notice that I follow my own rules; nothing
inherently OpenBSD-specific, besides a few optional files in
.Pa contrib/ ,
actually exists in the main repository. All operating-system specific files,
such as init scripts and the like, should go to Telodendria-Ports. It's also
important to note that the files placed in Telodendria-Ports are not automatically
assumed to be official builds. The developer that committed the files to
Telodendria-Ports will most likely also have to get them submitted upstream,
because I'm not going to go to all these upstream packagers with the port files
here, I'll only do that with ports I officially maintain, which is the
OpenBSD port.
.Pp
.Sh SEE ALSO
.Xr telodendria-contributing 7 ,
.Xr td 8 ,
.Xr telodendria 7

View File

@ -1,41 +0,0 @@
.Dd $Mdocdate: December 13 2022 $
.Dt TELODENDRIA-CHANGELOG 7
.Os Telodendria Project
.Sh NAME
.Nm Telodendria
.Nd The change log for the Telodendria project.
.Sh PROJECT STATUS
.Pp
.Nm
is a very ambitious project. There's a lot that needs to happen yet
before it is usable. At the moment,
.Nm
is starting to resemble a Matrix homeserver, but you can't really
call it one yet. The foundation is mostly in place; now there's the
Matrix specification to implement.
.Pp
Just because there's not much here yet doesn't mean you should go
away! I could always use help, so you are more than welcome to get
involved in the project if you want to see things move quicker.
Feel free to donate using the links on the project website, or
see the
.Xr contributing 7
page for details on how to get involved. The CVS repository has
a file called
.Pa TODO.txt ,
which contains a checklist of the items that need to be completed.
Feel free to grab an item on that list and start writing patches!
It's a good idea to join the Matrix rooms noted in
.Xr telodendria 7
as well, so you can discuss your progress and ask questions.
.Sh v0.1.0
.Pp
Tuesday, December 13, 2022
.Pp
This is the first public release of
.Nm
so there are no changes to report. Future releases will
have a complete change log entry here.
.Pp
This is a symbolic release targeted at developers, so there's nothing
useful to ordinary users yet. Stay tuned for future releases though!

View File

@ -1,251 +0,0 @@
.Dd $Mdocdate: November 11 2022 $
.Dt TELODENDRIA-CONTRIBUTING 7
.Os Telodendria Project
.Sh NAME
.Nm telodendria-contributing
.Nd Guide to contributing to the Telodendria project.
.Sh DESCRIPTION
Telodendria is an open source project. As such, it welcomes
contributions. There are many ways you can contribute, and any
way you can is greatly appreciated. This page contains some of
the ways you can help out.
.Sh REPORTING ISSUES
Please reach out to the Matrix rooms mentioned at the top of
.Xr telodendria 7 .
All issue tracking takes place in those rooms. Start by reaching
out to the general room, and if you think there's a legitimate
problem with Telodendria itself, then stick the issue in the
issues room, where it can be discussed further. Issues usually
remain in the Matrix rooms, but severe enough issues may be put
in a
.Pa TODO
file in the
.Xr cvs 1
repository so that they don't get lost.
.Sh DEVELOPING
The primary language used to write Telodendria code is ANSI C.
Other languages you'll find in the Telodendria repository include
shell scripts,
.Xr mdoc 7 ,
and a little bit of HTML and CSS. If you have any experience with
any of these languages, your contributions are valuable! Please follow
the guidelines on this page to ensure the contribution workflow goes
as smoothly as possible.
.Ss Getting the Code
If you'd like to hack on Telodendria, you'll need the following tools
in addition to a C compiler and POSIX shell:
.Bl -tag
.It Xr cvs 1
For checking out and updating your local copy of the source code.
.It Xr indent 1
For formatting your code before generating patches.
.It Xr patch 1
For applying patches to your local copy of the source code.
.El
.sp
All of these tools are built into OpenBSD. While you don't have to
use OpenBSD to develop Telodendria, it may make the process a bit
easier. In fact, these tools where chosen precisely because they
were built into my operating system of choice.
.sp
You can download an official release tarball from the website if
you would really like, but the preferred way to get the source
code for development is to check it out from CVS. This makes generating
patches a lot easier.
.Bd -literal -offset indent
$ cvs -d anoncvs@bancino.net:/cvs checkout -P Telodendria
$ cd Telodendria
.Ed
.sp
If you already checked out the code previously, make sure you update
your local copy before you start developing:
.Bd -literal -offset indent
$ cvs -q update -dP
.Ed
.sp
You should now have the latest source code. Follow the
.Sx CODE STYLE
as you make your changes. If the
.Xr cvs 1
command fails with a "Connection refused" error message, try setting
the
.Ev CVS_RSH
environment variable to "ssh", like this:
.Bd -literal -offset indent
$ export CVS_RSH=ssh
.Ed
.sp
Then run the checkout command again. Some versions of CVS on some
systems don't use SSH to checkout by default, so if yours doesn't,
you might want to put the above line into your shell init script.
.Ss Submitting Patches
Telodendria aims at remaining as minimal as possible. This doesn't just
mean minimal code, it also means a minimal development process, which is
why Telodendria doesn't use GitHub, GitLab, or even SourceHut. Instead,
the contribution workflow operates on submitting patch files to a public
Matrix room, sort of like the OpenBSD project operates on patch files
sent to a public mailing list.
.sp
If you're not used to manually creating and submitting patches instead of
just opening a "pull request," you should be pleased to hear that submitting
patches is fairly easy to do if you've got the CVS sources checked out. In
fact, I find it easier than having to make a GitHub account, forking a
project's repository, and then making a pull request for it. Once you have
made your changes in your local copy of the code, and you've configured your
environment properly as noted in the manual for
.Xr td 8 ,
just run the patch recipe:
.Bd -literal -offset indent
$ td patch
.Ed
.sp
This will automatically generate a patch file for all your changes, and then
open it in your preferred editor. You can also generate a patch file for only
certain files and directories. To do that, set
.Ev PATCHSET ,
like this:
.Bd -literal -offset indent
# Only write a patch for README.txt and the files in docs/
$ PATCHSET="README.txt docs/" td patch
.Ed
.sp
As you'll notice, the top of the patch file should have some email-style
headers that look like this:
.Bd -literal -offset indent
From: Jordan Bancino <@jordan:bancino.net>
Date: Fri Jul 29 03:21:21 PM EDT 2022
Subject: Document Patch Procedure
.Ed
.sp
As much information should be filled out for you, such as the date. An
attempt to fill out the From header was also made by
.Xr td 8 ,
but the information there can be modifed as necessary. Consult the manual
for
.Xr td 8
for more details. The Subject should very briefly describe what the patch
is about.
.sp
You'll also notice these lines in the patch:
.Bd -literal -offset indent
[ ] I have read the Telodendria Project development certificate of
origin, and certify that I have permission to submit this patch
under the conditions specified in it.
.Ed
.sp
This is a checkbox that tells me whether or not you actually have the
rights to submit your patch, and that once you submit your patch, your
code is bound by the Telodendria project license, which you can and
should view in
.Xr telodendria 7 .
The full text of the developer certificate of origin is as follows:
.Bl -enum
.It
The contribution was created in whole or in part by me, and I have the right
to submit it under the open source licenses of the Telodendria project; or
.It
The contribution is based upon a previous work that, to the best of my knowledge,
is covered under an appropriate open source license and I have the right under
that license to submit that work with modifications, whether created in whole
or in part by me, under the Telodendria project license; or
.It
The contribution whas provided directly to me by some other person who certified
(1), (2), or (3), and I have not modifed it.
.It
I understand and agree that this project and the contribution are made public
and that a record of the contribution\(emincluding all personal information
I submit with it\(emis maintained indefinitely and may be redistributed
consistent with this project or the open source licenses involved.
.El
.sp
If you agree to the above, fill in the square brackets with an 'x', and then after
the headers, but before the checkbox, write a more thorough description of the
patch and why it was created. Then, send the resulting patch file to the public
Matrix room using
.Xr send-patch 1 .
.sp
Try to keep your patches on topic\(emmake one patch file per feature or bug fix
being implemented. It is okay if your patches depend on previous patches, just
indicate that in the patch description. Note that it may take a while for
patches to be committed, and some patches may not be comitted at all. In either
case, all sent patches are queued from the Matrix room into the public patch
directory, so they can be referenced easier in the future. If you want to
reference a submitted patch in a Matrix message, email, or other digital medium,
it might be a good idea to link to it in the public patch directory.
.sp
The public patch directory works as follows: when you send your patch to the
Matrix room, it is downloaded by Telodendria Bot and placed in the
.Pa ingress/
directory, named as the message ID. Then, it is assigned a patch ID and
copied to the
.Pa p/
directory as just "%d.patch", where "%d" is obviously the patch ID. This is
a permanent link that will always reference your patch. Then, your patch will
be symlinked into the
.Pa queue/
directory. I have a script that automatically ingresses patches and queues them
for me, and I use this to review patches. If your patch is accepted, the queue
symlink will be moved to
.Pa accepted/
and the submitted patch will be committed to the official CVS repository.
If your patch is rejected for some reason, its symlink will be moved to the
.Pa rejected/
directory. Regardless of the state of your patch, it will always remain
permalinked in the
.Pa p/
directory, and when it is accepted or rejected, Telodendria Bot will send a
message to the Matrix room.
.sp
You're always welcome to inquire about rejected patches, and request that they
be reviewed again, or you can use them as a starting point for future patches.
.sp
The public patch directory is located at
.Lk https://telodendria.io/patches/
.Sh CODE STYLE
In general, these are the conventions used by the code base. This guide
may be slightly outdated or subject to change, but it should be a good
start. The source code itself is always the absolute source of truth, so
as long as you make your code look like the code surrounding it, you should
be fine.
.Bl -bullet
.It
All function, enumeration, structure, and header names are CamelCase. This
is preferred to snake_case because it is more compact.
.It
All variable names are lowerCamelCase. This is preferred to snake_case
because it is more compact.
.It
enumerations and structures are always typedef-ed to their same name. The
typedef should occur in the public API header, and the actual declaration
should live in the implementation file.
.It
A feature of the code base lives in a single C source file that has a
matching header. The header file should only export public symbols;
everything else in the C source should be static.
.It
Except where absolutely necessary, global variables are forbidden to
prevent problems with threads and whatnot. Every variable a function
needs should be passed to it either through a structure, or as a
separate argument.
.It
Anywhere curly braces are optional, there still must be curly braces. This
makes it easier to add on to the code later, and just makes things a bit
less ambiguous.
.El
.sp
As far as actually formatting the code goes, such as where to put brackets,
and whether or not to use tabs or spaces, use
.Xr indent 1
to take care of all of that. The root of the CVS repository has a
.Pa .indent.pro
that should automatically be loaded by
.Xr indent 1
to set the correct rules. If you don't have a working
.Xr indent 1 ,
then just indicate in your patch that I should run my
.Xr indent 1
on the code after applying it. Although in reality, I'll likely
run my own
.Xr indent 1
on the code anyway, just to make sure the spacing is consistent, if nothing
else.

View File

@ -1,221 +0,0 @@
.Dd $Mdocdate: December 13 2022 $
.Dt TELODENDRIA 7
.Os Telodendria Project
.Sh NAME
.Nm Telodendria
.Nd The terminal branches of an axon.
.Sh DESCRIPTION
.Nm
is an open source Matrix homeserver written entirely from scratch in ANSI C and
designed to be lightweight and simple, yet functional.
.Pp
.Nm
differentiates itself from the other Matrix homeserver
implementations because it:
.Bl -bullet
.It
Is written in C, a stable, low-level programming language with a
long history, low build and runtime overhead, and wide compatibility.
.It
Is written with minimalism as a primary design goal. Whenever possible
and practical, no third-party libraries are pulled into the source code.
Everything
.Nm
needs is custom written. As a result,
.Nm
depends only on a standard C compiler and a POSIX C library to be built,
both of which should come with any good Unix-style operating system already,
which means you shouldn't have to install anything extra.
.It
Uses a flat-file directory structure to store data instead of a database.
This has a number of advantages:
.Bl -bullet
.It
It makes setup and maintenance much easier.
.It
It allows
.Nm
to run on systems with fewer resources.
.El
.It
Is packaged as a single small, statically-linked and highly-optimized binary
that can be run just about anywhere. It is designed to be extremely easy to
set up and consume as few resources as possible.
.It
Is permissively licensed.
.Nm
is licensed under a modified MIT license, which imposes very few restrictions
on what you can do with it.
.El
.Pp
.Nm
is on Matrix! Check out the official Matrix rooms:
.Pp
.TS
box tab(;);
ll.
#telodendria:bancino.net;The public "space" room.
#telodendria-releases:bancino.net;Get notified of new releases.
#telodendria-general:bancino.net;General discussion and support.
#telodendria-newsletter:bancino.net;Periodic status updates.
#telodendria-issues:bancino.net;Report bugs and issues.
#telodendria-patches:bancino.net;Submit code patches to the project.
#telodendria-ports:bancino.net;Discussion about porting and packaging.
.TE
.Pp
.Nm
is designed to be fairly straightforward, but that doesn't mean there
won't be hiccups along the way. If you're struggling to get
.Nm
up and running, you're more than welcome to reach out for support. Just
hop into the appropriate Matrix rooms and talk to me!
.Sh RATIONALE
I want a lightweight Matrix homeserver designed specifically for OpenBSD,
and other Unix-like operating systems. I want a homeserver that can be
developed and compiled with built-in tools. I want it to function entirely
on a base OS install without having to install any packages whatsoever. I've
found that as far as these goals are concerned, the existing homeserver
implementations fall short. This project aims to prove that Matrix homeservers
can be lightweight and written in such a way that very few, if any, third-party
libraries need to be pulled in.
.Pp
I also want to learn how Matrix works, and I want to understand the code I'm
running on my server, which is why I'm writing every component from scratch,
even the HTTP server and router.
.Pp
The advantage of using a flat-file database instead of a real database is that
your data remains in a format that is incredibly easy to digest. Backups are
incredibly easy as well\(emjust
.Xr tar 1
up the data directory and you're good to go.
.Sh PROJECT GOALS
The goals of this project are generally divided into user goals and developer
goals, depending on who they impact the most. This isn't an exaustive list
of the project's goals, but it is a list of things that I want to prioritize,
because other server implementations lack them.
.Pp
The user goals are as follows:
.Bl -bullet
.It
To implement the latest Matrix specification as fully and completely as possible.
All features defined by the specification should eventually be present in
.Nm .
.It
To be as privacy-friendly as possible.
.Nm
should not store any information it does not absolutely need to function as a
Matrix homeserver. While
.Nm
strives to be feature-complete, it should not implement anything not explicitly
defined in the Matrix specification.
.It
To be a production-ready Matrix server capable of working in constrained environments,
such as embedded devices, cheap VPSs, or a peer-to-peer device.
.Nm
should also work on traditional setups as well, and be able to handle a decent
amount of users. It should work well for personal Matrix homeservers that also
host a few friends and/or family members.
.It
To be easier to get up and running, and yet also be more configurable than other
Matrix homeserver implementations. The configuration file should be flexible,
well-documented, and easy to understand and modify. An intuitive command-line
tool for administrative tasks should also be available.
.El
.Pp
The developer goals are as follows:
.Bl -bullet
.It
To have as few build and runtime dependencies as possible. It should be possible
to compile and run
.Nm
on any POSIX operating system right out of the box.
.Nm
should be fully statically-linked, so it can run under a
.Xr chroot 3 .
Furthermore, it should be possible to read all the documentation offline, and
without any overly complicated tools. You'll notice that this documentation is
a collection of
.Xr man 1
pages, not HTML or Markdown, to remove the dependency on a browser or Markdown
parser. Any Unix-like system has a manual page viewer, which makes the
documentation more accessible, even on remote systems that have no graphical
interface to read the documentation. This ensures that you can read the
documentation for the installed version of
.Nm
without having to go online.
.It
To have a simple yet elegant workflow, and not depend on any large or complex
development tools such as code forges. The entire development workflow should
be able to be successfully and efficiently completed on a base OpenBSD install.
Of course, you don't have to use OpenBSD for development, but the point is that
the workflow should require fairly standardized and simple tools.
.It
To write clean, elegant, well-tested, and well-documented code. The goal is to build
a Matrix homeserver from the ground up, not just because I don't like the way existing
homeservers are implemented, but also because I want to learn how Matrix works,
make it more accessible, and potentially teach others a little bit along the way.
.It
To foster a welcoming community. Many big communities such as Linux and OpenBSD
have hostile leaders.
.Nm
shouldn't have a hostile leader. I want to be as understanding as I can, and talk
through issues politely and in a civil manner. If I'm failing in this way, don't
be afraid to call me out!
.El
.Sh SEE ALSO
.Xr telodendria 8 ,
.Xr telodendria.conf 5 ,
.Xr td 8
.Sh STANDARDS
The installed version of
.Nm
conforms to the latest Matrix specification available at the time
of its release.
.Sh HISTORY
.Pp
.Nm
was started in early July of 2022. For a change log of this
project, see
.Xr telodendria-changelog 7 .
.Sh AUTHORS
.Nm
was started by and is almost exclusively developed by
Jordan Bancino <@jordan:bancino.net>. Contributions to the code,
website, documentation, or other components of this project have
been made by various open source developers.
.Sh LICENSE
All of the code and documentation for
.Nm
is licensed under a modified MIT license. Please consult the
.Pa LICENSE.txt
file for the actual license text. The
.Nm
license text differs from the MIT license in the following ways:
.Bl -bullet
.It
Where the MIT license states that the copyright notice and permission
notice shall be included in all copies or
.Pa substantial
portions of the software, the
.Nm
license requires the copyright notice and permission notice be included
with
.Pa all
portions, regardless of the size, of the software by omitting the word
.Pa substantial .
.El
.Pp
The
.Nm
logo in all forms, including the ASCII representation, belongs solely
to the
.Nm
project. It must only be used to represent the official
.Nm
project, and may only appear in official
.Nm
media. If
.Nm
is forked, the logo must be removed completely from the project, and
optionally replaced with a different one. The logo may not be modified
in any way or for any purpose.

View File

@ -1,63 +0,0 @@
.Dd $Mdocdate: November 27 2022 $
.Dt SEND-PATCH 8
.Os Telodendria Project
.Sh NAME
.Nm send-patch
.Nd Submit a patch file to the Telodendria Patches Matrix room
.Sh SYNOPSIS
.Nm
.Op patch
.Sh DESCRIPTION
.Nm
is a simple shell script for submitting patch files to Telodendria's patch
room for review. Do note that it depends on
.Xr jq 1
and
.Xr curl 1 ,
and so may not work out of the box on some systems. However, these tools are
readily available for most systems. Please consult your package manager's
manual for installing packages.
.Pp
.Nm
takes a single argument, a patch file. It also reads a number of environment
variables, as described in the following section. This script is designed to be
simple; it only pushes files into a hard-coded Matrix room. Thus, as far as
Matrix clients go, this one is a rather minimal one, and that is by design.
.Pp
This script exists so that users who are working on a machine that doesn't have
a Matrix client installed can still submit work to the Telodendria project. The
goal is to make development as accessible as possible.
.Pp
This script only supports password login, so if your homeserver does not
support password login, it will not work.
.Sh ENVIRONMENT
.Pp
.Nm
utilizes the following environment variables:
.Bl -tag -width Ds
.It Ev MXID
Your Matrix ID in the standard format. This is used to connect to your
homeserver to send the message.
.It Ev MXPW
Your Matrix account's password. If not set, you will be prompted for your
password by the script, unless
.Ev ACCESS_TOKEN
is set.
.It Ev ACCESS_TOKEN
If you already have an access token for your account, such as one from an
existing session, then you can set this environment variable to bypass the
password authentication flow.
.El
.Sh FILES
.Pp
.Nm
does utilize the
.Pa .env
file, just like
.Xr td 8 .
Consult that page for the specifics of the
.Pa .env
file.
.Sh SEE ALSO
.Xr td 8

View File

@ -1,251 +0,0 @@
.Dd $Mdocdate: November 7 2022 $
.Dt TD 8
.Os Telodendria Project
.Sh NAME
.Nm td
.Nd Telodendria build script
.Sh SYNOPSIS
.Nm
.Op recipe
.Sh DESCRIPTION
Telodendria uses a custom build script called
.Nm .
The
.Nm
script is not only a build script, however. It does all kinds of
cool things like format the source code, and generate patch files.
.Nm
is the only supported way to develop Telodendria.
.sp
I opted to write a custom build script instead of just writing a
.Pa Makefile
because I felt that there is really no way to make a truly portable
.Pa Makefile
that could do everything I wanted, with the flexibility I wanted. I
was doing a lot of research on the differences between the GNU and BSD
versions, and I felt it just wasn't worth trying to reconsile the two
when I could write a small and relatively robust POSIX shell script that
would run on both GNU and BSD systems without any problems. I also
think that shell scripts are a lot easier to read than complex
.Pa Makefiles.
They're easier to follow because they're not so cryptic.
.sp
The
.Nm
script is fairly intuitive. It operates somewhat like
.Xr make 1
in that it has recipes that you specify on the command line. To start
using it, just run the following command in the Telodendria source
directory:
.Bd -literal -offset indent
$ . tools/env.sh
.Ed
.sp
.Sy Note:
You will have to run the above command every time you start a new
terminal session, as nothing is persisted to your system. I believe in
non-invasive, fully self-contained tooling, so it is up to you to hook the
Telodendria tools into your environment as you see fit if you want them to
persist.
.sp
If you're going to be submitting patches, you should also configure a
.Pa .env
file in the project directory root, which
.Nm
will include automatically for you. See
.Em FILES
and
.Em ENVIRONMENT .
.sp
Telodendria is designed to be light enough that it can be built from source
on just about any operating system. It only requires an ANSI C compiler and a
standard POSIX environment. To build the Telodendria binary, run
.Nm
with no arguments, or with the
.Pa build
recipe. This will produce
.Pa build/telodendria ,
which you can then install to your system and run as a daemon.
.sp
A complete list of recipes is below. Note that you can run multiple recipes
with a single invocation of
.Nm ,
but recipes are run unconditionally; that is, even if a recipe fails, all the
following recipes are still executed.
.Bl -tag
.It build
Build the source code and generate the output binary. This is the default recipe,
which means it runs if no other recipes are specified. This recipe is incremental;
it only rebuilds sources that have been modifed since the last build, making
subsequent builds faster.
.It run
Run the build binary with the development configuration in the
.Pa contrib/
directory. This recipe is used for quick testing during development. It is
.Sy not
the recommended way to run Telodendria in a production environment; it should only
be used for development.
.It clean
Remove the
.Pa build/
directory and any ephemeral files in the source tree, such as
.Pa .orig
files. The build recipe does not place anything outside of
.Pa build/ ,
so you can usually just delete that directory and get the same effect.
.It install
Install Telodendria to the system. This recipe assumes you're running a
Unix-like system.
.It uninstall
Uninstall Telodendria from the system if it was installed with the install
recipe.
.It format
Make sure the source code copyright headers are up to date, and format the code
using the system's
.Xr indent 1 .
This should be run before generating patch files, to ensure that the code follows
the project conventions. Note that the provided
.Pa .indent.pro
assumes an OpenBSD indent, which may cause the GNU implementation to choke. In
that case, don't send patch files with totally different formatting; just submit
the patch as-is and they will get formatted before committing.
.It test
Run all of the unit tests and report the results. It is highly recommended to
ensure that all the tests pass before submitting a patch, because patches that
break the tests are likely to be rejected.
.It site
Deploy the Telodendria website by generating HTML files for the documentation,
and copying them along with the front page to the specified web root. This is
used to deploy the official website, but it could be used to deploy a local
development site as necessary. See
.Em ENVIRONMENT .
.It release
Generate a release tarball, checksum and sign it, and push it to the web root.
See the relevant environment variables below.
.It patch
Generate a formatted patch file. The Telodendria project isn't super picky about
how patches look as long as they apply cleanly, but this recipe generates patches
in the format we like them, and is therefore recommended. It makes patches easy
to read. This recipe will use your configured editor to open your formatted patch
so you can review and edit it as necessary before sending it off.
.It diff
Generate a temporary preview patch that is opened in the system pager. This can
be used for quickly quickly previewing your changes and the patch file you'll
be creating.
.El
.sp
.Sh ENVIRONMENT
Any of the following environment variables are used in the build recipes.
They can all be specified in your shell when invoking
.Nm ,
or they can be placed in a
.Pa .env
file. For most of these variables, if you would like to append or prepend
to the default values, do so in the
.Pa .env
file, which is sourced after the defaults are set, allowing you to reference
the default values in your new value.
.Bl -tag
.It Ev CC
The C compiler to use. This defaults to
.Pa cc,
which is usually a symlink to your system's preferred compiler. If for some
reason you want to use a diferent compiler, do so with this environment
variable.
.It Ev PREFIX
When installing/uninstalling Telodendria, the systeme prefix to use. This
defaults to
.Pa /usr/local .
.It Ev CFLAGS
The compiler flags used to generate object files.
.Nm
comes with reasonable defaults that shouldn't need to be changed in most
scenarios, but if you do need to change the compiler flags, you can do
so with this environment variable.
.It Ev LDFLAGS
The compiler flags used to link the object files to create an output
binary.
.Nm
comes with reasonable defaults that shouldn't need to be changed in most
scenarios, but if you need to change the linker flags, you do so with this
environment variable.
.It Ev PROG
The name of the output binary. This defaults to
.Pa build/telodendria.
.It Ev DEFINES
Global preprocessor definitions to append to
.Ev CFLAGS.
This is just kept separate to keep things organized.
.It Ev INCLUDES
Header directories to make available. This is appended to
.Ev CFLAGS,
it is just kept separate to keep things organized.
.It Ev DEBUG
If set to "1", append some debug flags to
.Ev CFLAGS
and whipe out any
.Ev LDFLAGS
that awould cause the output binary to be optimized in any way. This also
depends "-debug" to
.Ev PROG .
.It Ev TELODENDRIA_VERSION
This variable does make its way into the output binary, but it is primarily
used for generating and publishing releases. This variable affects the
.Sy release
recipe.
.It Ev TELODENDRIA_PUB
The web root where the Telodendria website lives. This is where the site
is pushed to, and where generated releases go.
.It Ev PATCHSET
This variable restricts the files that
.Nm
operates on when generating patches or diffs. If you only want to generate
a diff or patch for a certain file, directory, or collection of files and
directories, set this variable to those files and directories, separated
by a space. You can mix files and directories as necessary.
.It Ev MXID
Your Matrix ID in standard format. This is used when generating patches,
so that you can be assigned credit for your patches, as well as be contacted
about your patches.
.Nm
will automatically deduce this from your system, but it will most
likely get it wrong. Please make sure you are reachable at this ID.
.It Ev DISPLAY_NAME
The display name you want to appear on your patches. This should probably
match your Matrix display name, although it doesn't necessarily have to.
.Nm
will deduce this from your system, and if you set it up properly, you may
not even have to set this variable. If
.Nm
gets it wrong, this allows you to override your display name.
.It Ev EDITOR
Your preferred editor for writing patch file descriptions. This can be a
GUI or terminal editor. If unset, this defaults to the system's
.Xr vi 1
editor.
.It Ev PAGER
Your preferred pager for previewing patches. If left unset, this defaults
to
.Xr less 1 .
.Sh FILES
.Bl -tag
.It Pa .env
An environment file that contains lines in the form of
.Pa VARIABLE=value
with environment variables to set in the
.Nm
script. See
.Em ENVIRONMENT .
Note that
.Nm
simply sources this file, which means that any shell code in it will be
executed each time
.Nm
is invoked.
.Sh EXIT STATUS
.Sh HISTORY
.Sh BUGS
.Nm
unconditionally exits with code 0, even if errors occurred. Furthermore,
recipes are run unconditionally, regardless of whether or not any recipes
before have failed.

View File

@ -1,57 +0,0 @@
.Dd $Mdocdate: September 30 2022 $
.Dt TELODENDRIA 8
.Os Telodendria Project
.Sh NAME
.Nm telodendria
.Nd Matrix homeserver daemon
.Sh SYNOPSIS
.Nm
.Op Fl nVv
.Op Fl f Ar file
.Sh DESCRIPTION
.Nm
is a Matrix homeserver written entirely from scratch in ANSI C.
It is designed to be lightweight and simple, yet functional.
.sp
The options are as follows:
.Bl -tag -width Ds
.It Fl f Ar file
Specify an alternate configuration file. The default is
.Pa /etc/telodendria.conf .
.It Fl n
Configtest mode. Only check the configuration file for validity.
.It Fl V
Only print the version information header.
.It Fl v
Verbose mode. This overrides the configuration file and sets the
log level to
.Em LOG_DEBUG
.El
.Sh ENVIRONMENT
.Nm
does not read any environment variables. All configuration should
be done via the configuration file.
.Sh FILES
Just the configuration file and the data directory; see
.Xr telodendria.conf 5
for more details.
.El
.Sh EXIT STATUS
.Nm
exits with a non-0 exit code if the configuration file is invalid, or
one or more required paths is inaccessible.
.Nm
will print an error to the log and then terminate abnormally.
.Pp
.Nm
exits with a code of 0 if the configuration file is valid, all
paths and files required are accessible, and the HTTP listener starts
as intended. If
.Nm
is sent a signal that it catches after it begins servicing requests, it
will still exit with a code of 0 after it safely shuts down, because
the bootstrap process completed successfully, and by all accounts,
it ran normally and exitted normally.
.Sh SEE ALSO
.Xr telodendria 7 ,
.Xr telodendria.conf 5

View File

@ -1,283 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Jordan Bancino">
<meta name="description"
content="Telodendria, a Matrix homeserver written in ANSI C.">
<meta property="og:title"
content="Telodendria | A Matrix Homeserver written in ANSI C.">
<meta property="og:type" content="website">
<meta property="og:url"
content="https://telodendria.io">
<meta property="og:description"
content="Telodendria, a Matrix homeserver written in ANSI C.">
<link rel="stylesheet" href="style.css">
<link rel="icon" href="assets/Telodendria-196x196.png">
<title>Telodendria | A Matrix Homeserver written in ANSI C.</title>
</head>
<body>
<img id="logo" src="/assets/Telodendria-500x500.png" alt="Telodendria Logo"/>
<h1 style="font-size: x-large;" id="telodendria">Telodendria</h1>
<p>
<b>Tel-&#601;-'den-dr&#275;-&#601;:</b> The terminal aborizations of an axon.
</p>
<p>
<b>Telodendria</b> is an open source Matrix homeserver implementation written from
scratch in ANSI C and designed to be lightweight and simple, yet
functional.
</p>
<div class="msg-error">
<b><i>Note:</i></b> <b>Telodendria</b> is under <i>heavy</i> development and is not
yet ready for use.
Please see the <a href="/man/man7/telodendria-changelog.7.html#PROJECT_STATUS">Project Status</a>
for information about the project state, and use the links below to help fund development.
</div>
<h2 id="donate">Donate</h2>
<p>
If you would like to donate to this project, you can do so with the
following links:
</p>
<ul>
<li><a href="https://liberapay.com/Telodendria/donate">LiberaPay</a> (recurring, account required)</li>
<li><a href="https://donate.stripe.com/8wM29AfF5bRJc48eUU">Stripe</a> (one-time, no account required)</li>
</ul>
<p>
If you would like to do a recurring donation larger than what's allowed
by LiberaPay, please contact me directly on Matrix at <code>@jordan:bancino.net</code>.
</p>
<h2 id="download">Download</h2>
<p>
<b>Telodendria</b> is distributed as source tarballs, in true Unix
fashion. If you want, you can verify the checksum of your download,
and check the signature. To check the signature, you'll need
<code>signify</code>, and the signify public key:
<a href="/telodendria-signify.pub">
telodendria-signify.pub</a>.
</p>
<table>
<tr>
<th>Version</th>
<th>Download</th>
<th>Checksum</th>
<th>Signature</th>
</tr>
<tr>
<td>v0.1.0</td>
<td>
<a href="/pub/v0.1.0/Telodendria-v0.1.0.tar.gz">
Telodendria-v0.1.0.tar.gz
</a>
</td>
<td>
<a href="/pub/v0.1.0/Telodendria-v0.1.0.tar.gz.sha256">
SHA256
</a>
</td>
<td>
<a href="/pub/v0.1.0/Telodendria-v0.1.0.tar.gz.sig">
Signify
</a>
</td>
</tr>
</table>
<p>
See the <a href="man/man7/telodendria-changelog.7.html">change log</a> for
release notes. If you are looking for older <b>Telodendria</b> versions, you
can find them <a href="/pub">here</a>.
</p>
<p>
If your operating system has an official package or port of
<b>Telodendria</b>, you should prefer to use that instead of manually
downloading the source and building it. Consult your operating system's
manual for how to install packages, as well as the official repository,
to see if a package is available. If your operating system's
package or port is too out of date for your tastes, please contact
the package's maintainers to notify them, or offer to update the
package yourself.
</p>
<p>
If your operating system does <i>not</i> have a package or port of
<b>Telodendria</b>, please consult the
<a href="/man/man7/porting.7.html">porting(7)</a> page for guidelines
related to packaging <b>Telodendria</b> for your system.
</p>
<h2 id="documentation">Documentation</h2>
<p>
<b>Telodendria</b>'s documentation is distributed with the source
code as <code>man</code> pages, which contain all of the information
on what <b>Telodendria</b> is, what its goals are, how to build the source,
configure it, as well as contribute to the project. The <code>man</code>
pages are also available online for convenience:
</p>
<p>User Documentation:</p>
<table>
<tr>
<th>Man Page</th>
<th>Description</th>
</tr>
<tr>
<td><a href="man/man7/telodendria.7.html">telodendria(7)</a></td>
<td>
<b>Start here.</b> This page contains the project introduction, and provides
information about it, such as its status, how to contact the developers, and what
the source code license is.
</td>
</tr>
<tr>
<td><a href="man/man8/telodendria.8.html">telodendria(8)</a></td>
<td>
Command line usage for <b>Telodendria</b> administrators.
</td>
</tr>
<tr>
<td><a href="man/man5/telodendria.conf.5.html">telodendria.conf(5)</a></td>
<td>
Configuration file options.
</td>
</tr>
<tr>
<td><a href="man/man7/telodendria-contributing.7.html">telodendria-contributing(7)</a></td>
<td>
Contributing guide.
</td>
</tr>
<tr>
<td><a href="man/man7/telodendria-changelog.7.html">telodendria-changelog(7)</a></td>
<td>
The change log for the Telodendria project.
</td>
</tr>
</table>
<p>
<details>
<summary>Developer Documentation:</summary>
</p>
<table>
<tr>
<th>Man Page</th>
<th>Description</th>
</tr>
<tr>
<td><a href="man/man7/porting.7.html">porting(7)</a></td>
<td>
Want to package Telodendria for your operating system?
Look no further than this page.
</td>
</tr>
<tr>
<td><a href="man/man8/td.8.html">td(8)</a></td>
<td>
Build script and patch generation instructions.
</td>
</tr>
<tr>
<td><a href="man/man8/send-patch.8.html">send-patch(8)</a></td>
<td>
A simple script to send patch files to the Telodendria project.
</td>
</tr>
<tr>
<td><a href="man/man3/Array.3.html">Array(3)</a></td>
<td>
Dynamically-sized array API.
</td>
</tr>
<tr>
<td><a href="man/man3/Base64.3.html">Base64(3)</a></td>
<td>
Base64 implementation with Matrix's "unpadded base64" support.
</td>
</tr>
<tr>
<td><a href="man/man3/HashMap.3.html">HashMap(3)</a></td>
<td>
A simple hash map implementation.
</td>
</tr>
<tr>
<td><a href="man/man3/Queue.3.html">Queue(3)</a></td>
<td>
Basic fixed-size circular queue implementation.
</td>
</tr>
<tr>
<td><a href="man/man3/Log.3.html">Log(3)</a></td>
<td>
Logging framework used to log messages in Telodendria.
</td>
</tr>
<tr>
<td><a href="man/man3/Util.3.html">Util(3)</a></td>
<td>
Misc utility functions that don't need their own header.
</td>
</tr>
<tr>
<td><a href="man/man3/Db.3.html">Db(3)</a></td>
<td>
A minimal flat-file database with object locking and an efficient
cache.
</td>
</tr>
<tr>
<td><a href="man/man3/Memory.3.html">Memory(3)</a></td>
<td>
Smart memory management API.
</td>
</tr>
<tr>
<td><a href="man/man3/Json.3.html">Json(3)</a></td>
<td>
A feature-complete API for reading and writing JSON.
</td>
</tr>
<tr>
<td><a href="man/man3/CanonicalJson.3.html">CanonicalJson(3)</a></td>
<td>
An extension to the Json API that implements Matrix's canonical JSON.
</td>
</tr>
<tr>
<td><a href="man/man3/TelodendriaConfig.3.html">TelodendriaConfig(3)</a></td>
<td>
Parse the configuration file into a structure.
</td>
</tr>
<tr>
<td><a href="man/man3/Matrix.3.html">Matrix(3)</a></td>
<td>
Functions for writing Matrix API endpoints.
</td>
</tr>
<tr>
<td><a href="man/man3/Routes.3.html">Routes(3)</a></td>
<td>
Matrix API endpoint abstractions.
</td>
</tr>
<tr>
<td><a href="man/man3/Http.3.html">Http(3)</a></td>
<td>
Encode and decode various parts of the HTTP protocol.
</td>
</tr>
<tr>
<td><a href="man/man3/HttpServer.3.html">HttpServer(3)</a></td>
<td>
Extremely simple HTTP server.
</td>
</tr>
</table>
</details>
<hr>
<p>
&copy; 2022 Jordan Bancino &lt;@jordan:bancino.net&gt;
</p>
</body>
</html>

View File

@ -1,368 +0,0 @@
/* $OpenBSD: mandoc.css,v 1.39 2022/07/06 14:27:55 schwarze Exp $ */
/*
* Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
*
* Written by Ingo Schwarze <schwarze@openbsd.org>.
* I place this file into the public domain.
* Permission to use, copy, modify, and distribute it for any purpose
* with or without fee is hereby granted, without any conditions.
*/
/* Global defaults. */
html { max-width: 65em;
--bg: #FFFFFF;
--fg: #000000; }
body { background: var(--bg);
color: var(--fg);
font-family: Helvetica,Arial,sans-serif; }
h1, h2 { font-size: 110%; }
table { margin-top: 0em;
margin-bottom: 0em;
border-collapse: collapse; }
/* Some browsers set border-color in a browser style for tbody,
* but not for table, resulting in inconsistent border styling. */
tbody { border-color: inherit; }
tr { border-color: inherit; }
td { vertical-align: top;
padding-left: 0.2em;
padding-right: 0.2em;
border-color: inherit; }
ul, ol, dl { margin-top: 0em;
margin-bottom: 0em; }
li, dt { margin-top: 1em; }
pre { font-family: inherit; }
.permalink { border-bottom: thin dotted;
color: inherit;
font: inherit;
text-decoration: inherit; }
* { clear: both }
/* Search form and search results. */
fieldset { border: thin solid silver;
border-radius: 1em;
text-align: center; }
input[name=expr] {
width: 25%; }
table.results { margin-top: 1em;
margin-left: 2em;
font-size: smaller; }
/* Header and footer lines. */
div[role=doc-pageheader] {
display: flex;
border-bottom: 1px dotted #808080;
margin-bottom: 1em;
font-size: smaller; }
.head-ltitle { flex: 1; }
.head-vol { flex: 0 1 auto;
text-align: center; }
.head-rtitle { flex: 1;
text-align: right; }
div[role=doc-pagefooter] {
display: flex;
justify-content: space-between;
border-top: 1px dotted #808080;
margin-top: 1em;
font-size: smaller; }
.foot-left { flex: 1; }
.foot-date { flex: 0 1 auto;
text-align: center; }
.foot-os { flex: 1;
text-align: right; }
/* Sections and paragraphs. */
main { margin-left: 3.8em; }
.Nd { }
section.Sh { }
h2.Sh { margin-top: 1.2em;
margin-bottom: 0.6em;
margin-left: -3.2em; }
section.Ss { }
h3.Ss { margin-top: 1.2em;
margin-bottom: 0.6em;
margin-left: -1.2em;
font-size: 105%; }
.Pp { margin: 0.6em 0em; }
.Sx { }
.Xr { }
/* Displays and lists. */
.Bd { }
.Bd-indent { margin-left: 3.8em; }
.Bl-bullet { list-style-type: disc;
padding-left: 1em; }
.Bl-bullet > li { }
.Bl-dash { list-style-type: none;
padding-left: 0em; }
.Bl-dash > li:before {
content: "\2014 "; }
.Bl-item { list-style-type: none;
padding-left: 0em; }
.Bl-item > li { }
.Bl-compact > li {
margin-top: 0em; }
.Bl-enum { padding-left: 2em; }
.Bl-enum > li { }
.Bl-compact > li {
margin-top: 0em; }
.Bl-diag { }
.Bl-diag > dt {
font-style: normal;
font-weight: bold; }
.Bl-diag > dd {
margin-left: 0em; }
.Bl-hang { }
.Bl-hang > dt { }
.Bl-hang > dd {
margin-left: 5.5em; }
.Bl-inset { }
.Bl-inset > dt { }
.Bl-inset > dd {
margin-left: 0em; }
.Bl-ohang { }
.Bl-ohang > dt { }
.Bl-ohang > dd {
margin-left: 0em; }
.Bl-tag { margin-top: 0.6em;
margin-left: 5.5em; }
.Bl-tag > dt {
float: left;
margin-top: 0em;
margin-left: -5.5em;
padding-right: 0.5em;
vertical-align: top; }
.Bl-tag > dd {
clear: right;
column-count: 1; /* Force block formatting context. */
width: 100%;
margin-top: 0em;
margin-left: 0em;
margin-bottom: 0.6em;
vertical-align: top; }
.Bl-compact { margin-top: 0em; }
.Bl-compact > dd {
margin-bottom: 0em; }
.Bl-compact > dt {
margin-top: 0em; }
.Bl-column { }
.Bl-column > tbody > tr { }
.Bl-column > tbody > tr > td {
margin-top: 1em; }
.Bl-compact > tbody > tr > td {
margin-top: 0em; }
.Rs { font-style: normal;
font-weight: normal; }
.RsA { }
.RsB { font-style: italic;
font-weight: normal; }
.RsC { }
.RsD { }
.RsI { font-style: italic;
font-weight: normal; }
.RsJ { font-style: italic;
font-weight: normal; }
.RsN { }
.RsO { }
.RsP { }
.RsQ { }
.RsR { }
.RsT { text-decoration: underline; }
.RsU { }
.RsV { }
.eqn { }
.tbl td { vertical-align: middle; }
.HP { margin-left: 3.8em;
text-indent: -3.8em; }
/* Semantic markup for command line utilities. */
table.Nm { }
code.Nm { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Fl { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Cm { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Ar { font-style: italic;
font-weight: normal; }
.Op { display: inline flow; }
.Ic { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Ev { font-style: normal;
font-weight: normal;
font-family: monospace; }
.Pa { font-style: italic;
font-weight: normal; }
/* Semantic markup for function libraries. */
.Lb { }
code.In { font-style: normal;
font-weight: bold;
font-family: inherit; }
a.In { }
.Fd { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Ft { font-style: italic;
font-weight: normal; }
.Fn { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Fa { font-style: italic;
font-weight: normal; }
.Vt { font-style: italic;
font-weight: normal; }
.Va { font-style: italic;
font-weight: normal; }
.Dv { font-style: normal;
font-weight: normal;
font-family: monospace; }
.Er { font-style: normal;
font-weight: normal;
font-family: monospace; }
/* Various semantic markup. */
.An { }
.Lk { }
.Mt { }
.Cd { font-style: normal;
font-weight: bold;
font-family: inherit; }
.Ad { font-style: italic;
font-weight: normal; }
.Ms { font-style: normal;
font-weight: bold; }
.St { }
.Ux { }
/* Physical markup. */
.Bf { display: inline flow; }
.No { font-style: normal;
font-weight: normal; }
.Em { font-style: italic;
font-weight: normal; }
.Sy { font-style: normal;
font-weight: bold; }
.Li { font-style: normal;
font-weight: normal;
font-family: monospace; }
/* Tooltip support. */
h2.Sh, h3.Ss { position: relative; }
.An, .Ar, .Cd, .Cm, .Dv, .Em, .Er, .Ev, .Fa, .Fd, .Fl, .Fn, .Ft,
.Ic, code.In, .Lb, .Lk, .Ms, .Mt, .Nd, code.Nm, .Pa, .Rs,
.St, .Sx, .Sy, .Va, .Vt, .Xr {
display: inline flow;
position: relative; }
.An::before { content: "An"; }
.Ar::before { content: "Ar"; }
.Cd::before { content: "Cd"; }
.Cm::before { content: "Cm"; }
.Dv::before { content: "Dv"; }
.Em::before { content: "Em"; }
.Er::before { content: "Er"; }
.Ev::before { content: "Ev"; }
.Fa::before { content: "Fa"; }
.Fd::before { content: "Fd"; }
.Fl::before { content: "Fl"; }
.Fn::before { content: "Fn"; }
.Ft::before { content: "Ft"; }
.Ic::before { content: "Ic"; }
code.In::before { content: "In"; }
.Lb::before { content: "Lb"; }
.Lk::before { content: "Lk"; }
.Ms::before { content: "Ms"; }
.Mt::before { content: "Mt"; }
.Nd::before { content: "Nd"; }
code.Nm::before { content: "Nm"; }
.Pa::before { content: "Pa"; }
.Rs::before { content: "Rs"; }
h2.Sh::before { content: "Sh"; }
h3.Ss::before { content: "Ss"; }
.St::before { content: "St"; }
.Sx::before { content: "Sx"; }
.Sy::before { content: "Sy"; }
.Va::before { content: "Va"; }
.Vt::before { content: "Vt"; }
.Xr::before { content: "Xr"; }
.An::before, .Ar::before, .Cd::before, .Cm::before,
.Dv::before, .Em::before, .Er::before, .Ev::before,
.Fa::before, .Fd::before, .Fl::before, .Fn::before, .Ft::before,
.Ic::before, code.In::before, .Lb::before, .Lk::before,
.Ms::before, .Mt::before, .Nd::before, code.Nm::before,
.Pa::before, .Rs::before,
h2.Sh::before, h3.Ss::before, .St::before, .Sx::before, .Sy::before,
.Va::before, .Vt::before, .Xr::before {
opacity: 0;
transition: .15s ease opacity;
pointer-events: none;
position: absolute;
bottom: 100%;
box-shadow: 0 0 .35em var(--fg);
padding: .15em .25em;
white-space: nowrap;
font-family: Helvetica,Arial,sans-serif;
font-style: normal;
font-weight: bold;
background: var(--bg);
color: var(--fg); }
.An:hover::before, .Ar:hover::before, .Cd:hover::before, .Cm:hover::before,
.Dv:hover::before, .Em:hover::before, .Er:hover::before, .Ev:hover::before,
.Fa:hover::before, .Fd:hover::before, .Fl:hover::before, .Fn:hover::before,
.Ft:hover::before, .Ic:hover::before, code.In:hover::before,
.Lb:hover::before, .Lk:hover::before, .Ms:hover::before, .Mt:hover::before,
.Nd:hover::before, code.Nm:hover::before, .Pa:hover::before,
.Rs:hover::before, h2.Sh:hover::before, h3.Ss:hover::before, .St:hover::before,
.Sx:hover::before, .Sy:hover::before, .Va:hover::before, .Vt:hover::before,
.Xr:hover::before {
opacity: 1;
pointer-events: inherit; }
/* Overrides to avoid excessive margins on small devices. */
@media (max-width: 37.5em) {
main { margin-left: 0.5em; }
h2.Sh, h3.Ss { margin-left: 0em; }
.Bd-indent { margin-left: 2em; }
.Bl-hang > dd {
margin-left: 2em; }
.Bl-tag { margin-left: 2em; }
.Bl-tag > dt {
margin-left: -2em; }
.HP { margin-left: 2em;
text-indent: -2em; }
}
/* Overrides for a dark color scheme for accessibility. */
@media (prefers-color-scheme: dark) {
html { --bg: #1E1F21;
--fg: #EEEFF1; }
:link { color: #BAD7FF; }
:visited { color: #F6BAFF; }
}

View File

@ -1,115 +0,0 @@
@import "mandoc.css";
:root {
--border-radius: 10px;
--color-snippet: #161b22;
--color-link: #7b8333;
--color-bg: #0d1117;
--color-text: #c9d1d9;
--color-table-border: #30363d;
--color-table-accent: #161b22;
--color-error-bg: #5c6434;
--color-error: white;
}
html {
max-width: 100%;
}
body {
margin: auto;
max-width: 8.5in;
padding: 0.25in;
color: var(--color-text);
background-color: var(--color-bg);
font-family: Arial;
}
.msg-error {
background-color: var(--color-error-bg);
color: var(--color-error);
border-radius: var(--border-radius);
padding: 20px;
}
.msg-error a {
color: var(--color-error);
font-weight: bold;
text-decoration: underline
}
.Bd {
background-color: var(--color-snippet);
border-radius: var(--border-radius);
padding-left: 10px;
overflow: auto;
}
h1 {
text-align: center;
}
h1, h4, h5, h6 {
border-bottom: 1px dashed var(--color-table-border);
}
hr {
border: 0;
border-bottom: 1px dashed var(--color-table-border);
}
h4, h5, h6 {
width: fit-content;
}
a {
color: var(--color-link);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
table {
width: 100%;
border-collapse: separate;
border-spacing: 0px;
}
td, th {
border: 1px solid var(--color-table-border);
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: var(--color-table-accent);
}
/* The above styles are nice for real tables, but not for
* a man page synopsis. */
.Nm {
width: fit-content;
}
.Nm td, .Nm th {
border: none;
}
/* Thanks Jonah! */
#logo {
display: block;
margin: auto;
width: 50vw;
max-width: 400px;
}

View File

@ -1,2 +0,0 @@
untrusted comment: signify public key
RWTPPnWvnpee8NlygSggQqk5V5oghl6Ikq99bZl5IRQwiRMLaJnq82mw

View File

@ -1,313 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Array.h>
#ifndef ARRAY_BLOCK
#define ARRAY_BLOCK 16
#endif
#include <stddef.h>
#include <Memory.h>
struct Array
{
void **entries; /* An array of void pointers, to
* store any data */
size_t allocated; /* Elements allocated on the heap */
size_t size; /* Elements actually filled */
};
int
ArrayAdd(Array * array, void *value)
{
if (!array)
{
return 0;
}
return ArrayInsert(array, value, array->size);
}
Array *
ArrayCreate(void)
{
Array *array = Malloc(sizeof(Array));
if (!array)
{
return NULL;
}
array->size = 0;
array->allocated = ARRAY_BLOCK;
array->entries = Malloc(sizeof(void *) * ARRAY_BLOCK);
if (!array->entries)
{
Free(array);
return NULL;
}
return array;
}
void *
ArrayDelete(Array * array, size_t index)
{
size_t i;
void *element;
if (!array || array->size <= index)
{
return NULL;
}
element = array->entries[index];
for (i = index; i < array->size - 1; i++)
{
array->entries[i] = array->entries[i + 1];
}
array->size--;
return element;
}
void
ArrayFree(Array * array)
{
if (array)
{
Free(array->entries);
Free(array);
}
}
void *
ArrayGet(Array * array, size_t index)
{
if (!array)
{
return NULL;
}
if (index >= array->size)
{
return NULL;
}
return array->entries[index];
}
extern int
ArrayInsert(Array * array, void *value, size_t index)
{
size_t i;
if (!array || !value || index > array->size)
{
return 0;
}
if (array->size >= array->allocated)
{
void **tmp;
size_t newSize = array->allocated + ARRAY_BLOCK;
tmp = array->entries;
array->entries = Realloc(array->entries,
sizeof(void *) * newSize);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
array->allocated = newSize;
}
for (i = array->size; i > index; i--)
{
array->entries[i] = array->entries[i - 1];
}
array->size++;
array->entries[index] = value;
return 1;
}
size_t
ArraySize(Array * array)
{
if (!array)
{
return 0;
}
return array->size;
}
int
ArrayTrim(Array * array)
{
void **tmp;
if (!array)
{
return 0;
}
tmp = array->entries;
array->entries = Realloc(array->entries,
sizeof(void *) * array->size);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
return 1;
}
static void
ArraySwap(Array * array, size_t i, size_t j)
{
void *p = array->entries[i];
array->entries[i] = array->entries[j];
array->entries[j] = p;
}
static size_t
ArrayPartition(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
{
void *pivot = array->entries[high];
size_t i = low - 1;
size_t j;
for (j = low; j <= high - 1; j++)
{
if (compare(array->entries[j], pivot) < 0)
{
i++;
ArraySwap(array, i, j);
}
}
ArraySwap(array, i + 1, high);
return i + 1;
}
static void
ArrayQuickSort(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
{
if (low < high)
{
size_t pi = ArrayPartition(array, low, high, compare);
ArrayQuickSort(array, low, pi - 1, compare);
ArrayQuickSort(array, pi + 1, high, compare);
}
}
void
ArraySort(Array * array, int (*compare) (void *, void *))
{
if (!array)
{
return;
}
ArrayQuickSort(array, 0, array->size, compare);
}
/* Even though the following operations could be done using only the
* public Array API defined above, I opted for low-level struct
* manipulation because it allows much more efficient copying; we only
* allocate what we for sure need upfront, and don't have to
* re-allocate during the operation. */
Array *
ArrayFromVarArgs(size_t n, va_list ap)
{
size_t i;
Array *arr = Malloc(sizeof(Array));
if (!arr)
{
return NULL;
}
arr->size = n;
arr->allocated = n;
arr->entries = Malloc(sizeof(void *) * arr->allocated);
if (!arr->entries)
{
Free(arr);
return NULL;
}
for (i = 0; i < n; i++)
{
arr->entries[i] = va_arg(ap, void *);
}
return arr;
}
Array *
ArrayDuplicate(Array * arr)
{
size_t i;
Array *arr2 = Malloc(sizeof(Array));
if (!arr2)
{
return NULL;
}
arr2->size = arr->size;
arr2->allocated = arr->size;
arr2->entries = Malloc(sizeof(void *) * arr->allocated);
if (!arr2->entries)
{
Free(arr2);
return NULL;
}
for (i = 0; i < arr2->size; i++)
{
arr2->entries[i] = arr->entries[i];
}
return arr2;
}

View File

@ -1,244 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Base64.h>
#include <Memory.h>
static const char Base64EncodeMap[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int Base64DecodeMap[] = {
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51
};
size_t
Base64EncodedSize(size_t inputSize)
{
size_t size = inputSize;
if (inputSize % 3)
{
size += 3 - (inputSize % 3);
}
size /= 3;
size *= 4;
return size;
}
size_t
Base64DecodedSize(const char *base64, size_t len)
{
size_t ret;
size_t i;
if (!base64)
{
return 0;
}
ret = len / 4 * 3;
for (i = len; i > 0; i--)
{
if (base64[i] == '=')
{
ret--;
}
else
{
break;
}
}
return ret;
}
char *
Base64Encode(const char *input, size_t len)
{
char *out;
size_t outLen;
size_t i, j, v;
if (!input || !len)
{
return NULL;
}
outLen = Base64EncodedSize(len);
out = Malloc(outLen + 1);
if (!out)
{
return NULL;
}
out[outLen] = '\0';
for (i = 0, j = 0; i < len; i += 3, j += 4)
{
v = input[i];
v = i + 1 < len ? v << 8 | input[i + 1] : v << 8;
v = i + 2 < len ? v << 8 | input[i + 2] : v << 8;
out[j] = Base64EncodeMap[(v >> 18) & 0x3F];
out[j + 1] = Base64EncodeMap[(v >> 12) & 0x3F];
if (i + 1 < len)
{
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
}
else
{
out[j + 2] = '=';
}
if (i + 2 < len)
{
out[j + 3] = Base64EncodeMap[v & 0x3F];
}
else
{
out[j + 3] = '=';
}
}
return out;
}
static int
Base64IsValidChar(char c)
{
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c == '+') ||
(c == '/') ||
(c == '=');
}
char *
Base64Decode(const char *input, size_t len)
{
size_t i, j;
int v;
size_t outLen;
char *out;
if (!input)
{
return NULL;
}
outLen = Base64DecodedSize(input, len);
if (len % 4)
{
/* Invalid length; must have incorrect padding */
return NULL;
}
/* Scan for invalid characters. */
for (i = 0; i < len; i++)
{
if (!Base64IsValidChar(input[i]))
{
return NULL;
}
}
out = Malloc(outLen + 1);
if (!out)
{
return NULL;
}
out[outLen] = '\0';
for (i = 0, j = 0; i < len; i += 4, j += 3)
{
v = Base64DecodeMap[input[i] - 43];
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
v = input[i + 3] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 3] - 43];
out[j] = (v >> 16) & 0xFF;
if (input[i + 2] != '=')
out[j + 1] = (v >> 8) & 0xFF;
if (input[i + 3] != '=')
out[j + 2] = v & 0xFF;
}
return out;
}
extern void
Base64Unpad(char *base64, size_t length)
{
if (!base64)
{
return;
}
while (base64[length - 1] == '=')
{
length--;
}
base64[length] = '\0';
}
extern int
Base64Pad(char **base64Ptr, size_t length)
{
char *tmp;
size_t newSize;
size_t i;
if (length % 4 == 0)
{
return length; /* Success: no padding needed */
}
newSize = length + (4 - (length % 4));
tmp = Realloc(*base64Ptr, newSize + 100);;
if (!tmp)
{
return 0; /* Memory error */
}
*base64Ptr = tmp;
for (i = length; i < newSize; i++)
{
(*base64Ptr)[i] = '=';
}
(*base64Ptr)[newSize] = '\0';
return newSize;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
@ -23,36 +24,39 @@
*/
#include <CanonicalJson.h>
#include <HashMap.h>
#include <Array.h>
#include <Json.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Json.h>
#include <stdio.h>
#include <string.h>
int
static int
CanonicalJsonKeyCompare(void *k1, void *k2)
{
return strcmp((char *) k1, (char *) k2);
}
static void
CanonicalJsonEncodeValue(JsonValue * value, FILE * out)
int
CanonicalJsonEncodeValue(JsonValue * value, Stream * out)
{
Array *arr;
size_t i, len;
int length = 0;
/* Override object type to encode using the canonical functions */
switch (JsonValueType(value))
{
case JSON_OBJECT:
CanonicalJsonEncode(JsonValueAsObject(value), out);
length += CanonicalJsonEncode(JsonValueAsObject(value), out);
break;
case JSON_ARRAY:
arr = JsonValueAsArray(value);
len = ArraySize(arr);
fputc('[', out);
StreamPutc(out, '[');
length++;
for (i = 0; i < len; i++)
{
@ -64,39 +68,44 @@ CanonicalJsonEncodeValue(JsonValue * value, FILE * out)
continue;
}
CanonicalJsonEncodeValue(aVal, out);
length += CanonicalJsonEncodeValue(aVal, out);
if (i < len - 1)
{
fputc(',', out);
StreamPutc(out, ',');
length++;
}
}
fputc(']', out);
StreamPutc(out, ']');
length++;
break;
default:
JsonEncodeValue(value, out);
length += JsonEncodeValue(value, out, JSON_DEFAULT);
break;
}
return length;
}
int
CanonicalJsonEncode(HashMap * object, FILE * out)
CanonicalJsonEncode(HashMap * object, Stream * out)
{
char *key;
JsonValue *value;
Array *keys;
size_t i;
size_t keyCount;
int length;
if (!object || !out)
if (!object)
{
return 0;
return -1;
}
keys = ArrayCreate();
if (!keys)
{
return 0;
return -1;
}
while (HashMapIterate(object, &key, (void **) &value))
@ -106,7 +115,11 @@ CanonicalJsonEncode(HashMap * object, FILE * out)
ArraySort(keys, CanonicalJsonKeyCompare);
fputc('{', out);
/* The total number of bytes written */
length = 0;
StreamPutc(out, '{');
length++;
keyCount = ArraySize(keys);
for (i = 0; i < keyCount; i++)
@ -128,18 +141,21 @@ CanonicalJsonEncode(HashMap * object, FILE * out)
continue;
}
JsonEncodeString(key, out);
fputc(':', out);
CanonicalJsonEncodeValue(value, out);
length += JsonEncodeString(key, out);
StreamPutc(out, ':');
length++;
length += CanonicalJsonEncodeValue(value, out);
if (i < keyCount - 1)
{
fputc(',', out);
StreamPutc(out, ',');
length++;
}
}
fputc('}', out);
StreamPutc(out, '}');
length++;
ArrayFree(keys);
return 1;
return length;
}

232
src/Config.c Normal file
View File

@ -0,0 +1,232 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Schema/Config.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Db.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Util.h>
#include <sys/types.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <grp.h>
#include <pwd.h>
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
#endif
void
ConfigParse(HashMap * config, Config *tConfig)
{
size_t i;
if (!config)
{
tConfig->ok = 0;
tConfig->err = "Invalid object given as config.";
return;
}
memset(tConfig, 0, sizeof(Config));
tConfig->maxCache = 0;
if (!ConfigFromJson(config, tConfig, &tConfig->err))
{
ConfigFree(tConfig);
goto error;
}
if (!tConfig->baseUrl)
{
size_t len = strlen(tConfig->serverName) + 10;
tConfig->baseUrl = Malloc(len);
if (!tConfig->baseUrl)
{
tConfig->err = "Couldn't allocate enough memory for 'baseUrl'.";
goto error;
}
snprintf(tConfig->baseUrl, len, "https://%s/", tConfig->serverName);
}
if (!tConfig->log.timestampFormat)
{
tConfig->log.timestampFormat = StrDuplicate("default");
}
for (i = 0; i < ArraySize(tConfig->listen); i++)
{
ConfigListener *listener = ArrayGet(tConfig->listen, i);
if (!listener->maxConnections)
{
listener->maxConnections = 32;
}
if (!listener->threads)
{
listener->threads = 4;
}
if (!listener->port)
{
listener->port = 8008;
}
}
tConfig->ok = 1;
tConfig->err = NULL;
return;
error:
tConfig->ok = 0;
return;
}
int
ConfigExists(Db * db)
{
return DbExists(db, 1, "config");
}
int
ConfigCreateDefault(Db * db)
{
Config config;
ConfigListener *listener;
HashMap *json;
JsonValue *val;
DbRef *ref;
size_t len;
memset(&config, 0, sizeof(Config));
config.log.output = CONFIG_LOG_OUTPUT_FILE;
config.runAs.gid = StrDuplicate(getgrgid(getgid())->gr_name);
config.runAs.uid = StrDuplicate(getpwuid(getuid())->pw_name);
config.registration = 0;
config.federation = 1;
/* Create serverName and baseUrl. */
config.serverName = Malloc(HOST_NAME_MAX + 1);
memset(config.serverName, 0, HOST_NAME_MAX + 1);
gethostname(config.serverName, HOST_NAME_MAX);
len = strlen(config.serverName) + 10;
config.baseUrl = Malloc(len);
snprintf(config.baseUrl, len, "https://%s/", config.serverName);
/* Add simple listener without TLS. */
config.listen = ArrayCreate();
listener = Malloc(sizeof(ConfigListener));
listener->maxConnections = 32;
listener->port = 8008;
listener->threads = 4;
ArrayAdd(config.listen, listener);
/* Write it all out to the configuration file. */
json = ConfigToJson(&config);
val = JsonGet(json, 1, "listen");
val = ArrayGet(JsonValueAsArray(val), 0);
JsonValueFree(HashMapDelete(JsonValueAsObject(val), "tls"));
ref = DbCreate(db, 1, "config");
if (!ref)
{
ConfigFree(&config);
return 0;
}
DbJsonSet(ref, json);
DbUnlock(db, ref);
ConfigFree(&config);
JsonFree(json);
return 1;
}
void
ConfigLock(Db * db, Config *config)
{
DbRef *ref = DbLock(db, 1, "config");
if (!ref)
{
config->ok = 0;
config->err = "Couldn't lock configuration.";
}
ConfigParse(DbJson(ref), config);
if (config->ok)
{
config->db = db;
config->ref = ref;
}
}
int
ConfigUnlock(Config *config)
{
Db *db;
DbRef *dbRef;
if (!config->ok)
{
return 0;
}
db = config->db;
dbRef = config->ref;
ConfigFree(config);
config->ok = 0;
return DbUnlock(db, dbRef);
}
int
ConfigLogLevelToSyslog(ConfigLogLevel level)
{
switch (level)
{
case CONFIG_LOG_LEVEL_NOTICE:
return LOG_NOTICE;
case CONFIG_LOG_LEVEL_ERROR:
return LOG_ERR;
case CONFIG_LOG_LEVEL_MESSAGE:
return LOG_INFO;
case CONFIG_LOG_LEVEL_DEBUG:
return LOG_DEBUG;
case CONFIG_LOG_LEVEL_WARNING:
return LOG_WARNING;
}
return LOG_INFO;
}

604
src/Db.c
View File

@ -1,604 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Db.h>
#include <Memory.h>
#include <Json.h>
#include <Util.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
struct Db
{
char *dir;
pthread_mutex_t lock;
size_t cacheSize;
size_t maxCache;
HashMap *cache;
DbRef *mostRecent;
DbRef *leastRecent;
};
struct DbRef
{
HashMap *json;
pthread_mutex_t lock;
unsigned long ts;
size_t size;
Array *name;
DbRef *prev;
DbRef *next;
FILE *fp;
};
static ssize_t DbComputeSize(HashMap *);
static ssize_t
DbComputeSizeOfValue(JsonValue * val)
{
MemoryInfo *a;
ssize_t total = 0;
size_t i;
union
{
char *str;
Array *arr;
} u;
if (!val)
{
return -1;
}
a = MemoryInfoGet(val);
if (a)
{
total += MemoryInfoGetSize(a);
}
switch (JsonValueType(val))
{
case JSON_OBJECT:
total += DbComputeSize(JsonValueAsObject(val));
break;
case JSON_ARRAY:
u.arr = JsonValueAsArray(val);
a = MemoryInfoGet(u.arr);
if (a)
{
total += MemoryInfoGetSize(a);
}
for (i = 0; i < ArraySize(u.arr); i++)
{
total += DbComputeSizeOfValue(ArrayGet(u.arr, i));
}
break;
case JSON_STRING:
u.str = JsonValueAsString(val);
a = MemoryInfoGet(u.str);
if (a)
{
total += MemoryInfoGetSize(a);
}
break;
case JSON_NULL:
case JSON_INTEGER:
case JSON_FLOAT:
case JSON_BOOLEAN:
default:
/* These don't use any extra heap space */
break;
}
return total;
}
static ssize_t
DbComputeSize(HashMap * json)
{
char *key;
JsonValue *val;
MemoryInfo *a;
size_t total;
if (!json)
{
return -1;
}
total = 0;
a = MemoryInfoGet(json);
if (a)
{
total += MemoryInfoGetSize(a);
}
while (HashMapIterate(json, &key, (void **) &val))
{
a = MemoryInfoGet(key);
if (a)
{
total += MemoryInfoGetSize(a);
}
total += DbComputeSizeOfValue(val);
}
return total;
}
static char *
DbHashKey(Array * args)
{
size_t i;
char *str = NULL;
for (i = 0; i < ArraySize(args); i++)
{
char *tmp = UtilStringConcat(str, ArrayGet(args, i));
Free(str);
str = tmp;
}
return str;
}
static char *
DbDirName(Db * db, Array * args)
{
size_t i;
char *str = UtilStringConcat(db->dir, "/");
for (i = 0; i < ArraySize(args) - 1; i++)
{
char *tmp, *tmp2;
tmp = UtilStringConcat(str, ArrayGet(args, i));
tmp2 = UtilStringConcat(tmp, "/");
Free(str);
Free(tmp);
str = tmp2;
}
return str;
}
static char *
DbFileName(Db * db, Array * args)
{
size_t i;
char *str = UtilStringConcat(db->dir, "/");
for (i = 0; i < ArraySize(args); i++)
{
char *tmp, *tmp2;
tmp = UtilStringConcat(str, ArrayGet(args, i));
tmp2 = UtilStringConcat(tmp, (i < ArraySize(args) - 1) ? "/" : ".json");
Free(str);
Free(tmp);
str = tmp2;
}
return str;
}
static void
DbCacheEvict(Db * db)
{
DbRef *ref = db->leastRecent;
DbRef *tmp;
while (ref && db->cacheSize > db->maxCache)
{
char *hash = DbHashKey(ref->name);
if (pthread_mutex_trylock(&ref->lock) != 0)
{
/* This ref is locked by another thread, don't evict it. */
ref = ref->next;
continue;
}
JsonFree(ref->json);
pthread_mutex_unlock(&ref->lock);
pthread_mutex_destroy(&ref->lock);
hash = DbHashKey(ref->name);
HashMapDelete(db->cache, hash);
Free(hash);
ArrayFree(ref->name);
db->cacheSize -= ref->size;
ref->next->prev = ref->prev;
if (!ref->prev)
{
db->leastRecent = ref->next;
}
else
{
ref->prev->next = ref->next;
}
tmp = ref->next;
Free(ref);
ref = tmp;
}
}
Db *
DbOpen(char *dir, size_t cache)
{
Db *db;
if (!dir || cache < DB_MIN_CACHE)
{
return NULL;
}
db = Malloc(sizeof(Db));
if (!db)
{
return NULL;
}
db->dir = dir;
db->maxCache = cache;
pthread_mutex_init(&db->lock, NULL);
db->cache = HashMapCreate();
if (!db->cache)
{
return NULL;
}
db->mostRecent = NULL;
db->leastRecent = NULL;
return db;
}
void
DbClose(Db * db)
{
char *key;
DbRef *val;
if (!db)
{
return;
}
pthread_mutex_destroy(&db->lock);
while (HashMapIterate(db->cache, &key, (void **) &val))
{
Free(key);
JsonFree(val->json);
ArrayFree(val->name);
pthread_mutex_destroy(&val->lock);
Free(val);
}
HashMapFree(db->cache);
Free(db);
}
static DbRef *
DbLockFromArr(Db * db, Array * args)
{
char *file;
char *hash;
DbRef *ref;
FILE *fp;
struct flock lock;
if (!db || !args)
{
return NULL;
}
ref = NULL;
hash = NULL;
pthread_mutex_lock(&db->lock);
/* Check if the item is in the cache */
hash = DbHashKey(args);
ref = HashMapGet(db->cache, hash);
file = DbFileName(db, args);
/* Open the file for reading and writing so we can lock it */
fp = fopen(file, "r+");
if (!fp)
{
ref = NULL;
goto finish;
}
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
/* Lock the file on the disk */
if (fcntl(fileno(fp), F_SETLK, &lock) < 0)
{
fclose(fp);
ref = NULL;
goto finish;
}
if (ref) /* In cache */
{
unsigned long diskTs = UtilLastModified(file);
pthread_mutex_lock(&ref->lock);
ref->fp = fp;
if (diskTs > ref->ts)
{
/* File was modified on disk since it was cached */
HashMap *json = JsonDecode(fp);
if (!json)
{
pthread_mutex_unlock(&ref->lock);
fclose(fp);
ref = NULL;
goto finish;
}
JsonFree(ref->json);
ref->json = json;
ref->ts = diskTs;
ref->size = DbComputeSize(ref->json);
}
/* Float this ref to mostRecent */
if (ref->next)
{
ref->next->prev = ref->prev;
if (!ref->prev)
{
db->leastRecent = ref->next;
}
else
{
ref->prev->next = ref->next;
}
ref->prev = db->mostRecent;
ref->next = NULL;
db->mostRecent = ref;
}
/* The file on disk may be larger than what we have in memory,
* which may require items in cache to be evicted. */
DbCacheEvict(db);
}
else
{
Array *name = ArrayCreate();
size_t i;
/* Not in cache; load from disk */
ref = Malloc(sizeof(DbRef));
if (!ref)
{
fclose(fp);
goto finish;
}
ref->json = JsonDecode(fp);
ref->fp = fp;
if (!ref->json)
{
Free(ref);
fclose(fp);
ref = NULL;
goto finish;
}
pthread_mutex_init(&ref->lock, NULL);
pthread_mutex_lock(&ref->lock);
ref->ts = UtilServerTs();
ref->size = DbComputeSize(ref->json);
for (i = 0; i < ArraySize(args); i++)
{
ArrayAdd(name, UtilStringDuplicate(ArrayGet(args, i)));
}
ref->name = name;
HashMapSet(db->cache, hash, ref);
db->cacheSize += ref->size;
ref->next = NULL;
ref->prev = db->mostRecent;
db->mostRecent = ref;
/* Adding this item to the cache may case it to grow too large,
* requiring some items to be evicted */
DbCacheEvict(db);
}
finish:
pthread_mutex_unlock(&db->lock);
Free(file);
Free(hash);
return ref;
}
DbRef *
DbCreate(Db * db, size_t nArgs,...)
{
FILE *fp;
char *file;
char *dir;
va_list ap;
Array *args;
if (!db)
{
return NULL;
}
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
if (!args)
{
return NULL;
}
file = DbFileName(db, args);
if (UtilLastModified(file))
{
Free(file);
ArrayFree(args);
return NULL;
}
dir = DbDirName(db, args);
if (UtilMkdir(dir, 0750) < 0)
{
Free(file);
ArrayFree(args);
Free(dir);
return NULL;
}
Free(dir);
fp = fopen(file, "w");
Free(file);
if (!fp)
{
ArrayFree(args);
return NULL;
}
fprintf(fp, "{}");
fflush(fp);
fclose(fp);
return DbLockFromArr(db, args);
}
DbRef *
DbLock(Db * db, size_t nArgs,...)
{
va_list ap;
Array *args;
DbRef *ret;
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
if (!args)
{
return NULL;
}
ret = DbLockFromArr(db, args);
ArrayFree(args);
return ret;
}
int
DbUnlock(Db * db, DbRef * ref)
{
if (!db || !ref)
{
return 0;
}
pthread_mutex_lock(&db->lock);
rewind(ref->fp);
if (ftruncate(fileno(ref->fp), 0) < 0)
{
pthread_mutex_unlock(&db->lock);
return 0;
}
JsonEncode(ref->json, ref->fp);
fflush(ref->fp);
fclose(ref->fp);
db->cacheSize -= ref->size;
ref->size = DbComputeSize(ref->json);
db->cacheSize += ref->size;
pthread_mutex_unlock(&ref->lock);
/* If this ref has grown significantly since we last computed its
* size, it may have filled the cache and require some items to be
* evicted. */
DbCacheEvict(db);
pthread_mutex_unlock(&db->lock);
return 1;
}
HashMap *
DbJson(DbRef * ref)
{
return ref ? ref->json : NULL;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
@ -21,32 +22,14 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef TELODENDRIA_QUEUE_H
#define TELODENDRIA_QUEUE_H
#include <Filter.h>
#include <stddef.h>
#include <Schema/Filter.h>
typedef struct Queue Queue;
extern Queue *
QueueCreate(size_t);
extern void
QueueFree(Queue *);
extern int
QueuePush(Queue *, void *);
extern void *
QueuePop(Queue *);
extern void *
QueuePeek(Queue *);
extern int
QueueFull(Queue *);
extern int
QueueEmpty(Queue *);
#endif
HashMap *
FilterApply(Filter * filter, HashMap * event)
{
(void) filter;
(void) event;
return NULL;
}

View File

@ -1,376 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <HashMap.h>
#include <Memory.h>
#include <stddef.h>
#include <string.h>
typedef struct HashMapBucket
{
unsigned long hash;
char *key;
void *value;
} HashMapBucket;
struct HashMap
{
size_t count;
size_t capacity;
HashMapBucket **entries;
unsigned long (*hashFunc) (const char *);
float maxLoad;
size_t iterator;
};
static unsigned long
HashMapHashKey(const char *key)
{
unsigned long hash = 2166136261u;
size_t i = 0;
while (key[i])
{
hash ^= (unsigned char) key[i];
hash *= 16777619;
i++;
}
return hash;
}
static int
HashMapGrow(HashMap * map)
{
size_t oldCapacity;
size_t i;
HashMapBucket **newEntries;
if (!map)
{
return 0;
}
oldCapacity = map->capacity;
map->capacity *= 2;
newEntries = Malloc(map->capacity * sizeof(HashMapBucket *));
if (!newEntries)
{
map->capacity /= 2;
return 0;
}
memset(newEntries, 0, map->capacity * sizeof(HashMapBucket *));
for (i = 0; i < oldCapacity; i++)
{
/* If there is a value here, and it isn't a tombstone */
if (map->entries[i] && map->entries[i]->hash)
{
/* Copy it to the new entries array */
size_t index = map->entries[i]->hash % map->capacity;
for (;;)
{
if (newEntries[index])
{
if (!newEntries[index]->hash)
{
Free(newEntries[index]);
newEntries[index] = map->entries[i];
break;
}
}
else
{
newEntries[index] = map->entries[i];
break;
}
index = (index + 1) % map->capacity;
}
}
else
{
/* Either NULL or a tombstone */
Free(map->entries[i]);
}
}
Free(map->entries);
map->entries = newEntries;
return 1;
}
HashMap *
HashMapCreate(void)
{
HashMap *map = Malloc(sizeof(HashMap));
if (!map)
{
return NULL;
}
map->maxLoad = 0.75;
map->count = 0;
map->capacity = 16;
map->iterator = 0;
map->hashFunc = HashMapHashKey;
map->entries = Malloc(map->capacity * sizeof(HashMapBucket *));
if (!map->entries)
{
Free(map);
return NULL;
}
memset(map->entries, 0, map->capacity * sizeof(HashMapBucket *));
return map;
}
void *
HashMapDelete(HashMap * map, const char *key)
{
unsigned long hash;
size_t index;
if (!map || !key)
{
return NULL;
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
break;
}
if (bucket->hash == hash)
{
bucket->hash = 0;
return bucket->value;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
void
HashMapFree(HashMap * map)
{
if (map)
{
size_t i;
for (i = 0; i < map->capacity; i++)
{
if (map->entries[i])
{
Free(map->entries[i]);
}
}
Free(map->entries);
Free(map);
}
}
void *
HashMapGet(HashMap * map, const char *key)
{
unsigned long hash;
size_t index;
if (!map || !key)
{
return NULL;
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
break;
}
if (bucket->hash == hash)
{
return bucket->value;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
int
HashMapIterate(HashMap * map, char **key, void **value)
{
if (!map)
{
return 0;
}
if (map->iterator >= map->capacity)
{
map->iterator = 0;
*key = NULL;
*value = NULL;
return 0;
}
while (map->iterator < map->capacity)
{
HashMapBucket *bucket = map->entries[map->iterator];
map->iterator++;
if (bucket)
{
*key = bucket->key;
*value = bucket->value;
return 1;
}
}
map->iterator = 0;
return 0;
}
void
HashMapMaxLoadSet(HashMap * map, float load)
{
if (!map || (load > 1.0 || load <= 0))
{
return;
}
map->maxLoad = load;
}
void
HashMapFunctionSet(HashMap * map, unsigned long (*hashFunc) (const char *))
{
if (!map || !hashFunc)
{
return;
}
map->hashFunc = hashFunc;
}
void *
HashMapSet(HashMap * map, char *key, void *value)
{
unsigned long hash;
size_t index;
if (!map || !key || !value)
{
return NULL;
}
if (map->count + 1 > map->capacity * map->maxLoad)
{
HashMapGrow(map);
}
hash = map->hashFunc(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
bucket = Malloc(sizeof(HashMapBucket));
if (!bucket)
{
break;
}
bucket->hash = hash;
bucket->key = key;
bucket->value = value;
map->entries[index] = bucket;
map->count++;
break;
}
if (!bucket->hash)
{
bucket->hash = hash;
bucket->key = key;
bucket->value = value;
break;
}
if (bucket->hash == hash)
{
void *oldValue = bucket->value;
bucket->value = value;
return oldValue;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
void
HashMapIterateFree(char *key, void *value)
{
if (key)
{
Free(key);
}
if (value)
{
Free(value);
}
}

73
src/Html.c Normal file
View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Html.h>
#include <stdio.h>
#include <Telodendria.h>
void
HtmlBegin(Stream * stream, char *title)
{
size_t i;
if (!stream)
{
return;
}
StreamPrintf(stream,
"<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"utf-8\">"
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
"<title>%s | Telodendria</title>"
"<link rel=\"stylesheet\" href=\"/_matrix/static/telodendria.css\">"
"<script src=\"/_matrix/static/telodendria.js\"></script>"
"</head>"
"<body>"
"<pre class=\"logo\">"
,title
);
for (i = 0; i < TELODENDRIA_LOGO_HEIGHT; i++)
{
StreamPrintf(stream, "%s\n", TelodendriaLogo[i]);
}
StreamPrintf(stream,
"</pre>"
"<h1>%s</h1>"
,title);
}
void
HtmlEnd(Stream * stream)
{
StreamPuts(stream,
"</body>"
"</html>");
}

View File

@ -1,529 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Http.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Memory.h>
#include <HashMap.h>
#ifndef TELODENDRIA_STRING_CHUNK
#define TELODENDRIA_STRING_CHUNK 64
#endif
const char *
HttpRequestMethodToString(const HttpRequestMethod method)
{
switch (method)
{
case HTTP_GET:
return "GET";
case HTTP_HEAD:
return "HEAD";
case HTTP_POST:
return "POST";
case HTTP_PUT:
return "PUT";
case HTTP_DELETE:
return "DELETE";
case HTTP_CONNECT:
return "CONNECT";
case HTTP_OPTIONS:
return "OPTIONS";
case HTTP_TRACE:
return "TRACE";
case HTTP_PATCH:
return "PATCH";
default:
return NULL;
}
}
HttpRequestMethod
HttpRequestMethodFromString(const char *str)
{
if (strcmp(str, "GET") == 0)
{
return HTTP_GET;
}
if (strcmp(str, "HEAD") == 0)
{
return HTTP_HEAD;
}
if (strcmp(str, "POST") == 0)
{
return HTTP_POST;
}
if (strcmp(str, "PUT") == 0)
{
return HTTP_PUT;
}
if (strcmp(str, "DELETE") == 0)
{
return HTTP_DELETE;
}
if (strcmp(str, "CONNECT") == 0)
{
return HTTP_CONNECT;
}
if (strcmp(str, "OPTIONS") == 0)
{
return HTTP_OPTIONS;
}
if (strcmp(str, "TRACE") == 0)
{
return HTTP_TRACE;
}
if (strcmp(str, "PATCH") == 0)
{
return HTTP_PATCH;
}
return HTTP_METHOD_UNKNOWN;
}
const char *
HttpStatusToString(const HttpStatus status)
{
switch (status)
{
case HTTP_CONTINUE:
return "Continue";
case HTTP_SWITCHING_PROTOCOLS:
return "Switching Protocols";
case HTTP_EARLY_HINTS:
return "Early Hints";
case HTTP_OK:
return "Ok";
case HTTP_CREATED:
return "Created";
case HTTP_ACCEPTED:
return "Accepted";
case HTTP_NON_AUTHORITATIVE_INFORMATION:
return "Non-Authoritative Information";
case HTTP_NO_CONTENT:
return "No Content";
case HTTP_RESET_CONTENT:
return "Reset Content";
case HTTP_PARTIAL_CONTENT:
return "Partial Content";
case HTTP_MULTIPLE_CHOICES:
return "Multiple Choices";
case HTTP_MOVED_PERMANENTLY:
return "Moved Permanently";
case HTTP_FOUND:
return "Found";
case HTTP_SEE_OTHER:
return "See Other";
case HTTP_NOT_MODIFIED:
return "Not Modified";
case HTTP_TEMPORARY_REDIRECT:
return "Temporary Redirect";
case HTTP_PERMANENT_REDIRECT:
return "Permanent Redirect";
case HTTP_BAD_REQUEST:
return "Bad Request";
case HTTP_UNAUTHORIZED:
return "Unauthorized";
case HTTP_FORBIDDEN:
return "Forbidden";
case HTTP_NOT_FOUND:
return "Not Found";
case HTTP_METHOD_NOT_ALLOWED:
return "Method Not Allowed";
case HTTP_NOT_ACCEPTABLE:
return "Not Acceptable";
case HTTP_PROXY_AUTH_REQUIRED:
return "Proxy Authentication Required";
case HTTP_REQUEST_TIMEOUT:
return "Request Timeout";
case HTTP_CONFLICT:
return "Conflict";
case HTTP_GONE:
return "Gone";
case HTTP_LENGTH_REQUIRED:
return "Length Required";
case HTTP_PRECONDITION_FAILED:
return "Precondition Failed";
case HTTP_PAYLOAD_TOO_LARGE:
return "Payload Too Large";
case HTTP_URI_TOO_LONG:
return "URI Too Long";
case HTTP_UNSUPPORTED_MEDIA_TYPE:
return "Unsupported Media Type";
case HTTP_RANGE_NOT_SATISFIABLE:
return "Range Not Satisfiable";
case HTTP_EXPECTATION_FAILED:
return "Expectation Failed";
case HTTP_TEAPOT:
return "I'm a Teapot";
case HTTP_UPGRADE_REQUIRED:
return "Upgrade Required";
case HTTP_PRECONDITION_REQUIRED:
return "Precondition Required";
case HTTP_TOO_MANY_REQUESTS:
return "Too Many Requests";
case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
return "Request Header Fields Too Large";
case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
return "Unavailable For Legal Reasons";
case HTTP_INTERNAL_SERVER_ERROR:
return "Internal Server Error";
case HTTP_NOT_IMPLEMENTED:
return "Not Implemented";
case HTTP_BAD_GATEWAY:
return "Bad Gateway";
case HTTP_SERVICE_UNAVAILABLE:
return "Service Unavailable";
case HTTP_GATEWAY_TIMEOUT:
return "Gateway Timeout";
case HTTP_VERSION_NOT_SUPPORTED:
return "Version Not Supported";
case HTTP_VARIANT_ALSO_NEGOTIATES:
return "Variant Also Negotiates";
case HTTP_NOT_EXTENDED:
return "Not Extended";
case HTTP_NETWORK_AUTH_REQUIRED:
return "Network Authentication Required";
default:
return NULL;
}
}
char *
HttpUrlEncode(char *str)
{
size_t size;
size_t len;
char *encoded;
if (!str)
{
return NULL;
}
size = TELODENDRIA_STRING_CHUNK;
len = 0;
encoded = Malloc(size);
if (!encoded)
{
return NULL;
}
while (*str)
{
char c = *str;
if (len >= size - 4)
{
char *tmp;
size += TELODENDRIA_STRING_CHUNK;
tmp = Realloc(encoded, size);
if (!tmp)
{
Free(encoded);
return NULL;
}
encoded = tmp;
}
/* Control characters and extended characters */
if (c <= 0x1F || c >= 0x7F)
{
goto percentEncode;
}
/* Reserved and unsafe characters */
switch (c)
{
case '$':
case '&':
case '+':
case ',':
case '/':
case ':':
case ';':
case '=':
case '?':
case '@':
case ' ':
case '"':
case '<':
case '>':
case '#':
case '%':
case '{':
case '}':
case '|':
case '\\':
case '^':
case '~':
case '[':
case ']':
case '`':
goto percentEncode;
break;
default:
encoded[len] = c;
len++;
str++;
continue;
}
percentEncode:
encoded[len] = '%';
len++;
snprintf(encoded + len, 3, "%2X", c);
len += 2;
str++;
}
encoded[len] = '\0';
return encoded;
}
char *
HttpUrlDecode(char *str)
{
size_t i;
size_t inputLen;
char *decoded;
if (!str)
{
return NULL;
}
i = 0;
inputLen = strlen(str);
decoded = Malloc(inputLen + 1);
if (!decoded)
{
return NULL;
}
while (*str)
{
char c = *str;
if (c == '%')
{
unsigned int d;
str++;
if (sscanf(str, "%2X", &d) != 1)
{
/* Decoding error */
Free(decoded);
return NULL;
}
if (!d)
{
/* Null character given, don't put that in the string. */
continue;
}
c = (char) d;
str++;
}
decoded[i] = c;
i++;
str++;
}
decoded[i] = '\0';
return decoded;
}
HashMap *
HttpParamDecode(char *in)
{
HashMap *params;
if (!in)
{
return NULL;
}
params = HashMapCreate();
if (!params)
{
return NULL;
}
while (*in)
{
char *buf;
size_t allocated;
size_t len;
char *decKey;
char *decVal;
/* Read in key */
allocated = TELODENDRIA_STRING_CHUNK;
buf = Malloc(allocated);
len = 0;
while (*in && *in != '=')
{
if (len >= allocated - 1)
{
allocated += TELODENDRIA_STRING_CHUNK;
buf = Realloc(buf, allocated);
}
buf[len] = *in;
len++;
in++;
}
buf[len] = '\0';
/* Sanity check */
if (*in != '=')
{
/* Malformed param */
Free(buf);
HashMapFree(params);
return NULL;
}
in++;
/* Decode key */
decKey = HttpUrlDecode(buf);
Free(buf);
if (!decKey)
{
/* Decoding error */
Free(params);
return NULL;
}
/* Read in value */
allocated = TELODENDRIA_STRING_CHUNK;
buf = Malloc(allocated);
len = 0;
while (*in && *in != '&')
{
if (len >= allocated - 1)
{
allocated += TELODENDRIA_STRING_CHUNK;
buf = Realloc(buf, allocated);
}
buf[len] = *in;
len++;
in++;
}
buf[len] = '\0';
/* Decode value */
decVal = HttpUrlDecode(buf);
Free(buf);
if (!decVal)
{
/* Decoding error */
Free(params);
return NULL;
}
HashMapSet(params, decKey, decVal);
if (*in == '&')
{
in++;
continue;
}
else
{
break;
}
}
return params;
}
char *
HttpParamEncode(HashMap * params)
{
char *key;
char *val;
char *out = NULL;
if (!params || !out)
{
return NULL;
}
while (HashMapIterate(params, &key, (void *) &val))
{
char *encKey;
char *encVal;
encKey = HttpUrlEncode(key);
encVal = HttpUrlEncode(val);
if (!encKey || !encVal)
{
/* Memory error */
Free(encKey);
Free(encVal);
return NULL;
}
/* TODO */
Free(encKey);
Free(encVal);
}
return out;
}

View File

@ -1,779 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <HttpServer.h>
#include <Memory.h>
#include <Queue.h>
#include <Array.h>
#include <Util.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
static const char ENABLE = 1;
struct HttpServer
{
int sd;
unsigned int nThreads;
unsigned int maxConnections;
pthread_t socketThread;
volatile unsigned int stop:1;
volatile unsigned int isRunning:1;
HttpHandler *requestHandler;
void *handlerArgs;
Queue *connQueue;
pthread_mutex_t connQueueMutex;
Array *threadPool;
};
struct HttpServerContext
{
HashMap *requestHeaders;
HttpRequestMethod requestMethod;
char *requestPath;
HashMap *requestParams;
HashMap *responseHeaders;
HttpStatus responseStatus;
FILE *stream;
};
typedef struct HttpServerWorkerThreadArgs
{
HttpServer *server;
int id;
pthread_t thread;
} HttpServerWorkerThreadArgs;
static HttpServerContext *
HttpServerContextCreate(HttpRequestMethod requestMethod,
char *requestPath, HashMap * requestParams, FILE * stream)
{
HttpServerContext *c;
c = Malloc(sizeof(HttpServerContext));
if (!c)
{
return NULL;
}
c->requestHeaders = HashMapCreate();
if (!c->requestHeaders)
{
Free(c);
return NULL;
}
c->responseHeaders = HashMapCreate();
if (!c->responseHeaders)
{
Free(c->requestHeaders);
Free(c);
return NULL;
}
c->requestMethod = requestMethod;
c->requestPath = requestPath;
c->requestParams = requestParams;
c->stream = stream;
c->responseStatus = HTTP_OK;
return c;
}
static void
HttpServerContextFree(HttpServerContext * c)
{
char *key;
void *val;
if (!c)
{
return;
}
while (HashMapIterate(c->requestHeaders, &key, &val))
{
/*
* These are always parsed from the request, so they should
* always be on the heap.
*/
Free(key);
Free(val);
}
HashMapFree(c->requestHeaders);
while (HashMapIterate(c->responseHeaders, &key, &val))
{
/*
* These are generated by code. As such, they may be either
* on the heap, or on the stack, depending on how they were
* added.
*
* Basically, if the memory API knows about a pointer, then
* it can be freed. If it doesn't know about a pointer, skip
* freeing it because it's probably a stack pointer.
*/
if (MemoryInfoGet(key))
{
Free(key);
}
if (MemoryInfoGet(val))
{
Free(val);
}
}
HashMapFree(c->responseHeaders);
while (HashMapIterate(c->requestParams, &key, &val))
{
Free(key);
Free(val);
}
HashMapFree(c->requestParams);
Free(c->requestPath);
fclose(c->stream);
Free(c);
}
HashMap *
HttpRequestHeaders(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestHeaders;
}
HttpRequestMethod
HttpRequestMethodGet(HttpServerContext * c)
{
if (!c)
{
return HTTP_METHOD_UNKNOWN;
}
return c->requestMethod;
}
char *
HttpRequestPath(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestPath;
}
HashMap *
HttpRequestParams(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestParams;
}
char *
HttpResponseHeader(HttpServerContext * c, char *key, char *val)
{
if (!c)
{
return NULL;
}
return HashMapSet(c->responseHeaders, key, val);
}
void
HttpResponseStatus(HttpServerContext * c, HttpStatus status)
{
if (!c)
{
return;
}
c->responseStatus = status;
}
FILE *
HttpStream(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->stream;
}
void
HttpSendHeaders(HttpServerContext * c)
{
FILE *fp = c->stream;
char *key;
char *val;
fprintf(fp, "HTTP/1.0 %d %s\n", c->responseStatus, HttpStatusToString(c->responseStatus));
while (HashMapIterate(c->responseHeaders, &key, (void **) &val))
{
fprintf(fp, "%s: %s\n", key, val);
}
fprintf(fp, "\n");
}
static int
QueueConnection(HttpServer * server, int fd)
{
FILE *fp;
int result;
if (!server)
{
return 0;
}
fp = fdopen(fd, "r+");
if (!fp)
{
return 0;
}
pthread_mutex_lock(&server->connQueueMutex);
result = QueuePush(server->connQueue, fp);
pthread_mutex_unlock(&server->connQueueMutex);
return result;
}
static FILE *
DequeueConnection(HttpServer * server)
{
FILE *fp;
if (!server)
{
return NULL;
}
pthread_mutex_lock(&server->connQueueMutex);
fp = QueuePop(server->connQueue);
pthread_mutex_unlock(&server->connQueueMutex);
return fp;
}
HttpServer *
HttpServerCreate(unsigned short port, unsigned int nThreads, unsigned int maxConnections,
HttpHandler * requestHandler, void *handlerArgs)
{
HttpServer *server;
struct sockaddr_in sa = {0};
if (!requestHandler)
{
return NULL;
}
server = Malloc(sizeof(HttpServer));
if (!server)
{
goto error;
}
memset(server, 0, sizeof(HttpServer));
server->threadPool = ArrayCreate();
if (!server->threadPool)
{
goto error;
}
server->connQueue = QueueCreate(maxConnections);
if (!server->connQueue)
{
goto error;
}
if (pthread_mutex_init(&server->connQueueMutex, NULL) != 0)
{
goto error;
}
server->sd = socket(AF_INET, SOCK_STREAM, 0);
if (server->sd < 0)
{
goto error;
}
if (fcntl(server->sd, F_SETFL, O_NONBLOCK) == -1)
{
goto error;
}
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(int)) < 0)
{
goto error;
}
#ifdef SO_REUSEPORT
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEPORT, &ENABLE, sizeof(int)) < 0)
{
goto error;
}
#endif
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(server->sd, (struct sockaddr *) & sa, sizeof(sa)) < 0)
{
goto error;
}
if (listen(server->sd, maxConnections) < 0)
{
goto error;
}
server->nThreads = nThreads;
server->maxConnections = maxConnections;
server->requestHandler = requestHandler;
server->handlerArgs = handlerArgs;
server->stop = 0;
server->isRunning = 0;
return server;
error:
if (server)
{
if (server->connQueue)
{
QueueFree(server->connQueue);
}
pthread_mutex_destroy(&server->connQueueMutex);
if (server->threadPool)
{
ArrayFree(server->threadPool);
}
if (server->sd)
{
close(server->sd);
}
Free(server);
}
return NULL;
}
void
HttpServerFree(HttpServer * server)
{
if (!server)
{
return;
}
close(server->sd);
QueueFree(server->connQueue);
pthread_mutex_destroy(&server->connQueueMutex);
ArrayFree(server->threadPool);
Free(server);
}
static void *
HttpServerWorkerThread(void *args)
{
HttpServerWorkerThreadArgs *wArgs = (HttpServerWorkerThreadArgs *) args;
HttpServer *server = wArgs->server;
while (!server->stop)
{
FILE *fp;
HttpServerContext *context;
char *line = NULL;
size_t lineSize = 0;
ssize_t lineLen = 0;
char *requestMethodPtr;
char *pathPtr;
char *requestPath;
char *requestProtocol;
HashMap *requestParams;
ssize_t requestPathLen;
ssize_t i = 0;
HttpRequestMethod requestMethod;
long firstRead;
fp = DequeueConnection(server);
if (!fp)
{
/* Block for 1 millisecond before continuing so we don't
* murder the CPU if the queue is empty. */
UtilSleepMillis(1);
continue;
}
/* Get the first line of the request.
*
* Every once in a while, we're too fast for the client. When this
* happens, UtilGetLine() sets errno to EAGAIN. If we get
* EAGAIN, then clear the error on the stream and try again
* after 1ms. This is typically more than enough time for the
* client to send data. */
firstRead = UtilServerTs();
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) == -1
&& errno == EAGAIN)
{
clearerr(fp);
/* If the server is stopped, or it's been a while, just
* give up. */
if (server->stop || (UtilServerTs() - firstRead) > 1000 * 30)
{
goto finish;
}
UtilSleepMillis(5);
}
if (lineLen == -1)
{
goto bad_request;
}
requestMethodPtr = line;
for (i = 0; i < lineLen; i++)
{
if (line[i] == ' ')
{
line[i] = '\0';
break;
}
}
if (i == lineLen)
{
goto bad_request;
}
requestMethod = HttpRequestMethodFromString(requestMethodPtr);
if (requestMethod == HTTP_METHOD_UNKNOWN)
{
goto bad_request;
}
pathPtr = line + i + 1;
for (i = 0; i < (line + lineLen) - pathPtr; i++)
{
if (pathPtr[i] == ' ')
{
pathPtr[i] = '\0';
break;
}
}
requestPathLen = i;
requestPath = Malloc(((requestPathLen + 1) * sizeof(char)));
strcpy(requestPath, pathPtr);
requestProtocol = &pathPtr[i + 1];
line[lineLen - 2] = '\0'; /* Get rid of \r and \n */
if (strcmp(requestProtocol, "HTTP/1.1") != 0 && strcmp(requestProtocol, "HTTP/1.0") != 0)
{
goto bad_request;
}
/* Find request params */
for (i = 0; i < requestPathLen; i++)
{
if (requestPath[i] == '?')
{
break;
}
}
requestPath[i] = '\0';
requestParams = HttpParamDecode(requestPath + i);
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
if (!context)
{
goto internal_error;
}
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
{
char *headerKey;
char *headerValue;
char *headerPtr;
if (strcmp(line, "\r\n") == 0)
{
break;
}
for (i = 0; i < lineLen; i++)
{
if (line[i] == ':')
{
line[i] = '\0';
break;
}
line[i] = tolower(line[i]);
}
headerKey = Malloc((i * sizeof(char)) + 1);
if (!headerKey)
{
goto internal_error;
}
strcpy(headerKey, line);
headerPtr = line + i + 1;
while (isspace((unsigned char) *headerPtr))
{
headerPtr++;
}
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
{
if (!isspace((unsigned char) line[i]))
{
break;
}
line[i] = '\0';
}
headerValue = Malloc(strlen(headerPtr) + 1);
if (!headerValue)
{
goto internal_error;
}
strcpy(headerValue, headerPtr);
HashMapSet(context->requestHeaders, headerKey, headerValue);
}
server->requestHandler(context, server->handlerArgs);
HttpServerContextFree(context);
fp = NULL; /* The above call will close this
* FILE */
goto finish;
internal_error:
fprintf(fp, "HTTP/1.0 500 Internal Server Error\n");
fprintf(fp, "Connection: close\n");
goto finish;
bad_request:
fprintf(fp, "HTTP/1.0 400 Bad Request\n");
fprintf(fp, "Connection: close\n");
goto finish;
finish:
Free(line);
if (fp)
{
fclose(fp);
}
}
return NULL;
}
static void *
HttpServerEventThread(void *args)
{
HttpServer *server = (HttpServer *) args;
struct pollfd pollFds[1];
FILE *fp;
size_t i;
server->isRunning = 1;
server->stop = 0;
pollFds[0].fd = server->sd;
pollFds[0].events = POLLIN;
for (i = 0; i < server->nThreads; i++)
{
HttpServerWorkerThreadArgs *workerThread = Malloc(sizeof(HttpServerWorkerThreadArgs));
if (!workerThread)
{
/* TODO: Make the event thread return an error to the main
* thread */
return NULL;
}
workerThread->server = server;
workerThread->id = i;
if (pthread_create(&workerThread->thread, NULL, HttpServerWorkerThread, workerThread) != 0)
{
/* TODO: Make the event thread return an error to the main
* thread */
return NULL;
}
ArrayAdd(server->threadPool, workerThread);
}
while (!server->stop)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
int connFd;
int pollResult;
pollResult = poll(pollFds, 1, 500);
if (pollResult < 0)
{
/* The poll either timed out, or was interrupted. */
continue;
}
connFd = accept(server->sd, (struct sockaddr *) & addr, &addrLen);
if (connFd < 0)
{
continue;
}
QueueConnection(server, connFd);
}
for (i = 0; i < server->nThreads; i++)
{
HttpServerWorkerThreadArgs *workerThread = ArrayGet(server->threadPool, i);
pthread_join(workerThread->thread, NULL);
Free(workerThread);
}
while ((fp = DequeueConnection(server)))
{
fclose(fp);
}
server->isRunning = 0;
return NULL;
}
int
HttpServerStart(HttpServer * server)
{
if (!server)
{
return 0;
}
if (server->isRunning)
{
return 1;
}
if (pthread_create(&server->socketThread, NULL, HttpServerEventThread, server) != 0)
{
return 0;
}
return 1;
}
void
HttpServerJoin(HttpServer * server)
{
if (!server)
{
return;
}
pthread_join(server->socketThread, NULL);
}
void
HttpServerStop(HttpServer * server)
{
if (!server)
{
return;
}
server->stop = 1;
}

1056
src/Json.c

File diff suppressed because it is too large Load Diff

362
src/Log.c
View File

@ -1,362 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Log.h>
#include <Memory.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <stdarg.h>
#include <pthread.h>
#define LOG_TSBUFFER 64
struct LogConfig
{
int level;
size_t indent;
FILE *out;
int flags;
char *tsFmt;
pthread_mutex_t lock;
};
LogConfig *
LogConfigCreate(void)
{
LogConfig *config;
config = Malloc(sizeof(LogConfig));
if (!config)
{
return NULL;
}
memset(config, 0, sizeof(LogConfig));
LogConfigLevelSet(config, LOG_INFO);
LogConfigIndentSet(config, 0);
LogConfigOutputSet(config, NULL); /* Will set to stdout */
LogConfigFlagSet(config, LOG_FLAG_COLOR);
LogConfigTimeStampFormatSet(config, "%y-%m-%d %H:%M:%S");
return config;
}
void
LogConfigFlagClear(LogConfig * config, int flags)
{
if (!config)
{
return;
}
config->flags &= ~flags;
}
static int
LogConfigFlagGet(LogConfig * config, int flags)
{
if (!config)
{
return 0;
}
return config->flags & flags;
}
void
LogConfigFlagSet(LogConfig * config, int flags)
{
if (!config)
{
return;
}
config->flags |= flags;
}
void
LogConfigFree(LogConfig * config)
{
if (!config)
{
return;
}
fclose(config->out);
Free(config);
}
void
LogConfigIndent(LogConfig * config)
{
if (config)
{
config->indent += 2;
}
}
void
LogConfigIndentSet(LogConfig * config, size_t indent)
{
if (!config)
{
return;
}
config->indent = indent;
}
int
LogConfigLevelGet(LogConfig * config)
{
if (!config)
{
return -1;
}
return config->level;
}
void
LogConfigLevelSet(LogConfig * config, int level)
{
if (!config)
{
return;
}
switch (level)
{
case LOG_ERR:
case LOG_WARNING:
case LOG_INFO:
case LOG_DEBUG:
config->level = level;
default:
break;
}
}
void
LogConfigOutputSet(LogConfig * config, FILE * out)
{
if (!config)
{
return;
}
if (out)
{
config->out = out;
}
else
{
config->out = stdout;
}
}
void
LogConfigTimeStampFormatSet(LogConfig * config, char *tsFmt)
{
if (config)
{
config->tsFmt = tsFmt;
}
}
void
LogConfigUnindent(LogConfig * config)
{
if (config && config->indent >= 2)
{
config->indent -= 2;
}
}
void
Log(LogConfig * config, int level, const char *msg,...)
{
size_t i;
int doColor;
char indicator;
va_list argp;
/*
* Only proceed if we have a config and its log level is set to a
* value that permits us to log. This is as close as we can get
* to a no-op function if we aren't logging anything, without doing
* some crazy macro magic.
*/
if (!config || level > config->level)
{
return;
}
/* Misconfiguration */
if (!config->out)
{
return;
}
pthread_mutex_lock(&config->lock);
if (LogConfigFlagGet(config, LOG_FLAG_SYSLOG))
{
/* No further print logic is needed; syslog will handle it all
* for us. */
va_start(argp, msg);
vsyslog(level, msg, argp);
va_end(argp);
pthread_mutex_unlock(&config->lock);
return;
}
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
&& isatty(fileno(config->out));
if (doColor)
{
char *ansi;
switch (level)
{
case LOG_EMERG:
case LOG_ALERT:
case LOG_CRIT:
case LOG_ERR:
/* Bold Red */
ansi = "\033[1;31m";
break;
case LOG_WARNING:
/* Bold Yellow */
ansi = "\033[1;33m";
break;
case LOG_NOTICE:
/* Bold Magenta */
ansi = "\033[1;35m";
break;
case LOG_INFO:
/* Bold Green */
ansi = "\033[1;32m";
break;
case LOG_DEBUG:
/* Bold Blue */
ansi = "\033[1;34m";
break;
default:
ansi = "";
break;
}
fputs(ansi, config->out);
}
fputc('[', config->out);
if (config->tsFmt)
{
time_t timer = time(NULL);
struct tm *timeInfo = localtime(&timer);
char tsBuffer[LOG_TSBUFFER];
int tsLength = strftime(tsBuffer, LOG_TSBUFFER, config->tsFmt,
timeInfo);
if (tsLength)
{
fputs(tsBuffer, config->out);
if (!isspace((unsigned char) tsBuffer[tsLength - 1]))
{
fputc(' ', config->out);
}
}
}
switch (level)
{
case LOG_EMERG:
indicator = '#';
break;
case LOG_ALERT:
indicator = '@';
break;
case LOG_CRIT:
indicator = 'X';
break;
case LOG_ERR:
indicator = 'x';
break;
case LOG_WARNING:
indicator = '!';
break;
case LOG_NOTICE:
indicator = '~';
break;
case LOG_INFO:
indicator = '>';
break;
case LOG_DEBUG:
indicator = '*';
break;
default:
indicator = '?';
break;
}
fprintf(config->out, "%c]", indicator);
if (doColor)
{
/* ANSI Reset */
fputs("\033[0m", config->out);
}
fputc(' ', config->out);
for (i = 0; i < config->indent; i++)
{
fputc(' ', config->out);
}
va_start(argp, msg);
vfprintf(config->out, msg, argp);
fputc('\n', config->out);
va_end(argp);
/* If we are debugging, there might be something that's going to
* segfault the program coming up, so flush the output stream
* immediately. */
if (config->level == LOG_DEBUG)
{
fflush(config->out);
}
pthread_mutex_unlock(&config->lock);
}

648
src/Main.c Normal file
View File

@ -0,0 +1,648 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <limits.h>
#include <grp.h>
#include <pwd.h>
#include <Cytoplasm/Args.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Db.h>
#include <Cytoplasm/Cron.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Telodendria.h>
#include <Matrix.h>
#include <User.h>
#include <RegToken.h>
#include <Routes.h>
#include <Uia.h>
#include <Config.h>
static Array *httpServers;
static volatile int restart;
static void
SignalHandler(int signal)
{
size_t i;
switch (signal)
{
case SIGPIPE:
return;
case SIGUSR1:
restart = 1;
/* Fall through */
case SIGTERM:
case SIGINT:
if (!httpServers)
{
return;
}
for (i = 0; i < ArraySize(httpServers); i++)
{
HttpServer *server = ArrayGet(httpServers, i);
HttpServerStop(server);
}
break;
}
}
typedef enum ArgFlag
{
ARG_VERSION = (1 << 0),
ARG_VERBOSE = (1 << 2)
} ArgFlag;
int
Main(Array * args)
{
int exit;
/* Arg parsing */
ArgParseState arg;
int opt;
int flags;
char *dbPath;
/* Program configuration */
Config tConfig;
Stream *logFile;
Stream *pidFile;
char *pidPath;
/* User validation */
struct passwd *userInfo;
struct group *groupInfo;
/* HTTP server management */
size_t i;
HttpServer *server;
/* Signal handling */
struct sigaction sigAction;
MatrixHttpHandlerArgs matrixArgs;
Cron *cron;
char startDir[PATH_MAX];
char *token;
start:
/* Global variables */
httpServers = NULL;
restart = 0;
/* Local variables */
exit = EXIT_SUCCESS;
flags = 0;
dbPath = NULL;
logFile = NULL;
pidFile = NULL;
pidPath = NULL;
userInfo = NULL;
groupInfo = NULL;
cron = NULL;
token = NULL;
memset(&matrixArgs, 0, sizeof(matrixArgs));
if (!LogConfigGlobal())
{
printf("Fatal error: unable to allocate memory for logger.\n");
return EXIT_FAILURE;
}
TelodendriaPrintHeader();
ArgParseStateInit(&arg);
while ((opt = ArgParse(&arg, args, "d:Vv")) != -1)
{
switch (opt)
{
case 'd':
dbPath = arg.optArg;
break;
case 'V':
flags |= ARG_VERSION;
break;
case 'v':
flags |= ARG_VERBOSE;
break;
case '?':
exit = EXIT_FAILURE;
goto finish;
default:
break;
}
}
if (flags & ARG_VERBOSE)
{
LogConfigLevelSet(LogConfigGlobal(), LOG_DEBUG);
MemoryHook(TelodendriaMemoryHook, (void *) ARG_VERBOSE);
}
else
{
MemoryHook(TelodendriaMemoryHook, NULL);
}
if (flags & ARG_VERSION)
{
goto finish;
}
if (!dbPath)
{
Log(LOG_ERR, "No database directory specified.");
exit = EXIT_FAILURE;
goto finish;
}
if (!getcwd(startDir, PATH_MAX))
{
Log(LOG_ERR, "Unable to determine current working directory.");
exit = EXIT_FAILURE;
goto finish;
}
if (chdir(dbPath) != 0)
{
Log(LOG_ERR, "Unable to change into data directory: %s.", strerror(errno));
exit = EXIT_FAILURE;
goto finish;
}
else
{
Log(LOG_DEBUG, "Changed working directory to: %s", dbPath);
}
matrixArgs.db = DbOpen(".", 0);
if (!matrixArgs.db)
{
Log(LOG_ERR, "Unable to open data directory as a database.");
exit = EXIT_FAILURE;
goto finish;
}
else
{
Log(LOG_DEBUG, "Opened database.");
}
if (!ConfigExists(matrixArgs.db))
{
RegTokenInfo *info;
Log(LOG_NOTICE, "No configuration exists in the opened database.");
Log(LOG_NOTICE, "A default configuration will be created, and a");
Log(LOG_NOTICE, "new single-use registration token that grants all");
Log(LOG_NOTICE, "privileges will be created so an admin user can");
Log(LOG_NOTICE, "be created to configure this database using the");
Log(LOG_NOTICE, "administrator API.");
if (!ConfigCreateDefault(matrixArgs.db))
{
Log(LOG_ERR, "Unable to create default configuration.");
exit = EXIT_FAILURE;
goto finish;
}
token = StrRandom(32);
info = RegTokenCreate(matrixArgs.db, token, NULL, /* expires */ 0, /* uses */ 1, USER_ALL);
if (!info)
{
Free(token);
Log(LOG_ERR, "Unable to create admin registration token.");
exit = EXIT_FAILURE;
goto finish;
}
RegTokenClose(info);
RegTokenFree(info);
/* Don't free token, because we need to print it when logging
* is set up. */
}
Log(LOG_NOTICE, "Loading configuration...");
ConfigLock(matrixArgs.db, &tConfig);
if (!tConfig.ok)
{
Log(LOG_ERR, tConfig.err);
exit = EXIT_FAILURE;
goto finish;
}
if (!tConfig.log.timestampFormat || !StrEquals(tConfig.log.timestampFormat, "default"))
{
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig.log.timestampFormat);
}
if (tConfig.log.color)
{
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR);
}
else
{
LogConfigFlagClear(LogConfigGlobal(), LOG_FLAG_COLOR);
}
LogConfigLevelSet(
LogConfigGlobal(),
flags & ARG_VERBOSE ?
LOG_DEBUG :
ConfigLogLevelToSyslog(tConfig.log.level));
if (tConfig.log.output == CONFIG_LOG_OUTPUT_FILE)
{
logFile = StreamOpen("telodendria.log", "a");
if (!logFile)
{
Log(LOG_ERR, "Unable to open log file for appending.");
exit = EXIT_FAILURE;
tConfig.log.output = CONFIG_LOG_OUTPUT_STDOUT;
goto finish;
}
Log(LOG_INFO, "Logging to the log file. Check there for all future messages.");
LogConfigOutputSet(LogConfigGlobal(), logFile);
}
else if (tConfig.log.output == CONFIG_LOG_OUTPUT_STDOUT)
{
Log(LOG_DEBUG, "Already logging to standard output.");
}
else if (tConfig.log.output == CONFIG_LOG_OUTPUT_SYSLOG)
{
Log(LOG_INFO, "Logging to the syslog. Check there for all future messages.");
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_SYSLOG);
openlog("telodendria", LOG_PID | LOG_NDELAY, LOG_DAEMON);
/* Always log everything, because the Log API will control what
* messages get passed to the syslog */
setlogmask(LOG_UPTO(LOG_DEBUG));
}
/* If a token was created with a default config, print it to the
* log */
if (token)
{
Log(LOG_NOTICE, "Admin Registration token: %s", token);
Free(token);
}
if (tConfig.pid)
{
pidFile = StreamOpen(tConfig.pid, "w+");
if (!pidFile)
{
char *msg = "Couldn't lock PID file at '%s'";
Log(LOG_ERR, msg, tConfig.pid);
exit = EXIT_FAILURE;
goto finish;
}
pidPath = StrDuplicate(tConfig.pid);
StreamPrintf(pidFile, "%ld", (long) getpid());
StreamClose(pidFile);
}
Log(LOG_DEBUG, "Configuration:");
LogConfigIndent(LogConfigGlobal());
Log(LOG_DEBUG, "Server Name: %s", tConfig.serverName);
Log(LOG_DEBUG, "Base URL: %s", tConfig.baseUrl);
Log(LOG_DEBUG, "Identity Server: %s", tConfig.identityServer);
Log(LOG_DEBUG, "Run As: %s:%s", tConfig.runAs.uid, tConfig.runAs.gid);
Log(LOG_DEBUG, "Max Cache: %ld", tConfig.maxCache);
Log(LOG_DEBUG, "Registration: %s", tConfig.registration ? "true" : "false");
Log(LOG_DEBUG, "Federation: %s", tConfig.federation ? "true" : "false");
LogConfigUnindent(LogConfigGlobal());
httpServers = ArrayCreate();
if (!httpServers)
{
Log(LOG_ERR, "Error setting up HTTP server.");
exit = EXIT_FAILURE;
goto finish;
}
/* Bind servers before possibly dropping permissions. */
for (i = 0; i < ArraySize(tConfig.listen); i++)
{
ConfigListener *serverCfg = ArrayGet(tConfig.listen, i);
HttpServerConfig args;
args.port = serverCfg->port;
args.threads = serverCfg->maxConnections;
args.maxConnections = serverCfg->maxConnections;
args.tlsCert = serverCfg->tls.cert;
args.tlsKey = serverCfg->tls.key;
args.flags = args.tlsCert && args.tlsKey ? HTTP_FLAG_TLS : HTTP_FLAG_NONE;
Log(LOG_DEBUG, "HTTP listener: %lu", i);
LogConfigIndent(LogConfigGlobal());
Log(LOG_DEBUG, "Port: %hu", serverCfg->port);
Log(LOG_DEBUG, "Threads: %u", serverCfg->threads);
Log(LOG_DEBUG, "Max Connections: %u", serverCfg->maxConnections);
Log(LOG_DEBUG, "Flags: %d", args.flags);
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tls.cert);
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tls.key);
LogConfigUnindent(LogConfigGlobal());
args.handler = MatrixHttpHandler;
args.handlerArgs = &matrixArgs;
if (args.flags & HTTP_FLAG_TLS)
{
if (!UtilLastModified(serverCfg->tls.cert))
{
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.cert);
exit = EXIT_FAILURE;
goto finish;
}
if (UtilLastModified(serverCfg->tls.key))
{
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.key);
exit = EXIT_FAILURE;
goto finish;
}
}
server = HttpServerCreate(&args);
if (!server)
{
Log(LOG_ERR, "Unable to create HTTP server on port %d: %s",
serverCfg->port, strerror(errno));
exit = EXIT_FAILURE;
goto finish;
}
ArrayAdd(httpServers, server);
}
if (!ArraySize(httpServers))
{
Log(LOG_ERR, "No valid HTTP listeners specified in the configuration.");
exit = EXIT_FAILURE;
goto finish;
}
Log(LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid());
if (tConfig.runAs.uid && tConfig.runAs.gid)
{
userInfo = getpwnam(tConfig.runAs.uid);
groupInfo = getgrnam(tConfig.runAs.gid);
if (!userInfo || !groupInfo)
{
Log(LOG_ERR, "Unable to locate the user/group specified in the configuration.");
exit = EXIT_FAILURE;
goto finish;
}
else
{
Log(LOG_DEBUG, "Found user/group information using getpwnam() and getgrnam().");
}
}
else
{
Log(LOG_DEBUG, "No user/group info specified in the config.");
}
if (getuid() == 0)
{
if (userInfo && groupInfo)
{
if (setgid(groupInfo->gr_gid) != 0 || setuid(userInfo->pw_uid) != 0)
{
Log(LOG_ERR, "Unable to set process uid/gid.");
exit = EXIT_FAILURE;
goto finish;
}
else
{
Log(LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig.runAs.uid, tConfig.runAs.gid);
}
}
else
{
Log(LOG_WARNING, "We are running as root, and we are not dropping to another user");
Log(LOG_WARNING, "because none was specified in the configuration file.");
Log(LOG_WARNING, "This is probably a security issue.");
}
}
else
{
if (tConfig.runAs.uid && tConfig.runAs.gid)
{
if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid)
{
Log(LOG_WARNING, "Not running as the uid/gid specified in the configuration.");
}
else
{
Log(LOG_DEBUG, "Running as the uid/gid specified in the configuration.");
}
}
}
if (!tConfig.maxCache)
{
Log(LOG_WARNING, "Database caching is disabled.");
Log(LOG_WARNING, "If this is not what you intended, check the config file");
Log(LOG_WARNING, "and ensure that maxCache is a valid number of bytes.");
}
DbMaxCacheSet(matrixArgs.db, tConfig.maxCache);
ConfigUnlock(&tConfig);
cron = CronCreate(60 * 1000); /* 1-minute tick */
if (!cron)
{
Log(LOG_ERR, "Unable to set up job scheduler.");
exit = EXIT_FAILURE;
goto finish;
}
Log(LOG_DEBUG, "Registering jobs...");
CronEvery(cron, 30 * 60 * 1000, (JobFunc *) UiaCleanup, &matrixArgs);
Log(LOG_NOTICE, "Starting job scheduler...");
CronStart(cron);
Log(LOG_NOTICE, "Building routing tree...");
matrixArgs.router = RouterBuild();
if (!matrixArgs.router)
{
Log(LOG_ERR, "Unable to build routing tree.");
exit = EXIT_FAILURE;
goto finish;
}
Log(LOG_NOTICE, "Starting server...");
for (i = 0; i < ArraySize(httpServers); i++)
{
HttpServerConfig *serverCfg;
server = ArrayGet(httpServers, i);
serverCfg = HttpServerConfigGet(server);
if (!HttpServerStart(server))
{
Log(LOG_ERR, "Unable to start HTTP server %lu on port %hu.", i, serverCfg->port);
exit = EXIT_FAILURE;
goto finish;
}
else
{
Log(LOG_DEBUG, "Started HTTP server %lu.", i);
Log(LOG_INFO, "Listening on port: %hu", serverCfg->port);
}
}
sigAction.sa_handler = SignalHandler;
sigfillset(&sigAction.sa_mask);
sigAction.sa_flags = SA_RESTART;
#define SIGACTION(sig, act, oact) \
if (sigaction(sig, act, oact) < 0) \
{ \
Log(LOG_ERR, "Unable to install signal handler: %s", #sig); \
exit = EXIT_FAILURE; \
goto finish; \
} \
else \
{ \
Log(LOG_DEBUG, "Installed signal handler: %s", #sig); \
}
SIGACTION(SIGINT, &sigAction, NULL);
SIGACTION(SIGTERM, &sigAction, NULL);
SIGACTION(SIGPIPE, &sigAction, NULL);
SIGACTION(SIGUSR1, &sigAction, NULL);
#undef SIGACTION
/* Block this thread until the servers are terminated by a signal
* handler */
for (i = 0; i < ArraySize(httpServers); i++)
{
server = ArrayGet(httpServers, i);
HttpServerJoin(server);
Log(LOG_DEBUG, "Joined HTTP server %lu.", i);
}
finish:
Log(LOG_NOTICE, "Shutting down...");
if (httpServers)
{
for (i = 0; i < ArraySize(httpServers); i++)
{
Log(LOG_DEBUG, "Freeing HTTP server %lu...", i);
server = ArrayGet(httpServers, i);
HttpServerStop(server);
HttpServerFree(server);
Log(LOG_DEBUG, "Freed HTTP server %lu.", i);
}
ArrayFree(httpServers);
httpServers = NULL;
Log(LOG_DEBUG, "Freed HTTP servers array.");
}
if (cron)
{
Log(LOG_DEBUG, "Waiting on background jobs...");
CronStop(cron);
CronFree(cron);
Log(LOG_DEBUG, "Stopped and freed job scheduler.");
}
ConfigUnlock(&tConfig);
Log(LOG_DEBUG, "Unlocked configuration.");
DbClose(matrixArgs.db);
Log(LOG_DEBUG, "Closed database.");
HttpRouterFree(matrixArgs.router);
Log(LOG_DEBUG, "Freed routing tree.");
if (pidPath)
{
remove(pidPath);
Free(pidPath);
}
/*
* Uninstall the memory hook because it uses the Log
* API, whose configuration is being freed now, so it
* won't work anymore.
*/
MemoryHook(NULL, NULL);
StreamClose(logFile);
if (restart)
{
/*
* Change back into starting directory so initial chdir()
* call works.
*/
if (chdir(startDir) != 0)
{
/* TODO: Seems problematic, what do we do? */
}
goto start;
}
return exit;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
@ -25,53 +26,35 @@
#include <Matrix.h>
#include <string.h>
#include <ctype.h>
#include <Memory.h>
#include <HttpServer.h>
#include <Json.h>
#include <Util.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/HttpRouter.h>
#include <Routes.h>
void
MatrixHttpHandler(HttpServerContext * context, void *argp)
{
MatrixHttpHandlerArgs *args = (MatrixHttpHandlerArgs *) argp;
LogConfig *lc = args->lc;
HashMap *requestHeaders = HttpRequestHeaders(context);
FILE *stream;
char *key;
char *val;
HashMap *response;
Stream *stream;
HashMap *response = NULL;
char *requestPath;
MATRIX_PATH *pathParts;
char *pathPart;
RouteArgs routeArgs;
requestPath = HttpRequestPath(context);
stream = HttpServerStream(context);
Log(lc, LOG_INFO, "%s %s",
Log(LOG_DEBUG, "%s %s",
HttpRequestMethodToString(HttpRequestMethodGet(context)),
requestPath);
LogConfigIndent(lc);
Log(lc, LOG_DEBUG, "Request headers:");
LogConfigIndent(lc);
while (HashMapIterate(requestHeaders, &key, (void **) &val))
{
Log(lc, LOG_DEBUG, "%s: %s", key, val);
}
LogConfigUnindent(lc);
HttpResponseStatus(context, HTTP_OK);
HttpResponseHeader(context, "Server", "Telodendria/" TELODENDRIA_VERSION);
HttpResponseHeader(context, "Content-Type", "application/json");
/* CORS */
HttpResponseHeader(context, "Access-Control-Allow-Origin", "*");
@ -90,72 +73,50 @@ MatrixHttpHandler(HttpServerContext * context, void *argp)
HttpResponseStatus(context, HTTP_NO_CONTENT);
HttpSendHeaders(context);
goto finish;
}
pathParts = MATRIX_PATH_CREATE();
key = requestPath;
while ((pathPart = strtok_r(key, "/", &key)))
{
char *decoded = HttpUrlDecode(pathPart);
MATRIX_PATH_APPEND(pathParts, decoded);
return;
}
routeArgs.matrixArgs = args;
routeArgs.context = context;
routeArgs.path = pathParts;
pathPart = MATRIX_PATH_POP(pathParts);
if (MATRIX_PATH_EQUALS(pathPart, ".well-known"))
{
response = RouteWellKnown(&routeArgs);
}
else if (MATRIX_PATH_EQUALS(pathPart, "_matrix"))
{
response = RouteMatrix(&routeArgs);
}
else
if (!HttpRouterRoute(args->router, requestPath, &routeArgs, (void **) &response))
{
HttpResponseHeader(context, "Content-Type", "application/json");
HttpResponseStatus(context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
}
Free(pathPart);
HttpSendHeaders(context);
if (!response)
{
Log(lc, LOG_WARNING, "A route handler returned NULL.");
HttpResponseStatus(context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN);
}
stream = HttpStream(context);
JsonEncode(response, stream);
fprintf(stream, "\n");
/*
* By this point, there should be no path parts remaining, but if
* there are, free them up now.
* If the route handler returned a JSON object, take care
* of sending it here.
*
* Otherwise, if the route handler returned NULL, assume
* that it sent its own headers and and body.
*/
while ((pathPart = MATRIX_PATH_POP(pathParts)) != NULL)
if (response)
{
Free(pathPart);
char *contentLen = StrInt(JsonEncode(response, NULL, JSON_DEFAULT));
HttpResponseHeader(context, "Content-Type", "application/json");
HttpResponseHeader(context, "Content-Length", contentLen);
HttpSendHeaders(context);
Free(contentLen);
JsonEncode(response, stream, JSON_DEFAULT);
JsonFree(response);
StreamPrintf(stream, "\n");
}
MATRIX_PATH_FREE(pathParts);
JsonFree(response);
finish:
LogConfigUnindent(lc);
Log(LOG_INFO, "%s %s (%d %s)",
HttpRequestMethodToString(HttpRequestMethodGet(context)),
requestPath,
HttpResponseStatusGet(context),
HttpStatusToString(HttpResponseStatusGet(context)));
}
HashMap *
MatrixErrorCreate(MatrixError errorArg)
MatrixErrorCreate(MatrixError errorArg, char *msg)
{
HashMap *errorObj;
char *errcode;
@ -275,6 +236,10 @@ MatrixErrorCreate(MatrixError errorArg)
errcode = "M_MISSING_PARAM";
error = "A required parameter was missing from the request.";
break;
case M_INVALID_PARAM:
errcode = "M_INVALID_PARAM";
error = "A required parameter was invalid in some way.";
break;
case M_TOO_LARGE:
errcode = "M_TOO_LARGE";
error = "The request or entity was too large.";
@ -297,14 +262,101 @@ MatrixErrorCreate(MatrixError errorArg)
return NULL;
}
if (msg)
{
error = msg;
}
errorObj = HashMapCreate();
if (!errorObj)
{
return NULL;
}
HashMapSet(errorObj, "errcode", JsonValueString(UtilStringDuplicate(errcode)));
HashMapSet(errorObj, "error", JsonValueString(UtilStringDuplicate(error)));
HashMapSet(errorObj, "errcode", JsonValueString(errcode));
HashMapSet(errorObj, "error", JsonValueString(error));
return errorObj;
}
HashMap *
MatrixGetAccessToken(HttpServerContext * context, char **accessToken)
{
HashMap *params;
char *token;
params = HttpRequestHeaders(context);
token = HashMapGet(params, "authorization");
if (token)
{
/* If the header was provided but it's not given correctly,
* that's an error */
if (strncmp(token, "Bearer ", 7) != 0)
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
return MatrixErrorCreate(M_MISSING_TOKEN, NULL);
}
/* Seek past "Bearer" */
token += 7;
/* Seek past any spaces between "Bearer" and the token */
while (*token && isspace((unsigned char) *token))
{
token++;
}
}
else
{
/* Header was not provided, we must check for ?access_token */
params = HttpRequestParams(context);
token = HashMapGet(params, "access_token");
if (!token)
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
return MatrixErrorCreate(M_MISSING_TOKEN, NULL);
}
}
*accessToken = token;
return NULL;
}
HashMap *
MatrixRateLimit(HttpServerContext * context, Db * db)
{
/* TODO: Implement rate limiting */
(void) context;
(void) db;
return NULL;
}
HashMap *
MatrixClientWellKnown(char *base, char *identity)
{
HashMap *response;
HashMap *homeserver;
if (!base)
{
return NULL;
}
response = HashMapCreate();
homeserver = HashMapCreate();
HashMapSet(homeserver, "base_url", JsonValueString(base));
HashMapSet(response, "m.homeserver", JsonValueObject(homeserver));
if (identity)
{
HashMap *identityServer = HashMapCreate();
HashMapSet(identityServer, "base_url", JsonValueString(identity));
HashMapSet(response, "m.identity_server", JsonValueObject(identityServer));
}
return response;
}

View File

@ -1,421 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#ifndef TELODENDRIA_MEMORY_TABLE_CHUNK
#define TELODENDRIA_MEMORY_TABLE_CHUNK 256
#endif
struct MemoryInfo
{
size_t size;
const char *file;
int line;
void *pointer;
};
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static void (*hook) (MemoryAction, MemoryInfo *, void *) = NULL;
static void *hookArgs = NULL;
static MemoryInfo **allocations = NULL;
static size_t allocationsSize = 0;
static size_t allocationsLen = 0;
static size_t
MemoryHash(void *p)
{
return (((size_t) p) >> 2 * 7) % allocationsSize;
}
static int
MemoryInsert(MemoryInfo * a)
{
size_t hash;
if (!allocations)
{
allocationsSize = TELODENDRIA_MEMORY_TABLE_CHUNK;
allocations = calloc(allocationsSize, sizeof(void *));
if (!allocations)
{
return 0;
}
}
/* If the next insertion would cause the table to be at least 3/4
* full, re-allocate and re-hash. */
if ((allocationsLen + 1) >= ((allocationsSize * 3) >> 2))
{
size_t i;
size_t tmpAllocationsSize = allocationsSize;
MemoryInfo **tmpAllocations;
allocationsSize += TELODENDRIA_MEMORY_TABLE_CHUNK;
tmpAllocations = calloc(allocationsSize, sizeof(void *));
if (!tmpAllocations)
{
return 0;
}
for (i = 0; i < tmpAllocationsSize; i++)
{
if (allocations[i])
{
hash = MemoryHash(allocations[i]->pointer);
while (tmpAllocations[hash])
{
hash = (hash + 1) % allocationsSize;
}
tmpAllocations[hash] = allocations[i];
}
}
free(allocations);
allocations = tmpAllocations;
}
hash = MemoryHash(a->pointer);
while (allocations[hash])
{
hash = (hash + 1) % allocationsSize;
}
allocations[hash] = a;
allocationsLen++;
return 1;
}
static void
MemoryDelete(MemoryInfo * a)
{
size_t hash = MemoryHash(a->pointer);
size_t count = 0;
while (count <= allocationsSize)
{
if (allocations[hash] && allocations[hash] == a)
{
allocations[hash] = NULL;
allocationsLen--;
return;
}
else
{
hash = (hash + 1) % allocationsSize;
count++;
}
}
}
void *
MemoryAllocate(size_t size, const char *file, int line)
{
void *p;
MemoryInfo *a;
pthread_mutex_lock(&lock);
p = malloc(size);
if (!p)
{
pthread_mutex_unlock(&lock);
return NULL;
}
a = malloc(sizeof(MemoryInfo));
if (!a)
{
free(p);
pthread_mutex_unlock(&lock);
return NULL;
}
a->size = size;
a->file = file;
a->line = line;
a->pointer = p;
if (!MemoryInsert(a))
{
free(a);
free(p);
pthread_mutex_unlock(&lock);
return NULL;
}
if (hook)
{
hook(MEMORY_ALLOCATE, a, hookArgs);
}
pthread_mutex_unlock(&lock);
return p;
}
void *
MemoryReallocate(void *p, size_t size, const char *file, int line)
{
MemoryInfo *a;
void *new = NULL;
if (!p)
{
return MemoryAllocate(size, file, line);
}
a = MemoryInfoGet(p);
if (a)
{
pthread_mutex_lock(&lock);
new = realloc(a->pointer, size);
if (new)
{
MemoryDelete(a);
a->size = size;
a->file = file;
a->line = line;
a->pointer = new;
MemoryInsert(a);
if (hook)
{
hook(MEMORY_REALLOCATE, a, hookArgs);
}
}
pthread_mutex_unlock(&lock);
}
else if (hook)
{
a = malloc(sizeof(MemoryInfo));
if (a)
{
a->size = 0;
a->file = file;
a->line = line;
a->pointer = p;
hook(MEMORY_BAD_POINTER, a, hookArgs);
free(a);
}
}
return new;
}
void
MemoryFree(void *p, const char *file, int line)
{
MemoryInfo *a;
if (!p)
{
return;
}
a = MemoryInfoGet(p);
if (a)
{
pthread_mutex_lock(&lock);
if (hook)
{
a->file = file;
a->line = line;
hook(MEMORY_FREE, a, hookArgs);
}
MemoryDelete(a);
free(a->pointer);
free(a);
pthread_mutex_unlock(&lock);
}
else if (hook)
{
a = malloc(sizeof(MemoryInfo));
if (a)
{
a->file = file;
a->line = line;
a->size = 0;
a->pointer = p;
hook(MEMORY_BAD_POINTER, a, hookArgs);
free(a);
}
}
}
size_t
MemoryAllocated(void)
{
size_t i;
size_t total = 0;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
total += allocations[i]->size;
}
}
pthread_mutex_unlock(&lock);
return total;
}
void
MemoryFreeAll(void)
{
size_t i;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
free(allocations[i]->pointer);
free(allocations[i]);
}
}
free(allocations);
allocations = NULL;
allocationsSize = 0;
allocationsLen = 0;
pthread_mutex_unlock(&lock);
}
MemoryInfo *
MemoryInfoGet(void *p)
{
size_t hash, count;
pthread_mutex_lock(&lock);
hash = MemoryHash(p);
count = 0;
while (count <= allocationsSize)
{
if (!allocations[hash] || allocations[hash]->pointer != p)
{
hash = (hash + 1) % allocationsSize;
count++;
}
else
{
pthread_mutex_unlock(&lock);
return allocations[hash];
}
}
pthread_mutex_unlock(&lock);
return NULL;
}
size_t
MemoryInfoGetSize(MemoryInfo * a)
{
if (!a)
{
return 0;
}
return a->size;
}
const char *
MemoryInfoGetFile(MemoryInfo * a)
{
if (!a)
{
return NULL;
}
return a->file;
}
int
MemoryInfoGetLine(MemoryInfo * a)
{
if (!a)
{
return -1;
}
return a->line;
}
void *
MemoryInfoGetPointer(MemoryInfo * a)
{
if (!a)
{
return NULL;
}
return a->pointer;
}
void
MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args)
{
size_t i;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
iterFunc(allocations[i], args);
}
}
pthread_mutex_unlock(&lock);
}
void
MemoryHook(void (*memHook) (MemoryAction, MemoryInfo *, void *), void *args)
{
pthread_mutex_lock(&lock);
hook = memHook;
hookArgs = args;
pthread_mutex_unlock(&lock);
}

516
src/Parser.c Normal file
View File

@ -0,0 +1,516 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Parser.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
/* Iterate through a char **. */
#define Iterate(s) (*(*s)++)
/* Parse an extended localpart */
static bool
ParseUserLocalpart(char **str, char **out)
{
char c;
char *start;
size_t length;
if (!str || !out)
{
return false;
}
/* An extended localpart contains every ASCII printable character,
* except an ':'. */
start = *str;
while (isascii((c = Iterate(str))) && c != ':' && c)
{
/* Do nothing */
}
length = (size_t) (*str - start) - 1;
if (length < 1)
{
*str = start;
return false;
}
if (c == ':')
{
--(*str);
}
*out = Malloc(length + 1);
memcpy(*out, start, length);
(*out)[length] = '\0';
return true;
}
/* Parses an IPv4 address. */
static int
ParseIPv4(char **str, char **out)
{
/* Be *very* careful with this buffer */
char buffer[4];
char *start;
size_t length;
char c;
int digit = 0;
int digits = 0;
memset(buffer, 0, sizeof(buffer));
start = *str;
/* An IPv4 address is made of 4 blocks between 1-3 digits, like so:
* (1-3)*DIGIT.(1-3)*DIGIT.(1-3)*DIGIT.(1-3)*DIGIT */
while ((isdigit(c = Iterate(str)) || c == '.') && c && digits < 4)
{
if (isdigit(c))
{
digit++;
continue;
}
if (digit < 1 || digit > 3)
{
/* Current digit is too long for the spec! */
*str = start;
return false;
}
memcpy(buffer, *str - digit - 1, digit);
if (atoi(buffer) > 255)
{
/* Current digit is too large for the spec! */
*str = start;
return false;
}
memset(buffer, 0, sizeof(buffer));
digit = 0;
digits++; /* We have parsed a digit. */
}
if (c == '.' || digits != 3)
{
*str = start;
return false;
}
length = (size_t) (*str - start) - 1;
*out = Malloc(length + 1);
memcpy(*out, start, length);
(*str)--;
return true;
}
static bool
IsIPv6Char(char c)
{
return (isxdigit(c) || c == ':' || c == '.');
}
static bool
ParseIPv6(char **str, char **out)
{
char *start;
size_t length;
char c;
int filled = 0;
int digit = 0;
int digits = 0;
start = *str;
length = 0;
if (Iterate(str) != '[')
{
goto fail;
}
while ((c = Iterate(str)) && IsIPv6Char(c) && digits < 8)
{
char *ipv4;
if (isxdigit(c))
{
digit++;
length++;
continue;
}
if (c == ':')
{
if (**str == ':')
{
digit = 0;
if (!filled)
{
filled = 1;
length++;
c = Iterate(str); /* Skip over the character */
continue;
}
/* RFC3513 says the following:
* > 'The "::" can only appear once in an address.' */
*str = start;
return false;
}
if (digit < 1 || digit > 4)
{
goto fail;
}
/* We do not have to check whenever the digit here is valid,
* because it has to be. */
digit = 0;
digits++;
length++;
continue;
}
/* The only remaining character being '.', we are probably dealing
* with an IPv4 literal. */
*str -= digit + 1;
length -= digit + 1;
if (ParseIPv4(str, &ipv4))
{
length += strlen(ipv4);
Free(ipv4);
c = Iterate(str);
filled = 1;
goto end;
}
}
end:
--(*str);
if (Iterate(str) != ']')
{
goto fail;
}
length = (size_t) (*str - start);
if (length < 4 || length > 47)
{
goto fail;
}
*out = Malloc(length + 1);
memset(*out, '\0', length + 1);
memcpy(*out, start, length);
return true;
fail:
*str = start;
return false;
}
static bool
ParseHostname(char **str, char **out)
{
char *start;
size_t length = 0;
char c;
start = *str;
while ((c = Iterate(str)) &&
(isalnum(c) || c == '.' || c == '-') &&
++length < 256)
{
/* Do nothing. */
}
if (length < 1 || length > 255)
{
*str = start;
return false;
}
length = (size_t) (*str - start) - 1;
*out = Malloc(length + 1);
memcpy(*out, start, length);
(*str)--;
return true;
}
static bool
ParseServerName(char **str, ServerPart *out)
{
char c;
char *start;
char *startPort;
size_t chars = 0;
char *host = NULL;
char *port = NULL;
if (!str || !out)
{
return false;
}
start = *str;
if (!host)
{
/* If we can parse an IPv4 address, use that. */
ParseIPv4(str, &host);
}
if (!host)
{
/* If we can parse an IPv6 address, use that. */
ParseIPv6(str, &host);
}
if (!host)
{
/* If we can parse an hostname, use that. */
ParseHostname(str, &host);
}
if (!host)
{
/* Can't parse a valid server name. */
return false;
}
/* Now, there's only 2 options: a ':', or the end(everything else.) */
if (**str != ':')
{
/* We're done. */
out->hostname = host;
out->port = NULL;
return true;
}
/* TODO: Separate this out */
startPort = ++(*str);
while(isdigit(c = Iterate(str)) && c && ++chars < 5)
{
/* Do nothing. */
}
if (chars < 1 || chars > 5)
{
*str = start;
Free(host);
host = NULL;
return false;
}
port = Malloc(chars + 1);
memcpy(port, startPort, chars);
port[chars] = '\0';
if (atol(port) > 65535)
{
Free(port);
Free(host);
*str = start;
return false;
}
out->hostname = host;
out->port = port;
return true;
}
bool
ParseServerPart(char *str, ServerPart *part)
{
/* This is a wrapper behind the internal ParseServerName. */
if (!str || !part)
{
return false;
}
return ParseServerName(&str, part);
}
void
ServerPartFree(ServerPart part)
{
if (part.hostname)
{
Free(part.hostname);
}
if (part.port)
{
Free(part.port);
}
}
bool
ParseCommonID(char *str, CommonID *id)
{
char sigil;
if (!str || !id)
{
return false;
}
/* There must at least be 2 chararacters: the sigil and a string.*/
if (strlen(str) < 2)
{
return false;
}
sigil = *str++;
/* Some sigils have the following restriction:
* > MUST NOT exceed 255 bytes (including the # sigil and the domain).
*/
if ((sigil == '#' || sigil == '@') && strlen(str) > 255)
{
return false;
}
id->sigil = sigil;
id->local = NULL;
id->server.hostname = NULL;
id->server.port = NULL;
switch (sigil)
{
case '$':
/* For event IDs, it depends on the version, so we're just
* accepting it all. */
if (!ParseUserLocalpart(&str, &id->local))
{
return false;
}
if (*str == ':')
{
(*str)++;
if (!ParseServerName(&str, &id->server))
{
Free(id->local);
id->local = NULL;
return false;
}
return true;
}
break;
case '!':
case '#': /* It seems like the localpart should be the same as the
user's: everything, except ':'. */
case '@':
if (!ParseUserLocalpart(&str, &id->local))
{
return false;
}
if (*str++ != ':')
{
Free(id->local);
id->local = NULL;
return false;
}
if (!ParseServerName(&str, &id->server))
{
Free(id->local);
id->local = NULL;
return false;
}
break;
}
return true;
}
void
CommonIDFree(CommonID id)
{
if (id.local)
{
Free(id.local);
}
ServerPartFree(id.server);
}
bool
ValidCommonID(char *str, char sigil)
{
CommonID id;
bool ret;
memset(&id, 0, sizeof(CommonID));
if (!str)
{
return false;
}
ret = ParseCommonID(str, &id) && id.sigil == sigil;
CommonIDFree(id);
return ret;
}
char *
ParserRecomposeServerPart(ServerPart serverPart)
{
if (serverPart.hostname && serverPart.port)
{
return StrConcat(3, serverPart.hostname, ":", serverPart.port);
}
if (serverPart.hostname)
{
return StrDuplicate(serverPart.hostname);
}
return NULL;
}
char *
ParserRecomposeCommonID(CommonID id)
{
char *ret = Malloc(2 * sizeof(char));
ret[0] = id.sigil;
ret[1] = '\0';
if (id.local)
{
char *tmp = StrConcat(2, ret, id.local);
Free(ret);
ret = tmp;
}
if (id.server.hostname)
{
char *server = ParserRecomposeServerPart(id.server);
char *tmp = StrConcat(4, "@", ret, ":", server);
Free(ret);
Free(server);
ret = tmp;
}
return ret;
}
bool
ParserServerNameEquals(ServerPart serverPart, char *str)
{
char *idServer;
bool ret;
if (!str)
{
return false;
}
idServer = ParserRecomposeServerPart(serverPart);
ret = StrEquals(idServer, str);
Free(idServer);
return ret;
}

View File

@ -1,176 +0,0 @@
/*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Queue.h>
#include <Memory.h>
struct Queue
{
void **items;
size_t size;
size_t front;
size_t rear;
};
Queue *
QueueCreate(size_t size)
{
Queue *q;
if (!size)
{
/* Can't have a queue of length zero */
return NULL;
}
q = Malloc(sizeof(Queue));
if (!q)
{
return NULL;
}
q->items = Malloc(size * sizeof(void *));
if (!q->items)
{
Free(q);
return NULL;
}
q->size = size;
q->front = size + 1;
q->rear = size + 1;
return q;
}
void
QueueFree(Queue * q)
{
if (q)
{
Free(q->items);
}
Free(q);
}
int
QueueFull(Queue * q)
{
if (!q)
{
return 0;
}
return ((q->front == q->rear + 1) || (q->front == 0 && q->rear == q->size - 1));
}
int
QueueEmpty(Queue * q)
{
if (!q)
{
return 0;
}
return q->front == q->size + 1;
}
int
QueuePush(Queue * q, void *element)
{
if (!q || !element)
{
return 0;
}
if (QueueFull(q))
{
return 0;
}
if (q->front == q->size + 1)
{
q->front = 0;
}
if (q->rear == q->size + 1)
{
q->rear = 0;
}
else
{
q->rear = (q->rear + 1) % q->size;
}
q->items[q->rear] = element;
return 1;
}
void *
QueuePop(Queue * q)
{
void *element;
if (!q)
{
return NULL;
}
if (QueueEmpty(q))
{
return NULL;
}
element = q->items[q->front];
if (q->front == q->rear)
{
q->front = q->size + 1;
q->rear = q->size + 1;
}
else
{
q->front = (q->front + 1) % q->size;
}
return element;
}
void *
QueuePeek(Queue * q)
{
if (!q)
{
return NULL;
}
if (QueueEmpty(q))
{
return NULL;
}
return q->items[q->front];
}

241
src/RegToken.c Normal file
View File

@ -0,0 +1,241 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <RegToken.h>
#include <string.h>
#include <ctype.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <User.h>
int
RegTokenValid(RegTokenInfo * token)
{
HashMap *tokenJson;
int64_t uses, used;
uint64_t expiration;
if (!token || !RegTokenExists(token->db, token->name))
{
return 0;
}
tokenJson = DbJson(token->ref);
uses = JsonValueAsInteger(HashMapGet(tokenJson, "uses"));
used = JsonValueAsInteger(HashMapGet(tokenJson, "used"));
expiration = JsonValueAsInteger(HashMapGet(tokenJson, "expires_on"));
return (!expiration || (UtilTsMillis() < expiration)) && (uses == -1 || used < uses);
}
void
RegTokenUse(RegTokenInfo * token)
{
HashMap *tokenJson;
if (!token || !RegTokenExists(token->db, token->name))
{
return;
}
if (token->uses >= 0 && token->used >= token->uses)
{
return;
}
token->used++;
/* Write the information to the hashmap */
tokenJson = DbJson(token->ref);
JsonValueFree(HashMapSet(tokenJson, "used", JsonValueInteger(token->used)));
}
int
RegTokenExists(Db * db, char *token)
{
if (!token || !db)
{
return 0;
}
return DbExists(db, 3, "tokens", "registration", token);
}
int
RegTokenDelete(RegTokenInfo * token)
{
if (!token || !RegTokenClose(token))
{
return 0;
}
if (!DbDelete(token->db, 3, "tokens", "registration", token->name))
{
return 0;
}
RegTokenInfoFree(token);
Free(token);
return 1;
}
RegTokenInfo *
RegTokenGetInfo(Db * db, char *token)
{
RegTokenInfo *ret;
DbRef *tokenRef;
HashMap *tokenJson;
char *errp = NULL;
if (!RegTokenExists(db, token))
{
return NULL;
}
tokenRef = DbLock(db, 3, "tokens", "registration", token);
if (!tokenRef)
{
return NULL;
}
tokenJson = DbJson(tokenRef);
ret = Malloc(sizeof(RegTokenInfo));
if (!RegTokenInfoFromJson(tokenJson, ret, &errp))
{
Log(LOG_ERR, "RegTokenGetInfo(): Database decoding error: %s", errp);
RegTokenFree(ret);
return NULL;
}
ret->db = db;
ret->ref = tokenRef;
return ret;
}
void
RegTokenFree(RegTokenInfo *tokeninfo)
{
if (tokeninfo)
{
RegTokenInfoFree(tokeninfo);
Free(tokeninfo);
}
}
int
RegTokenClose(RegTokenInfo * tokeninfo)
{
HashMap *json;
if (!tokeninfo)
{
return 0;
}
/* Write object to database. */
json = RegTokenInfoToJson(tokeninfo);
DbJsonSet(tokeninfo->ref, json); /* Copies json into internal structure. */
JsonFree(json);
return DbUnlock(tokeninfo->db, tokeninfo->ref);
}
static int
RegTokenVerify(char *token)
{
size_t i, size;
char c;
if (!token)
{
return 0;
}
/* The spec says the following: "The token required for this
* authentication [...] is an opaque string with maximum length of
* 64 characters in the range [A-Za-z0-9._~-]." */
if ((size = strlen(token)) > 64)
{
return 0;
}
for (i = 0; i < size; i++)
{
c = token[i];
if (!(isalnum(c) || c == '0' || c == '_' || c == '~' || c == '-'))
{
return 0;
}
}
return 1;
}
RegTokenInfo *
RegTokenCreate(Db * db, char *name, char *owner, uint64_t expires, int64_t uses, int privileges)
{
RegTokenInfo *ret;
uint64_t timestamp = UtilTsMillis();
if (!db || !name)
{
return NULL;
}
/* -1 indicates infinite uses; zero and all positive values are a
* valid number of uses; althought zero would be rather useless.
* Anything less than -1 doesn't make sense. */
if (uses < -1)
{
return NULL;
}
/* Verify the token */
if (!RegTokenVerify(name) || ((expires > 0) && (expires < timestamp)))
{
return NULL;
}
ret = Malloc(sizeof(RegTokenInfo));
/* Set the token's properties */
ret->db = db;
ret->ref = DbCreate(db, 3, "tokens", "registration", name);
if (!ret->ref)
{
/* RegToken already exists or some weird fs error */
Free(ret);
return NULL;
}
ret->name = StrDuplicate(name);
ret->created_by = StrDuplicate(owner);
ret->used = 0;
ret->uses = uses;
ret->created_on = timestamp;
ret->expires_on = expires;
ret->grants = UserEncodePrivileges(privileges);
return ret;
}

124
src/Room.c Normal file
View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Room.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Db.h>
#include <Schema/RoomCreateRequest.h>
struct Room
{
Db *db;
DbRef *ref;
char *id;
int version;
};
Room *
RoomCreate(Db * db, RoomCreateRequest * req)
{
(void) db;
(void) req;
return NULL;
}
Room *
RoomLock(Db * db, char *id)
{
DbRef *ref;
Room *room;
if (!db || !id)
{
return NULL;
}
ref = DbLock(db, 3, "rooms", id, "state");
if (!ref)
{
return NULL;
}
room = Malloc(sizeof(Room));
if (!room)
{
DbUnlock(db, ref);
return NULL;
}
room->db = db;
room->ref = ref;
room->id = StrDuplicate(id);
return room;
}
int
RoomUnlock(Room * room)
{
Db *db;
DbRef *ref;
if (!room)
{
return 0;
}
db = room->db;
ref = room->ref;
Free(room->id);
Free(room);
return DbUnlock(db, ref);
}
char *
RoomIdGet(Room * room)
{
return room ? room->id : NULL;
}
int
RoomVersionGet(Room * room)
{
return room ? room->version : 0;
}
HashMap *
RoomStateGet(Room * room)
{
if (!room)
{
return NULL;
}
return NULL;
}

97
src/Routes.c Normal file
View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Routes.h>
HttpRouter *
RouterBuild(void)
{
HttpRouter *router = HttpRouterCreate();
if (!router)
{
return NULL;
}
#define R(path, func) \
if (!HttpRouterAdd(router, path, func)) \
{ \
Log(LOG_ERR, "Unable to add route: %s", path); \
HttpRouterFree(router); \
return NULL; \
}
/* Matrix Specifification Routes */
R("/.well-known/matrix/(client|server)", RouteWellKnown);
R("/_matrix/client/versions", RouteVersions);
R("/_matrix/static", RouteStaticDefault);
R("/_matrix/static/telodendria\\.(js|css)", RouteStaticResources);
R("/_matrix/static/client/login", RouteStaticLogin);
R("/_matrix/client/v3/auth/(.*)/fallback/web", RouteUiaFallback);
R("/_matrix/client/v3/capabilities", RouteCapabilities);
R("/_matrix/client/v3/login", RouteLogin);
R("/_matrix/client/v3/logout", RouteLogout);
R("/_matrix/client/v3/logout/(all)", RouteLogout);
R("/_matrix/client/v3/register", RouteRegister);
R("/_matrix/client/v3/register/(available)", RouteRegister);
R("/_matrix/client/v3/refresh", RouteRefresh);
R("/_matrix/client/v3/account/whoami", RouteWhoami);
R("/_matrix/client/v3/account/password", RouteChangePwd);
R("/_matrix/client/v3/account/deactivate", RouteDeactivate);
R("/_matrix/client/v1/register/m.login.registration_token/validity", RouteTokenValid);
R("/_matrix/client/v3/account/password/(email|msisdn)/requestToken", RouteRequestToken);
R("/_matrix/client/v3/register/(email|msisdn)/requestToken", RouteRequestToken);
R("/_matrix/client/v3/profile/(.*)", RouteUserProfile);
R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile);
R("/_matrix/client/v3/user/(.*)/filter", RouteFilter);
R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter);
R("/_matrix/client/v3/createRoom", RouteCreateRoom);
R("/_matrix/client/v3/directory/room/(.*)", RouteAliasDirectory);
R("/_matrix/client/v3/rooms/(.*)/aliases", RouteRoomAliases);
/* Telodendria Admin API Routes */
R("/_telodendria/admin/v1/(restart|shutdown|stats)", RouteProcControl);
R("/_telodendria/admin/v1/config", RouteConfig);
R("/_telodendria/admin/v1/privileges", RoutePrivileges);
R("/_telodendria/admin/v1/privileges/(.*)", RoutePrivileges);
R("/_telodendria/admin/v1/deactivate/(.*)", RouteAdminDeactivate);
R("/_telodendria/admin/v1/tokens/(.*)", RouteAdminTokens);
R("/_telodendria/admin/v1/tokens", RouteAdminTokens);
#undef R
return router;
}

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