Compare commits

...

65 Commits

Author SHA1 Message Date
lda b72f18538d Merge branch 'master' of https://git.telodendria.io/Telodendria/Telodendria
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
2024-05-01 21:02:08 +02:00
lda ff85b72899 Fix IPv6 issue in parser (#52)
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: Telodendria/Telodendria#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)
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: Telodendria/Telodendria#53
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-03-29 11:48:04 -05:00
lda f815f653b0 Merge branch 'master' of https://git.telodendria.io/Telodendria/Telodendria
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
2024-03-20 15:29:16 +01:00
Jordan Bancino dede82ad33 Update docs/dev/hosting.md 2024-01-14 14:13:53 -05:00
Jordan Bancino fde2b26857 Update docs/dev/hosting.md 2024-01-14 09:43:47 -05:00
Jordan Bancino 6305f5d76e Update docs/dev/hosting.md 2024-01-14 09:36:57 -05:00
Jordan Bancino 95a5f6f087 Fix compile warnings on 32-bit systems. 2024-01-13 20:41:29 -05:00
Jordan Bancino 129802fe94 Fix ordering of LDFLAGS. 2024-01-13 20:31:49 -05:00
Jordan Bancino c7d44866c3 Put -L before -l in LDFLAGS. 2024-01-13 20:26:46 -05:00
Jordan Bancino 85672985eb Fix compiler warnings on 32-bit platforms in json.c. 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. 2024-01-13 20:14:42 -05:00
Jordan Bancino cd22aea772 Require Cytoplasm to be compiled separately.
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: Telodendria/Telodendria#51
2024-01-11 19:33:50 -05:00
lda 243de4f1a0 Add new entry to CONTRIBUTORS.txt (#50)
Reviewed-on: Telodendria/Telodendria#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: Telodendria/Telodendria#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: Telodendria/Telodendria#48
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-01-04 19:32:03 -05:00
lda 8231e8a078
[LICENSE] Update licensing for 2024 2024-01-05 01:13:56 +01:00
lda 12061afe62 Merge branch 'master' of https://git.telodendria.io/Telodendria/Telodendria 2024-01-05 01:09:13 +01:00
lda 2c6d5194d2 Set an error message with MatrixErrorCreate whenever applicable (#45)
Closes #6.

Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: Telodendria/Telodendria#45
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2023-12-02 10:24:08 -05:00
lda 18488d463e
[FIX] Remove Cytoplasm. 2023-11-30 19:25:04 +01:00
lda 184a0ef1de Merge branch 'master' of https://git.telodendria.io/lda/Telodendria 2023-11-30 19:10:06 +01:00
lda e8d6f0f857 [FIX] Start fixing merging issue 2023-11-30 19:08:44 +01:00
lda 829b9bd6b4 Merge remote-tracking branch 'upstream/master' 2023-11-30 19:07:18 +01: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: Telodendria/Telodendria#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 f4cb10804a Add change log entry for #35. 2023-09-11 19:53:31 +02:00
Jordan Bancino 3f69954ca7 Convert configuration documentation. 2023-09-11 19:53:31 +02:00
lda afc9e0e5dc 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 19:53:31 +02:00
Jordan Bancino 5067b5bcf0 Remove `send-patch` and `tp`. See #20. 2023-09-11 19:53:31 +02:00
Jordan Bancino 36c07ed17d Put the finishing touches on `CONTRIBUTING.md`. 2023-09-11 19:53:31 +02:00
Jordan Bancino d7afd6285d Add contributing documentation. 2023-09-11 19:53:31 +02:00
Jordan Bancino 69e7837fb4 Fix issue config. 2023-09-11 19:53:31 +02:00
Jordan Bancino 6f9ac042b5 Don't allow blank issues. 2023-09-11 19:53:31 +02:00
Jordan Bancino 9b65558a5a Fix a few small bugs in Gitea templates. 2023-09-11 19:53:31 +02:00
Jordan Bancino 16a45e67e6 Add Gitea issue templates. 2023-09-11 19:53:31 +02:00
Jordan Bancino afde23df70 Fix table rendering in stats.md 2023-09-11 19:53:31 +02:00
Jordan Bancino b8c99a2b1f Finish moving over current Administrator API documentation. 2023-09-11 19:53:31 +02:00
Jordan Bancino c781950fff Another typo. 2023-09-11 19:53:30 +02:00
Jordan Bancino 60ebbccf7d Fix typo in privileges.md. 2023-09-11 19:53:30 +02:00
Jordan Bancino 73dd12ef7e Add privileges documentation. 2023-09-11 19:53:30 +02:00
Jordan Bancino 20c4a4d0fb Fix typo in docs/user/admin/README.md 2023-09-11 19:53:30 +02:00
Jordan Bancino 4168557c4b Add admin documentation home page. 2023-09-11 19:53:30 +02:00
Jordan Bancino 5ad199353b Add logo and center title. 2023-09-08 22:54:05 +02:00
Jordan Bancino b32aababb3 Add technical rationale document. 2023-09-08 22:54:05 +02:00
Jordan Bancino 82d4711f7f Add a nice README which will serve as the basis for the website. 2023-09-08 22:54:05 +02:00
Jordan Bancino 66537d1b0c Add repository structure documentation. 2023-09-08 22:54:05 +02:00
Jordan Bancino 6eb16af9b5 Add setup documentation. 2023-09-08 22:54:05 +02:00
Jordan Bancino cb6963299c Add porting instructions. 2023-09-08 22:54:05 +02:00
Jordan Bancino c2294723e6 Add usage and install documentation. 2023-09-08 22:54:05 +02:00
Jordan Bancino 5c7ce30b15 Add documentation home page. 2023-09-08 22:54:05 +02:00
Jordan Bancino 63a6bcce1a Remove old change log. 2023-09-08 22:54:05 +02:00
Jordan Bancino 6f14d0eef3 Add `CHANGELOG.md` 2023-09-08 22:54:05 +02:00
lda d9217946f3
[FIX] Fix commit f61009a423's mistake 2023-09-08 16:54:07 +02:00
77 changed files with 2042 additions and 1840 deletions

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

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

View File

@ -1,6 +1,6 @@
<h1 style="text-align: center;">Telodendria</h1>
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
Telodendria is an extremely powerful, yet lightweight and portable
**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
@ -79,7 +79,7 @@ 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 May of 2024. This
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.

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"
}
}
}

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"
}

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"
}

112
configure vendored
View File

@ -13,13 +13,40 @@ SRC="src"
INCLUDE="src/include"
TOOLS="tools/src"
SCHEMA="Schema"
CYTOPLASM="Cytoplasm"
CFLAGS="-Wall -Wextra -pedantic -std=c89 -O3 -pipe -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
LIBS="-lm -pthread -lCytoplasm"
# Set default args for all platforms
SCRIPT_ARGS="--cc=cc --prefix=/usr/local --enable-ld-extra --bin-name=telodendria --version=1.7.0-alpha4 --static $@"
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"
@ -29,16 +56,18 @@ 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-)
;;
--enable-ld-extra)
LD_EXTRA="-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections"
;;
--disable-ld-extra)
LD_EXTRA=""
;;
--bin-name=*)
BIN_NAME=$(echo "$arg" | cut -d '=' -f 2-)
;;
@ -47,20 +76,22 @@ for arg in $SCRIPT_ARGS; do
;;
--enable-debug)
DEBUG="-O0 -g"
echo "Notice: --enable-debug implies --disable-ld-extra and --no-static."
echo "You must explicitly provide --enable-ld-extra and/or --static after"
echo "specifying --enable-debug if you wish to enable these features in debug mode."
LD_EXTRA=""
STATIC=""
;;
--disable-debug)
DEBUG=""
;;
--static)
STATIC="-static -Wl,-static"
;;
--no-static)
STATIC=""
--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"
@ -70,7 +101,7 @@ for arg in $SCRIPT_ARGS; do
done
CFLAGS="${CFLAGS} '-DTELODENDRIA_VERSION=\"${VERSION}\"' ${DEBUG}"
LDFLAGS="${LIBS} ${LD_EXTRA}"
LDFLAGS="${LDFLAGS} ${LIBS}"
#
# Makefile generation
@ -103,6 +134,16 @@ prefix() {
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"
}
@ -111,11 +152,27 @@ 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=$(${CC} -I${INCLUDE} -I${BUILD} -MM -MT "${obj}" "${src}")
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}\""
@ -129,7 +186,7 @@ compile_bin() {
echo "${out}: ${src}"
echo "${TAB}@mkdir -p ${OUT}/bin"
echo "${TAB}\$(CC) \$(CFLAGS) -o \"${out}\" \"${src}\" $depObjs \$(LDFLAGS) ${STATIC}"
echo "${TAB}\$(CC) \$(CFLAGS) -o \"${out}\" \"${src}\" $depObjs \$(LDFLAGS)"
}
compile_doc() {
@ -142,7 +199,7 @@ compile_doc() {
echo "${out}: ${src}"
echo "${TAB}@mkdir -p ${OUT}/man/man3"
echo "${TAB}hdoc -D \"Os=${BIN_NAME}\" -i \"${src}\" -o \"${out}\""
echo "${TAB}$(cytoplasm_tool hdoc) -D \"Os=${BIN_NAME}\" -i \"${src}\" -o \"${out}\""
}
print_doc() {
@ -160,11 +217,11 @@ compile_schema() {
echo "${BUILD}/Schema/${out}.h:"
echo "${TAB}@mkdir -p ${BUILD}/Schema"
echo "${TAB}j2s -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
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}j2s -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
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"
@ -247,6 +304,13 @@ $(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

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -31,6 +31,7 @@ The following endpoints were added:
### 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.
@ -38,9 +39,15 @@ with certain Matrix clients. (#35)
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.
@ -53,6 +60,10 @@ the ability to change only a subset of the configuration.
- **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

View File

@ -213,3 +213,44 @@ 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.

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.

View File

@ -16,10 +16,10 @@ registration tokens.
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 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.
- **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

View File

@ -19,8 +19,7 @@ key-value form:
"serverName": "telodendria.io",
"listen": [
{
"port": 8008,
"tls": false
"port": 8008
}
]
@ -51,7 +50,7 @@ Here are the top-level directives:
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|null|false`
- **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
@ -106,6 +105,9 @@ Here are the top-level directives:
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,

View File

@ -1,138 +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.">
<meta http-equiv="onion-location" content="http://cszrjvqbeim4dbbynucd4xucpfsips4alpxyxm5s3uh2itjxsnythhyd.onion">
<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>${TELODENDRIA_VERSION}</td>
<td>
<a href="/pub/v${TELODENDRIA_VERSION}/Telodendria-v${TELODENDRIA_VERSION}.tar.gz">
<code>Telodendria-v${TELODENDRIA_VERSION}.tar.gz</code>
</a>
</td>
<td>
<a href="/pub/v${TELODENDRIA_VERSION}/Telodendria-v${TELODENDRIA_VERSION}.tar.gz.sha256">
sha256
</a>
</td>
<td>
<a href="/pub/v${TELODENDRIA_VERSION}/Telodendria-v${TELODENDRIA_VERSION}.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>
${USER_DOCS}
<br>
<details>
<summary>Developer Documentation:</summary>
<p>
This documentation is intended primarily for developers. It details all
of the internal workings of Telodendria.
</p>
${DEV_DOCS}
</details>
<hr>
<p>
&copy; 2023 Jordan Bancino &lt;@jordan:bancino.net&gt;
<br>
Updated on ${DATE}.
</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,114 +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
}
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) !important;
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);
}
/* Thanks Jonah! */
#logo {
display: block;
margin: auto;
width: 50vw;
max-width: 400px;
}
/* Mandoc overrides */
.Bd {
background-color: var(--color-snippet);
border-radius: var(--border-radius);
padding-left: 10px;
overflow: auto;
}
.Nm {
width: fit-content;
}
.Nm td, .Nm th {
border: none;
}

View File

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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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,405 +22,88 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Config.h>
#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/HttpServer.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Int64.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
#define CONFIG_REQUIRE(key, type) \
value = HashMapGet(config, key); \
if (!value) \
{ \
tConfig->err = "Missing required " key " directive."; \
goto error; \
} \
if (JsonValueType(value) == JSON_NULL) \
{ \
tConfig->err = "Missing value for " key " directive."; \
goto error; \
} \
if (JsonValueType(value) != type) \
{ \
tConfig->err = "Expected " key " to be of type " #type; \
goto error; \
}
#define CONFIG_COPY_STRING(into) \
into = StrDuplicate(JsonValueAsString(value));
#define CONFIG_OPTIONAL_STRING(into, key, default) \
value = HashMapGet(config, key); \
if (value && JsonValueType(value) != JSON_NULL) \
{ \
if (JsonValueType(value) != JSON_STRING) \
{ \
tConfig->err = "Expected " key " to be of type JSON_STRING"; \
goto error; \
} \
into = StrDuplicate(JsonValueAsString(value)); \
} \
else \
{ \
into = default ? StrDuplicate(default) : NULL; \
}
#define CONFIG_OPTIONAL_INTEGER(into, key, default) \
value = HashMapGet(config, key); \
if (value && JsonValueType(value) != JSON_NULL) \
{ \
if (JsonValueType(value) != JSON_INTEGER) \
{ \
tConfig->err = "Expected " key " to be of type JSON_INTEGER"; \
goto error; \
} \
into = Int64Low(JsonValueAsInteger(value)); \
} \
else \
{ \
into = default; \
}
static int
ConfigParseRunAs(Config * tConfig, HashMap * config)
{
JsonValue *value;
CONFIG_REQUIRE("uid", JSON_STRING);
CONFIG_COPY_STRING(tConfig->uid);
CONFIG_OPTIONAL_STRING(tConfig->gid, "gid", tConfig->uid);
return 1;
error:
return 0;
}
static int
ConfigParseListen(Config * tConfig, Array * listen)
void
ConfigParse(HashMap * config, Config *tConfig)
{
size_t i;
if (!ArraySize(listen))
{
tConfig->err = "Listen array cannot be empty; you must specify at least one listener.";
goto error;
}
if (!tConfig->servers)
{
tConfig->servers = ArrayCreate();
if (!tConfig->servers)
{
tConfig->err = "Unable to allocate memory for listener configurations.";
goto error;
}
}
for (i = 0; i < ArraySize(listen); i++)
{
JsonValue *val = ArrayGet(listen, i);
HashMap *obj;
HttpServerConfig *serverCfg = Malloc(sizeof(HttpServerConfig));
if (!serverCfg)
{
tConfig->err = "Unable to allocate memory for listener configuration.";
goto error;
}
if (JsonValueType(val) != JSON_OBJECT)
{
tConfig->err = "Invalid value in listener array. All listeners must be objects.";
goto error;
}
obj = JsonValueAsObject(val);
serverCfg->port = Int64Low(JsonValueAsInteger(HashMapGet(obj, "port")));
serverCfg->threads = Int64Low(JsonValueAsInteger(HashMapGet(obj, "threads")));
serverCfg->maxConnections = Int64Low(JsonValueAsInteger(HashMapGet(obj, "maxConnections")));
if (!serverCfg->port)
{
Free(serverCfg);
continue;
}
if (!serverCfg->threads)
{
serverCfg->threads = 4;
}
if (!serverCfg->maxConnections)
{
serverCfg->maxConnections = 32;
}
val = HashMapGet(obj, "tls");
if ((JsonValueType(val) == JSON_BOOLEAN && !JsonValueAsBoolean(val)) || JsonValueType(val) == JSON_NULL)
{
serverCfg->flags = HTTP_FLAG_NONE;
serverCfg->tlsCert = NULL;
serverCfg->tlsKey = NULL;
}
else if (JsonValueType(val) != JSON_OBJECT)
{
tConfig->err = "Invalid value for listener.tls. It must be an object.";
goto error;
}
else
{
serverCfg->flags = HTTP_FLAG_TLS;
obj = JsonValueAsObject(val);
serverCfg->tlsCert = StrDuplicate(JsonValueAsString(HashMapGet(obj, "cert")));
serverCfg->tlsKey = StrDuplicate(JsonValueAsString(HashMapGet(obj, "key")));
if (!serverCfg->tlsCert || !serverCfg->tlsKey)
{
tConfig->err = "TLS cert and key must both be valid file names.";
goto error;
}
}
ArrayAdd(tConfig->servers, serverCfg);
}
return 1;
error:
return 0;
}
static int
ConfigParseLog(Config * tConfig, HashMap * config)
{
JsonValue *value;
char *str;
CONFIG_REQUIRE("output", JSON_STRING);
str = JsonValueAsString(value);
if (StrEquals(str, "stdout"))
{
tConfig->flags |= CONFIG_LOG_STDOUT;
}
else if (StrEquals(str, "file"))
{
tConfig->flags |= CONFIG_LOG_FILE;
}
else if (StrEquals(str, "syslog"))
{
tConfig->flags |= CONFIG_LOG_SYSLOG;
}
else
{
tConfig->err = "Invalid value for log.output";
goto error;
}
CONFIG_OPTIONAL_STRING(str, "level", "message");
if (StrEquals(str, "message"))
{
tConfig->logLevel = LOG_INFO;
}
else if (StrEquals(str, "debug"))
{
tConfig->logLevel = LOG_DEBUG;
}
else if (StrEquals(str, "notice"))
{
tConfig->logLevel = LOG_NOTICE;
}
else if (StrEquals(str, "warning"))
{
tConfig->logLevel = LOG_WARNING;
}
else if (StrEquals(str, "error"))
{
tConfig->logLevel = LOG_ERR;
}
else
{
tConfig->err = "Invalid value for log.level.";
goto error;
}
Free(str);
CONFIG_OPTIONAL_STRING(tConfig->logTimestamp, "timestampFormat", "default");
if (StrEquals(tConfig->logTimestamp, "none"))
{
Free(tConfig->logTimestamp);
tConfig->logTimestamp = NULL;
}
value = HashMapGet(config, "color");
if (value && JsonValueType(value) != JSON_NULL)
{
if (JsonValueType(value) != JSON_BOOLEAN)
{
tConfig->err = "Expected type JSON_BOOLEAN for log.color.";
goto error;
}
if (JsonValueAsBoolean(value))
{
tConfig->flags |= CONFIG_LOG_COLOR;
}
}
return 1;
error:
return 0;
}
void
ConfigFree(Config * tConfig)
{
if (!tConfig)
{
return;
}
Free(tConfig->serverName);
Free(tConfig->baseUrl);
Free(tConfig->identityServer);
Free(tConfig->uid);
Free(tConfig->gid);
Free(tConfig->logTimestamp);
if (tConfig->servers)
{
size_t i;
for (i = 0; i < ArraySize(tConfig->servers); i++)
{
HttpServerConfig *serverCfg = ArrayGet(tConfig->servers, i);
Free(serverCfg->tlsCert);
Free(serverCfg->tlsKey);
Free(serverCfg);
}
ArrayFree(tConfig->servers);
}
Free(tConfig);
}
Config *
ConfigParse(HashMap * config)
{
Config *tConfig;
JsonValue *value;
if (!config)
{
return NULL;
}
tConfig = Malloc(sizeof(Config));
if (!tConfig)
{
return NULL;
tConfig->ok = 0;
tConfig->err = "Invalid object given as config.";
return;
}
memset(tConfig, 0, sizeof(Config));
CONFIG_REQUIRE("listen", JSON_ARRAY);
if (!ConfigParseListen(tConfig, JsonValueAsArray(value)))
tConfig->maxCache = 0;
if (!ConfigFromJson(config, tConfig, &tConfig->err))
{
ConfigFree(tConfig);
goto error;
}
CONFIG_REQUIRE("serverName", JSON_STRING);
CONFIG_COPY_STRING(tConfig->serverName);
value = HashMapGet(config, "baseUrl");
if (value)
{
CONFIG_COPY_STRING(tConfig->baseUrl);
}
else
if (!tConfig->baseUrl)
{
size_t len = strlen(tConfig->serverName) + 10;
tConfig->baseUrl = Malloc(len);
if (!tConfig->baseUrl)
{
tConfig->err = "Error allocating memory for default config value 'baseUrl'.";
tConfig->err = "Couldn't allocate enough memory for 'baseUrl'.";
goto error;
}
snprintf(tConfig->baseUrl, len, "https://%s", tConfig->serverName);
snprintf(tConfig->baseUrl, len, "https://%s/", tConfig->serverName);
}
CONFIG_OPTIONAL_STRING(tConfig->identityServer, "identityServer", NULL);
value = HashMapGet(config, "runAs");
if (value && JsonValueType(value) != JSON_NULL)
if (!tConfig->log.timestampFormat)
{
if (JsonValueType(value) == JSON_OBJECT)
tConfig->log.timestampFormat = StrDuplicate("default");
}
for (i = 0; i < ArraySize(tConfig->listen); i++)
{
ConfigListener *listener = ArrayGet(tConfig->listen, i);
if (!listener->maxConnections)
{
if (!ConfigParseRunAs(tConfig, JsonValueAsObject(value)))
{
goto error;
}
listener->maxConnections = 32;
}
else
if (!listener->threads)
{
tConfig->err = "Config directive 'runAs' should be a JSON object that contains a 'uid' and 'gid'.";
goto error;
listener->threads = 4;
}
if (!listener->port)
{
listener->port = 8008;
}
}
CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0);
CONFIG_REQUIRE("federation", JSON_BOOLEAN);
if (JsonValueAsBoolean(value))
{
tConfig->flags |= CONFIG_FEDERATION;
}
CONFIG_REQUIRE("registration", JSON_BOOLEAN);
if (JsonValueAsBoolean(value))
{
tConfig->flags |= CONFIG_REGISTRATION;
}
CONFIG_REQUIRE("log", JSON_OBJECT);
if (!ConfigParseLog(tConfig, JsonValueAsObject(value)))
{
goto error;
}
tConfig->ok = 1;
tConfig->err = NULL;
return tConfig;
return;
error:
tConfig->ok = 0;
return tConfig;
return;
}
int
@ -431,75 +115,91 @@ ConfigExists(Db * db)
int
ConfigCreateDefault(Db * db)
{
DbRef *ref;
Config config;
ConfigListener *listener;
HashMap *json;
Array *listeners;
HashMap *listen;
JsonValue *val;
char hostname[HOST_NAME_MAX + 1];
DbRef *ref;
if (!db)
{
return 0;
}
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);
json = DbJson(ref);
ConfigFree(&config);
JsonFree(json);
JsonSet(json, JsonValueString("file"), 2, "log", "output");
listeners = ArrayCreate();
listen = HashMapCreate();
HashMapSet(listen, "port", JsonValueInteger(Int64Create(0, 8008)));
HashMapSet(listen, "tls", JsonValueBoolean(0));
ArrayAdd(listeners, JsonValueObject(listen));
HashMapSet(json, "listen", JsonValueArray(listeners));
if (gethostname(hostname, HOST_NAME_MAX + 1) < 0)
{
strncpy(hostname, "localhost", HOST_NAME_MAX);
}
HashMapSet(json, "serverName", JsonValueString(hostname));
HashMapSet(json, "federation", JsonValueBoolean(1));
HashMapSet(json, "registration", JsonValueBoolean(0));
return DbUnlock(db, ref);
return 1;
}
Config *
ConfigLock(Db * db)
void
ConfigLock(Db * db, Config *config)
{
Config *config;
DbRef *ref = DbLock(db, 1, "config");
if (!ref)
{
return NULL;
config->ok = 0;
config->err = "Couldn't lock configuration.";
}
config = ConfigParse(DbJson(ref));
if (config)
ConfigParse(DbJson(ref), config);
if (config->ok)
{
config->db = db;
config->ref = ref;
}
return config;
}
int
ConfigUnlock(Config * config)
ConfigUnlock(Config *config)
{
Db *db;
DbRef *dbRef;
if (!config)
if (!config->ok)
{
return 0;
}
@ -508,5 +208,25 @@ ConfigUnlock(Config * config)
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;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -35,14 +36,12 @@
#include <Cytoplasm/Args.h>
#include <Cytoplasm/Memory.h>
#include <Config.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 <Uia.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
@ -51,6 +50,9 @@
#include <User.h>
#include <RegToken.h>
#include <Routes.h>
#include <Uia.h>
#include <Config.h>
static Array *httpServers;
static volatile int restart;
@ -102,8 +104,11 @@ Main(Array * args)
char *dbPath;
/* Program configuration */
Config *tConfig;
Config tConfig;
Stream *logFile;
Stream *pidFile;
char *pidPath;
/* User validation */
struct passwd *userInfo;
@ -132,8 +137,9 @@ start:
exit = EXIT_SUCCESS;
flags = 0;
dbPath = NULL;
tConfig = NULL;
logFile = NULL;
pidFile = NULL;
pidPath = NULL;
userInfo = NULL;
groupInfo = NULL;
cron = NULL;
@ -243,7 +249,7 @@ start:
}
token = StrRandom(32);
info = RegTokenCreate(matrixArgs.db, token, NULL, UInt64Create(0, 0), Int64Create(0, 1), USER_ALL);
info = RegTokenCreate(matrixArgs.db, token, NULL, /* expires */ 0, /* uses */ 1, USER_ALL);
if (!info)
{
Free(token);
@ -261,33 +267,20 @@ start:
Log(LOG_NOTICE, "Loading configuration...");
tConfig = ConfigLock(matrixArgs.db);
if (!tConfig)
ConfigLock(matrixArgs.db, &tConfig);
if (!tConfig.ok)
{
Log(LOG_ERR, "Error locking the configuration.");
Log(LOG_ERR, "The configuration object is corrupted or otherwise invalid.");
Log(LOG_ERR, "Please restore from a backup.");
exit = EXIT_FAILURE;
goto finish;
}
else if (!tConfig->ok)
{
Log(LOG_ERR, tConfig->err);
Log(LOG_ERR, tConfig.err);
exit = EXIT_FAILURE;
goto finish;
}
if (!tConfig->logTimestamp || !StrEquals(tConfig->logTimestamp, "default"))
if (!tConfig.log.timestampFormat || !StrEquals(tConfig.log.timestampFormat, "default"))
{
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig->logTimestamp);
}
else
{
Free(tConfig->logTimestamp);
tConfig->logTimestamp = NULL;
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig.log.timestampFormat);
}
if (tConfig->flags & CONFIG_LOG_COLOR)
if (tConfig.log.color)
{
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR);
}
@ -296,9 +289,13 @@ start:
LogConfigFlagClear(LogConfigGlobal(), LOG_FLAG_COLOR);
}
LogConfigLevelSet(LogConfigGlobal(), flags & ARG_VERBOSE ? LOG_DEBUG : tConfig->logLevel);
LogConfigLevelSet(
LogConfigGlobal(),
flags & ARG_VERBOSE ?
LOG_DEBUG :
ConfigLogLevelToSyslog(tConfig.log.level));
if (tConfig->flags & CONFIG_LOG_FILE)
if (tConfig.log.output == CONFIG_LOG_OUTPUT_FILE)
{
logFile = StreamOpen("telodendria.log", "a");
@ -306,18 +303,18 @@ start:
{
Log(LOG_ERR, "Unable to open log file for appending.");
exit = EXIT_FAILURE;
tConfig->flags &= CONFIG_LOG_STDOUT;
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->flags & CONFIG_LOG_STDOUT)
else if (tConfig.log.output == CONFIG_LOG_OUTPUT_STDOUT)
{
Log(LOG_DEBUG, "Already logging to standard output.");
}
else if (tConfig->flags & CONFIG_LOG_SYSLOG)
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);
@ -327,13 +324,6 @@ start:
* messages get passed to the syslog */
setlogmask(LOG_UPTO(LOG_DEBUG));
}
else
{
Log(LOG_ERR, "Unknown logging method in flags: '%d'", tConfig->flags);
Log(LOG_ERR, "This is a programmer error; please report it.");
exit = EXIT_FAILURE;
goto finish;
}
/* If a token was created with a default config, print it to the
* log */
@ -343,14 +333,30 @@ start:
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->uid, tConfig->gid);
Log(LOG_DEBUG, "Max Cache: %ld", tConfig->maxCache);
Log(LOG_DEBUG, "Flags: %x", tConfig->flags);
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();
@ -362,41 +368,51 @@ start:
}
/* Bind servers before possibly dropping permissions. */
for (i = 0; i < ArraySize(tConfig->servers); i++)
for (i = 0; i < ArraySize(tConfig.listen); i++)
{
HttpServerConfig *serverCfg = ArrayGet(tConfig->servers, 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", serverCfg->flags);
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tlsCert);
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tlsKey);
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());
serverCfg->handler = MatrixHttpHandler;
serverCfg->handlerArgs = &matrixArgs;
if (serverCfg->flags & HTTP_FLAG_TLS)
args.handler = MatrixHttpHandler;
args.handlerArgs = &matrixArgs;
if (args.flags & HTTP_FLAG_TLS)
{
if (UInt64Eq(UtilLastModified(serverCfg->tlsCert), UInt64Create(0, 0)))
if (!UtilLastModified(serverCfg->tls.cert))
{
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tlsCert);
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.cert);
exit = EXIT_FAILURE;
goto finish;
}
if (UInt64Eq(UtilLastModified(serverCfg->tlsKey), UInt64Create(0, 0)))
if (UtilLastModified(serverCfg->tls.key))
{
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tlsKey);
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.key);
exit = EXIT_FAILURE;
goto finish;
}
}
server = HttpServerCreate(serverCfg);
server = HttpServerCreate(&args);
if (!server)
{
Log(LOG_ERR, "Unable to create HTTP server on port %d: %s",
@ -417,10 +433,10 @@ start:
Log(LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid());
if (tConfig->uid && tConfig->gid)
if (tConfig.runAs.uid && tConfig.runAs.gid)
{
userInfo = getpwnam(tConfig->uid);
groupInfo = getgrnam(tConfig->gid);
userInfo = getpwnam(tConfig.runAs.uid);
groupInfo = getgrnam(tConfig.runAs.gid);
if (!userInfo || !groupInfo)
{
@ -450,7 +466,7 @@ start:
}
else
{
Log(LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig->uid, tConfig->gid);
Log(LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig.runAs.uid, tConfig.runAs.gid);
}
}
else
@ -462,7 +478,7 @@ start:
}
else
{
if (tConfig->uid && tConfig->gid)
if (tConfig.runAs.uid && tConfig.runAs.gid)
{
if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid)
{
@ -475,17 +491,16 @@ start:
}
}
if (!tConfig->maxCache)
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);
DbMaxCacheSet(matrixArgs.db, tConfig.maxCache);
ConfigUnlock(tConfig);
tConfig = NULL;
ConfigUnlock(&tConfig);
cron = CronCreate(60 * 1000); /* 1-minute tick */
if (!cron)
@ -592,7 +607,7 @@ finish:
Log(LOG_DEBUG, "Stopped and freed job scheduler.");
}
ConfigUnlock(tConfig);
ConfigUnlock(&tConfig);
Log(LOG_DEBUG, "Unlocked configuration.");
DbClose(matrixArgs.db);
@ -601,6 +616,12 @@ finish:
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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

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,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -30,7 +31,6 @@
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Int64.h>
#include <Cytoplasm/Log.h>
#include <User.h>
@ -39,9 +39,9 @@ int
RegTokenValid(RegTokenInfo * token)
{
HashMap *tokenJson;
Int64 uses, used;
int64_t uses, used;
UInt64 expiration;
uint64_t expiration;
if (!token || !RegTokenExists(token->db, token->name))
{
@ -53,9 +53,7 @@ RegTokenValid(RegTokenInfo * token)
used = JsonValueAsInteger(HashMapGet(tokenJson, "used"));
expiration = JsonValueAsInteger(HashMapGet(tokenJson, "expires_on"));
return (UInt64Eq(expiration, UInt64Create(0, 0)) ||
UInt64Geq(UtilServerTs(), expiration)) &&
(Int64Eq(uses, Int64Neg(Int64Create(0, 1))) || Int64Lt(used, uses));
return (!expiration || (UtilTsMillis() < expiration)) && (uses == -1 || used < uses);
}
void
RegTokenUse(RegTokenInfo * token)
@ -67,13 +65,12 @@ RegTokenUse(RegTokenInfo * token)
return;
}
if (Int64Geq(token->uses, Int64Create(0, 0)) &&
Int64Geq(token->used, token->uses))
if (token->uses >= 0 && token->used >= token->uses)
{
return;
}
token->used = Int64Add(token->used, Int64Create(0, 1));
token->used++;
/* Write the information to the hashmap */
tokenJson = DbJson(token->ref);
@ -198,11 +195,11 @@ RegTokenVerify(char *token)
}
RegTokenInfo *
RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int privileges)
RegTokenCreate(Db * db, char *name, char *owner, uint64_t expires, int64_t uses, int privileges)
{
RegTokenInfo *ret;
UInt64 timestamp = UtilServerTs();
uint64_t timestamp = UtilTsMillis();
if (!db || !name)
{
@ -212,13 +209,13 @@ RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int
/* -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 (Int64Lt(uses, Int64Neg(Int64Create(0, 1))))
if (uses < -1)
{
return NULL;
}
/* Verify the token */
if (!RegTokenVerify(name) || (UInt64Gt(expires, UInt64Create(0, 0)) && UInt64Lt(expires, timestamp)))
if (!RegTokenVerify(name) || ((expires > 0) && (expires < timestamp)))
{
return NULL;
}
@ -234,7 +231,7 @@ RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int
}
ret->name = StrDuplicate(name);
ret->created_by = StrDuplicate(owner);
ret->used = Int64Create(0, 0);
ret->used = 0;
ret->uses = uses;
ret->created_on = timestamp;
ret->expires_on = expires;

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -28,7 +29,6 @@
#include <Cytoplasm/Str.h>
#include <User.h>
#include <Cytoplasm/Log.h>
ROUTE_IMPL(RouteAdminDeactivate, path, argp)
{

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -24,8 +25,12 @@
#include <Routes.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Config.h>
#include <Parser.h>
#include <User.h>
ROUTE_IMPL(RouteAliasDirectory, path, argp)
@ -37,20 +42,40 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
HashMap *response;
Db *db = args->matrixArgs->db;
DbRef *ref;
DbRef *ref = NULL;
HashMap *aliases;
HashMap *idObject;
JsonValue *val;
Array *arr;
char *token;
char *msg;
User *user = NULL;
/* TODO: Return HTTP 400 M_INVALID_PARAM if alias is invalid */
CommonID aliasID;
Config config;
aliasID.sigil = '\0';
aliasID.local = NULL;
aliasID.server.hostname = NULL;
aliasID.server.port = NULL;
ConfigLock(db, &config);
if (!ParseCommonID(alias, &aliasID) || aliasID.sigil != '#')
{
msg = "Invalid room alias.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
ref = DbLock(db, 1, "aliases");
if (!ref && !(ref = DbCreate(db, 1, "aliases")))
{
msg = "Unable to access alias database.",
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, "Unable to access alias database.");
response = MatrixErrorCreate(M_UNKNOWN, msg);
goto finish;
}
@ -68,8 +93,9 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
}
else
{
msg = "There is no mapped room ID for this room alias.";
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, "There is no mapped room ID for this room alias.");
response = MatrixErrorCreate(M_NOT_FOUND, msg);
}
break;
case HTTP_PUT:
@ -91,9 +117,21 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
if (HttpRequestMethodGet(args->context) == HTTP_PUT)
{
HashMap *newAlias;
char *id;
char *serverPart;
/* TODO: Validate alias domain and make sure it matches
* server name and is well formed. */
serverPart = ParserRecomposeServerPart(aliasID.server);
if (!StrEquals(serverPart, config.serverName))
{
msg = "Invalid server name.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
Free(serverPart);
goto finish;
}
Free(serverPart);
if (JsonGet(aliases, 2, "alias", alias))
{
@ -110,40 +148,81 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
goto finish;
}
if (!JsonValueAsString(HashMapGet(request, "room_id")))
id = JsonValueAsString(HashMapGet(request, "room_id"));
if (!id)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, "Missing or invalid room_id.");
goto finish;
}
/* TODO: Validate room ID to make sure it is well
* formed. */
if (!ValidCommonID(id, '!'))
{
msg = "Invalid room ID.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
newAlias = HashMapCreate();
HashMapSet(newAlias, "createdBy", JsonValueString(UserGetName(user)));
HashMapSet(newAlias, "id", JsonValueDuplicate(HashMapGet(request, "room_id")));
HashMapSet(newAlias, "id", JsonValueString(id));
HashMapSet(newAlias, "servers", JsonValueArray(ArrayCreate()));
JsonSet(aliases, JsonValueObject(newAlias), 2, "alias", alias);
if (!(idObject = JsonValueAsObject(JsonGet(aliases, 2, "id", id))))
{
arr = ArrayCreate();
idObject = HashMapCreate();
HashMapSet(idObject, "aliases", JsonValueArray(arr));
JsonSet(aliases, JsonValueObject(idObject), 2, "id", id);
}
val = HashMapGet(idObject, "aliases");
arr = JsonValueAsArray(val);
ArrayAdd(arr, JsonValueString(alias));
}
else
{
if (!JsonGet(aliases, 2, "alias", alias))
HashMap *roomAlias;
char *id;
if (!(val = JsonGet(aliases, 2, "alias", alias)))
{
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, "Room alias not found.");
goto finish;
}
roomAlias = JsonValueAsObject(val);
id = StrDuplicate(JsonValueAsString(HashMapGet(roomAlias, "id")));
if (!(UserGetPrivileges(user) & USER_ALIAS) && !StrEquals(UserGetName(user), JsonValueAsString(JsonGet(aliases, 3, "alias", alias, "createdBy"))))
if (!(UserGetPrivileges(user) & USER_ALIAS) && !StrEquals(UserGetName(user), JsonValueAsString(JsonGet(roomAlias, 1, "createdBy"))))
{
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
Free(id);
goto finish;
}
JsonValueFree(HashMapDelete(JsonValueAsObject(HashMapGet(aliases, "alias")), alias));
idObject = JsonValueAsObject(JsonGet(aliases, 2, "id", id));
if (idObject)
{
size_t i;
val = HashMapGet(idObject, "aliases");
arr = JsonValueAsArray(val);
for (i = 0; i < ArraySize(arr); i++)
{
if (StrEquals(JsonValueAsString(ArrayGet(arr, i)), alias))
{
JsonValueFree(ArrayDelete(arr, i));
break;
}
}
}
Free(id);
}
response = HashMapCreate();
@ -155,6 +234,8 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
}
finish:
CommonIDFree(aliasID);
ConfigUnlock(&config);
UserUnlock(user);
DbUnlock(db, ref);
JsonFree(request);

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -65,21 +66,25 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
char *token;
char *newPassword;
Config *config = ConfigLock(db);
char *msg;
if (!config)
Config config;
ConfigLock(db, &config);
if (!config.ok)
{
Log(LOG_ERR, "Password endpoint failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL);
return MatrixErrorCreate(M_UNKNOWN, config.err);
}
(void) path;
if (HttpRequestMethodGet(args->context) != HTTP_POST)
{
msg = "Route only supports POST.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
goto finish;
}
@ -118,9 +123,10 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
newPassword = JsonValueAsString(HashMapGet(request, "new_password"));
if (!newPassword)
{
msg = "'new_password' is unset or not a string.";
JsonFree(request);
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
@ -152,7 +158,7 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
response = HashMapCreate();
finish:
ConfigUnlock(config);
ConfigUnlock(&config);
UserUnlock(user);
JsonFree(request);
return response;

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -33,12 +34,13 @@ ROUTE_IMPL(RouteConfig, path, argp)
RouteArgs *args = argp;
HashMap *response;
char *token;
char *msg;
User *user = NULL;
Config *config = NULL;
Config config;
HashMap *request = NULL;
Config *newConf;
Config newConf;
HashMap *newJson = NULL;
(void) path;
@ -59,24 +61,25 @@ ROUTE_IMPL(RouteConfig, path, argp)
if (!(UserGetPrivileges(user) & USER_CONFIG))
{
msg = "User does not have the 'CONFIG' privilege.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
config = ConfigLock(args->matrixArgs->db);
if (!config)
ConfigLock(args->matrixArgs->db, &config);
if (!config.ok)
{
Log(LOG_ERR, "Config endpoint failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, config.err);
goto finish;
}
switch (HttpRequestMethodGet(args->context))
{
case HTTP_GET:
response = JsonDuplicate(DbJson(config->ref));
response = JsonDuplicate(DbJson(config.ref));
break;
case HTTP_POST:
request = JsonDecode(HttpServerStream(args->context));
@ -87,17 +90,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
break;
}
newConf = ConfigParse(request);
if (!newConf)
ConfigParse(request, &newConf);
if (newConf.ok)
{
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
break;
}
if (newConf->ok)
{
if (DbJsonSet(config->ref, request))
if (DbJsonSet(config.ref, request))
{
response = HashMapCreate();
/*
@ -108,17 +104,18 @@ ROUTE_IMPL(RouteConfig, path, argp)
}
else
{
msg = "Internal server error while writing the config.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, msg);
}
}
else
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, newConf->err);
response = MatrixErrorCreate(M_BAD_JSON, newConf.err);
}
ConfigFree(newConf);
ConfigFree(&newConf);
JsonFree(request);
break;
case HTTP_PUT:
@ -130,21 +127,14 @@ ROUTE_IMPL(RouteConfig, path, argp)
break;
}
newJson = JsonDuplicate(DbJson(config->ref));
newJson = JsonDuplicate(DbJson(config.ref));
JsonMerge(newJson, request);
newConf = ConfigParse(newJson);
ConfigParse(newJson, &newConf);
if (!newConf)
if (newConf.ok)
{
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
break;
}
if (newConf->ok)
{
if (DbJsonSet(config->ref, newJson))
if (DbJsonSet(config.ref, newJson))
{
response = HashMapCreate();
/*
@ -155,28 +145,30 @@ ROUTE_IMPL(RouteConfig, path, argp)
}
else
{
msg = "Internal server error while writing the config.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, msg);
}
}
else
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, newConf->err);
response = MatrixErrorCreate(M_BAD_JSON, newConf.err);
}
ConfigFree(newConf);
ConfigFree(&newConf);
JsonFree(request);
JsonFree(newJson);
break;
default:
msg = "Unknown request method.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, "Unknown request method.");
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
break;
}
finish:
UserUnlock(user);
ConfigUnlock(config);
ConfigUnlock(&config);
return response;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -41,8 +42,9 @@ ROUTE_IMPL(RouteCreateRoom, path, argp)
if (HttpRequestMethodGet(args->context) != HTTP_POST)
{
err = "Unknown request method.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, "Unknown request method.");
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
goto finish;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -45,22 +46,27 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
Db *db = args->matrixArgs->db;
User *user = NULL;
Config *config = ConfigLock(db);
Config config;
char *msg;
(void) path;
if (!config)
ConfigLock(db, &config);
if (!config.ok)
{
Log(LOG_ERR, "Deactivate endpoint failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, config.err);
goto finish;
}
if (HttpRequestMethodGet(args->context) != HTTP_POST)
{
msg = "Route only accepts POST.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
goto finish;
}
@ -128,8 +134,9 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
if (!UserDeleteTokens(user, NULL) || !UserDeactivate(user, NULL, NULL))
{
msg = "Internal server error: couldn't remove user properly.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, msg);
goto finish;
}
@ -144,6 +151,6 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
finish:
JsonFree(request);
UserUnlock(user);
ConfigUnlock(config);
ConfigUnlock(&config);
return response;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -39,16 +40,17 @@ GetServerName(Db * db)
{
char *name;
Config *config = ConfigLock(db);
Config config;
if (!config)
ConfigLock(db, &config);
if (!config.ok)
{
return NULL;
}
name = StrDuplicate(config->serverName);
name = StrDuplicate(config.serverName);
ConfigUnlock(config);
ConfigUnlock(&config);
return name;
}
@ -62,13 +64,15 @@ ROUTE_IMPL(RouteFilter, path, argp)
HashMap *response = NULL;
User *user = NULL;
UserId *id = NULL;
CommonID *id = NULL;
char *token = NULL;
char *serverName = NULL;
char *userParam = ArrayGet(path, 0);
char *msg;
if (!userParam)
{
/* Should be impossible */
@ -87,15 +91,17 @@ ROUTE_IMPL(RouteFilter, path, argp)
id = UserIdParse(userParam, serverName);
if (!id)
{
msg = "Invalid user ID.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
if (!StrEquals(id->server, serverName))
if (!ParserServerNameEquals(id->server, serverName))
{
msg = "Cannot use /filter for non-local users.";
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
response = MatrixErrorCreate(M_UNAUTHORIZED, msg);
goto finish;
}
@ -113,10 +119,11 @@ ROUTE_IMPL(RouteFilter, path, argp)
goto finish;
}
if (!StrEquals(id->localpart, UserGetName(user)))
if (!StrEquals(id->local, UserGetName(user)))
{
msg = "Unauthorized to use /filter.";
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
@ -126,8 +133,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
if (!ref)
{
msg = "The filter for this user was not found.";
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
response = MatrixErrorCreate(M_NOT_FOUND, msg);
goto finish;
}
@ -161,8 +169,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
filterId = StrRandom(12);
if (!filterId)
{
msg = "Couldn't generate random filter ID; this is unintended.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, msg);
goto finish;
}
@ -170,8 +179,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
if (!ref)
{
Free(filterId);
msg = "Couldn't write filter to the database, this is unintended.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, msg);
goto finish;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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,6 +26,8 @@
#include <string.h>
#include <Schema/LoginRequest.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Str.h>
@ -43,13 +46,10 @@ ROUTE_IMPL(RouteLogin, path, argp)
HashMap *identifier;
char *deviceId = NULL;
char *initialDeviceDisplayName = NULL;
int refreshToken = 0;
LoginRequest loginRequest;
LoginRequestUserIdentifier userIdentifier;
char *password;
char *type;
UserId *userId = NULL;
CommonID *userId = NULL;
Db *db = args->matrixArgs->db;
@ -57,17 +57,29 @@ ROUTE_IMPL(RouteLogin, path, argp)
UserLoginInfo *loginInfo;
char *fullUsername;
Config *config = ConfigLock(db);
char *type;
char *initialDeviceDisplayName;
char *deviceId;
char *password;
int refreshToken;
if (!config)
char *msg;
Config config;
ConfigLock(db, &config);
if (!config.ok)
{
Log(LOG_ERR, "Login endpoint failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL);
return MatrixErrorCreate(M_UNKNOWN, config.err);
}
(void) path;
memset(&loginRequest, 0, sizeof(LoginRequest));
memset(&userIdentifier, 0, sizeof(LoginRequestUserIdentifier));
switch (HttpRequestMethodGet(args->context))
{
case HTTP_GET:
@ -88,49 +100,27 @@ ROUTE_IMPL(RouteLogin, path, argp)
break;
}
val = HashMapGet(request, "type");
if (!val)
if (!LoginRequestFromJson(request, &loginRequest, &msg))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
break;
}
if (JsonValueType(val) != JSON_STRING)
if (loginRequest.type != REQUEST_TYPE_PASSWORD)
{
msg = "Unsupported login type.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
break;
}
type = JsonValueAsString(val);
if (!StrEquals(type, "m.login.password"))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
break;
}
val = HashMapGet(request, "identifier");
if (!val)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
break;
}
if (JsonValueType(val) != JSON_OBJECT)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
break;
}
identifier = JsonValueAsObject(val);
identifier = loginRequest.identifier;
val = HashMapGet(identifier, "type");
if (!val)
{
msg = "No login identifier type set.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
break;
@ -138,112 +128,60 @@ ROUTE_IMPL(RouteLogin, path, argp)
if (JsonValueType(val) != JSON_STRING)
{
msg = "Invalid login identifier type.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
break;
}
type = JsonValueAsString(val);
if (!StrEquals(type, "m.id.user"))
{
msg = "Invalid login identifier type.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
break;
}
val = HashMapGet(identifier, "user");
if (!val)
if (!LoginRequestUserIdentifierFromJson(identifier,
&userIdentifier, &msg))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
break;
}
if (JsonValueType(val) != JSON_STRING)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
break;
}
userId = UserIdParse(JsonValueAsString(val), config->serverName);
userId = UserIdParse(userIdentifier.user, config.serverName);
if (!userId)
{
msg = "Invalid user ID.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
break;
}
if (!StrEquals(userId->server, config->serverName)
|| !UserExists(db, userId->localpart))
if (!ParserServerNameEquals(userId->server, config.serverName)
|| !UserExists(db, userId->local))
{
msg = "Unknown user ID.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
break;
}
deviceId = loginRequest.device_id;
val = HashMapGet(request, "device_id");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
break;
}
initialDeviceDisplayName =loginRequest.initial_device_display_name;
password = loginRequest.password;
refreshToken = loginRequest.refresh_token;
deviceId = JsonValueAsString(val);
}
val = HashMapGet(request, "initial_device_display_name");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
break;
}
initialDeviceDisplayName = JsonValueAsString(val);
}
val = HashMapGet(request, "password");
if (!val)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
break;
}
if (JsonValueType(val) != JSON_STRING)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
break;
}
password = JsonValueAsString(val);
val = HashMapGet(request, "refresh_token");
if (val)
{
if (JsonValueType(val) != JSON_BOOLEAN)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
break;
}
refreshToken = JsonValueAsBoolean(val);
}
user = UserLock(db, userId->localpart);
user = UserLock(db, userId->local);
if (!user)
{
msg = "Couldn't lock user.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
break;
}
@ -261,10 +199,11 @@ ROUTE_IMPL(RouteLogin, path, argp)
if (!loginInfo)
{
msg = "Invalid creditentials for user.";
UserUnlock(user);
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
break;
}
@ -284,13 +223,13 @@ ROUTE_IMPL(RouteLogin, path, argp)
}
fullUsername = StrConcat(4, "@", UserGetName(user), ":",
config->serverName);
config.serverName);
HashMapSet(response, "user_id", JsonValueString(fullUsername));
Free(fullUsername);
HashMapSet(response, "well_known",
JsonValueObject(
MatrixClientWellKnown(config->baseUrl, config->identityServer)));
MatrixClientWellKnown(config.baseUrl, config.identityServer)));
UserAccessTokenFree(loginInfo->accessToken);
Free(loginInfo->refreshToken);
@ -300,14 +239,18 @@ ROUTE_IMPL(RouteLogin, path, argp)
break;
default:
msg = "Route only accepts GET and POST.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
break;
}
UserIdFree(userId);
JsonFree(request);
ConfigUnlock(config);
ConfigUnlock(&config);
LoginRequestFree(&loginRequest);
LoginRequestUserIdentifierFree(&userIdentifier);
return response;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -38,14 +39,17 @@ ROUTE_IMPL(RouteLogout, path, argp)
char *tokenstr;
char *msg;
Db *db = args->matrixArgs->db;
User *user;
if (HttpRequestMethodGet(args->context) != HTTP_POST)
{
msg = "This route only accepts POST.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
}
response = MatrixGetAccessToken(args->context, &tokenstr);
@ -84,8 +88,9 @@ ROUTE_IMPL(RouteLogout, path, argp)
{
if (!UserDeleteToken(user, tokenstr))
{
msg = "Internal server error: couldn't delete token.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, msg);
goto finish;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -39,6 +40,8 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
JsonValue *val;
int privileges;
char *msg;
response = MatrixGetAccessToken(args->context, &token);
if (response)
{
@ -55,8 +58,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
if (!(UserGetPrivileges(user) & USER_GRANT_PRIVILEGES))
{
msg = "User doesn't have the GRANT_PRIVILEGES privilege";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
@ -68,8 +72,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
user = UserLock(args->matrixArgs->db, ArrayGet(path, 0));
if (!user)
{
msg = "Unknown user.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
}
@ -90,8 +95,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
val = HashMapGet(request, "privileges");
if (!val || JsonValueType(val) != JSON_ARRAY)
{
msg = "'privileges' is unset or not an array.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
break;
}
@ -116,8 +122,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
if (!UserSetPrivileges(user, privileges))
{
msg = "Internal server error: couldn't set privileges.";
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, msg);
break;
}
@ -127,8 +134,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
HashMapSet(response, "privileges", JsonValueArray(UserEncodePrivileges(UserGetPrivileges(user))));
break;
default:
msg = "Route only accepts POST, PUT, DELETE, and GET.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
goto finish;
break;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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,7 +24,6 @@
*/
#include <Routes.h>
#include <Cytoplasm/Int64.h>
#include <User.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
@ -37,6 +37,7 @@ ROUTE_IMPL(RouteProcControl, path, argp)
char *op = ArrayGet(path, 0);
HashMap *response;
char *token;
char *msg;
User *user = NULL;
response = MatrixGetAccessToken(args->context, &token);
@ -55,11 +56,13 @@ ROUTE_IMPL(RouteProcControl, path, argp)
if (!(UserGetPrivileges(user) & USER_PROC_CONTROL))
{
msg = "User doesn't have PROC_CONTROL privilege.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
msg = "Unknown operation.";
switch (HttpRequestMethodGet(args->context))
{
case HTTP_POST:
@ -74,7 +77,7 @@ ROUTE_IMPL(RouteProcControl, path, argp)
else
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
goto finish;
}
break;
@ -82,36 +85,23 @@ ROUTE_IMPL(RouteProcControl, path, argp)
if (StrEquals(op, "stats"))
{
size_t allocated = MemoryAllocated();
Int64 a;
response = HashMapCreate();
if (sizeof(size_t) == sizeof(Int64))
{
UInt32 high = (UInt32) (allocated >> 32);
UInt32 low = (UInt32) (allocated);
a = Int64Create(high, low);
}
else
{
a = Int64Create(0, allocated);
}
HashMapSet(response, "version", JsonValueString(TELODENDRIA_VERSION));
HashMapSet(response, "memory_allocated", JsonValueInteger(a));
HashMapSet(response, "memory_allocated", JsonValueInteger(allocated));
goto finish;
}
else
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
goto finish;
}
default:
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
goto finish;
break;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -45,6 +46,8 @@ ROUTE_IMPL(RouteRefresh, path, argp)
UserAccessToken *newAccessToken;
char *deviceId;
char *msg;
Db *db = args->matrixArgs->db;
User *user = NULL;
@ -55,8 +58,9 @@ ROUTE_IMPL(RouteRefresh, path, argp)
if (HttpRequestMethodGet(args->context) != HTTP_POST)
{
msg = "This route only accepts POST.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
}
request = JsonDecode(HttpServerStream(args->context));
@ -69,8 +73,9 @@ ROUTE_IMPL(RouteRefresh, path, argp)
val = HashMapGet(request, "refresh_token");
if (!val || JsonValueType(val) != JSON_STRING)
{
msg = "'refresh_token' is unset or not a string.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -30,6 +31,8 @@
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Memory.h>
#include <Schema/Registration.h>
#include <User.h>
#include <Uia.h>
#include <RegToken.h>
@ -55,35 +58,36 @@ ROUTE_IMPL(RouteRegister, path, argp)
HashMap *request = NULL;
HashMap *response = NULL;
JsonValue *val;
RegistrationRequest regReq;
char *kind;
char *username = NULL;
char *password = NULL;
char *initialDeviceDisplayName = NULL;
int refreshToken = 0;
int inhibitLogin = 0;
char *deviceId = NULL;
char *fullUsername;
char *msg;
char *username;
Db *db = args->matrixArgs->db;
User *user = NULL;
Array *uiaFlows = NULL;
int uiaResult;
char *session;
DbRef *sessionRef;
Config *config = ConfigLock(db);
Config config;
if (!config)
regReq.username = NULL;
regReq.password = NULL;
regReq.device_id = NULL;
regReq.initial_device_display_name = NULL;
regReq.refresh_token = 0;
regReq.inhibit_login = 0;
ConfigLock(db, &config);
if (!config.ok)
{
Log(LOG_ERR, "Registration endpoint failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL);
return MatrixErrorCreate(M_UNKNOWN, config.err);
}
if (ArraySize(path) == 0)
@ -102,26 +106,23 @@ ROUTE_IMPL(RouteRegister, path, argp)
response = MatrixErrorCreate(M_NOT_JSON, NULL);
goto end;
}
val = HashMapGet(request, "username");
if (val)
if (!RegistrationRequestFromJson(request, &regReq, &msg))
{
if (JsonValueType(val) != JSON_STRING)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
goto finish;
}
username = StrDuplicate(JsonValueAsString(val));
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_NOT_JSON, msg);
goto end;
}
if (!UserValidate(username, config->serverName))
if (regReq.username)
{
if (!UserValidate(regReq.username, config.serverName))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
goto finish;
}
if (UserExists(db, username))
if (UserExists(db, regReq.username))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_USER_IN_USE, NULL);
@ -132,7 +133,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
uiaFlows = ArrayCreate();
ArrayAdd(uiaFlows, RouteRegisterRegFlow());
if (config->flags & CONFIG_REGISTRATION)
if (config.registration)
{
ArrayAdd(uiaFlows, UiaDummyFlow());
}
@ -158,99 +159,44 @@ ROUTE_IMPL(RouteRegister, path, argp)
/* We don't support guest accounts yet */
if (kind && !StrEquals(kind, "user"))
{
msg = "Guest accounts are currently not supported";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
val = HashMapGet(request, "password");
if (!val)
if (!regReq.password)
{
msg = "'password' field is unset";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
response = MatrixErrorCreate(M_MISSING_PARAM, msg);
goto finish;
}
if (JsonValueType(val) != JSON_STRING)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
goto finish;
}
/* All of the other fields are optional, we don't have to check
* them. */
password = StrDuplicate(JsonValueAsString(val));
val = HashMapGet(request, "device_id");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
goto finish;
}
deviceId = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(request, "inhibit_login");
if (val)
{
if (JsonValueType(val) != JSON_BOOLEAN)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
goto finish;
}
inhibitLogin = JsonValueAsBoolean(val);
}
val = HashMapGet(request, "initial_device_display_name");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
goto finish;
}
initialDeviceDisplayName = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(request, "refresh_token");
if (val)
{
if (JsonValueType(val) != JSON_BOOLEAN)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
goto finish;
}
refreshToken = JsonValueAsBoolean(val);
}
user = UserCreate(db, username, password);
user = UserCreate(db, regReq.username, regReq.password);
response = HashMapCreate();
fullUsername = StrConcat(4, "@", UserGetName(user), ":", config->serverName);
fullUsername = StrConcat(4,
"@", UserGetName(user), ":", config.serverName);
HashMapSet(response, "user_id", JsonValueString(fullUsername));
Free(fullUsername);
HttpResponseStatus(args->context, HTTP_OK);
if (!inhibitLogin)
if (!regReq.inhibit_login)
{
UserLoginInfo *loginInfo = UserLogin(user, password, deviceId,
initialDeviceDisplayName, refreshToken);
UserLoginInfo *loginInfo = UserLogin(user, regReq.password,
regReq.device_id, regReq.initial_device_display_name,
regReq.refresh_token);
HashMapSet(response, "access_token",
JsonValueString(loginInfo->accessToken->string));
HashMapSet(response, "device_id",
JsonValueString(loginInfo->accessToken->deviceId));
if (refreshToken)
if (regReq.refresh_token)
{
HashMapSet(response, "expires_in_ms",
JsonValueInteger(loginInfo->accessToken->lifetime));
@ -294,10 +240,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
UserUnlock(user);
finish:
UiaFlowsFree(uiaFlows);
Free(username);
Free(password);
Free(deviceId);
Free(initialDeviceDisplayName);
RegistrationRequestFree(&regReq);
JsonFree(request);
}
else
@ -310,10 +253,11 @@ finish:
if (!username)
{
msg = "'username' path parameter is not set.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
response = MatrixErrorCreate(M_MISSING_PARAM, msg);
}
else if (!UserValidate(username, config->serverName))
else if (!UserValidate(username, config.serverName))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
@ -337,6 +281,6 @@ finish:
}
end:
ConfigUnlock(config);
ConfigUnlock(&config);
return response;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -26,19 +27,35 @@
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Json.h>
#include <Schema/RequestToken.h>
ROUTE_IMPL(RouteRequestToken, path, argp)
{
RouteArgs *args = argp;
char *type = ArrayGet(path, 0);
HashMap *request;
HashMap *response;
JsonValue *val;
char *str;
char *msg;
RequestToken reqTok;
reqTok.client_secret = NULL;
reqTok.next_link = NULL;
reqTok.id_access_token = NULL;
reqTok.id_server = NULL;
reqTok.email = NULL;
reqTok.country = NULL;
reqTok.phone_number = NULL;
reqTok.send_attempt = -1;
if (HttpRequestMethodGet(args->context) != HTTP_POST)
{
msg = "This route only accepts POST.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
}
request = JsonDecode(HttpServerStream(args->context));
@ -48,87 +65,92 @@ ROUTE_IMPL(RouteRequestToken, path, argp)
return MatrixErrorCreate(M_NOT_JSON, NULL);
}
val = HashMapGet(request, "client_secret");
if (!val || JsonValueType(val) != JSON_STRING)
if (!RequestTokenFromJson(request, &reqTok, &msg))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
str = JsonValueAsString(val);
if (strlen(str) > 255 || StrBlank(str))
if (!reqTok.client_secret)
{
msg = "'client_secret' is not set";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
val = HashMapGet(request, "send_attempt");
if (!val || JsonValueType(val) != JSON_INTEGER)
if (strlen(reqTok.client_secret) > 255 || StrBlank(reqTok.client_secret))
{
msg = "'client_secret' is blank or too long";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
val = HashMapGet(request, "next_link");
if (val && JsonValueType(val) != JSON_STRING)
if (reqTok.send_attempt == -1)
{
msg = "Invalid or inexistent 'send_attempt'";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
val = HashMapGet(request, "id_access_token");
if (val && JsonValueType(val) != JSON_STRING)
if (!reqTok.next_link)
{
msg = "'next_link' is not set";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
val = HashMapGet(request, "id_server");
if (val && JsonValueType(val) != JSON_STRING)
if (!reqTok.id_access_token)
{
msg = "'id_access_token' is not set";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
if (!reqTok.id_server)
{
msg = "'id_server' is not set";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
if (StrEquals(type, "email"))
{
val = HashMapGet(request, "email");
if (val && JsonValueType(val) != JSON_STRING)
if (!reqTok.email)
{
msg = "Type is set to 'email' yet none was set";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
}
else if (StrEquals(type, "msisdn"))
{
val = HashMapGet(request, "country");
if (val && JsonValueType(val) != JSON_STRING)
if (!reqTok.country)
{
msg = "Type is set to 'msisdn' yet no country is set";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
str = JsonValueAsString(val);
if (strlen(str) != 2)
if (strlen(reqTok.country) != 2)
{
msg = "Invalid country tag, length must be 2";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
val = HashMapGet(request, "phone_number");
if (val && JsonValueType(val) != JSON_STRING)
if (!reqTok.phone_number)
{
msg = "Type is set to 'msisdn' yet phone_number is unset";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, NULL);
response = MatrixErrorCreate(M_BAD_JSON, msg);
goto finish;
}
}
@ -145,5 +167,6 @@ ROUTE_IMPL(RouteRequestToken, path, argp)
finish:
JsonFree(request);
RequestTokenFree(&reqTok);
return response;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -24,25 +25,80 @@
#include <Routes.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Db.h>
#include <Matrix.h>
#include <User.h>
ROUTE_IMPL(RouteRoomAliases, path, argp)
{
RouteArgs *args = argp;
char *roomId = ArrayGet(path, 0);
char *token;
char *msg;
HashMap *request = NULL;
HashMap *response = NULL;
HashMap *aliases = NULL;
HashMap *reversealias = NULL;
JsonValue *val;
Db *db = args->matrixArgs->db;
DbRef *ref = NULL;
(void) roomId;
User *user = NULL;
/* TODO: Placeholder; remove. */
goto finish;
if (HttpRequestMethodGet(args->context) != HTTP_GET)
{
msg = "Route only accepts GET.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
response = MatrixGetAccessToken(args->context, &token);
if (response)
{
goto finish;
}
user = UserAuthenticate(db, token);
if (!user)
{
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
goto finish;
}
/* TODO: Check whenever the user is in the room or if its world readable
* once this is implemented instead of just checking for the ALIAS
* privilege. */
if (!(UserGetPrivileges(user) & USER_ALIAS))
{
msg = "User is not allowed to get this room's aliases.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
ref = DbLock(db, 1, "aliases");
aliases = DbJson(ref);
reversealias = JsonValueAsObject(JsonGet(aliases, 2, "id", roomId));
if (!reversealias)
{
/* We do not know about the room ID. */
msg = "Unknown room ID.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
response = HashMapCreate();
val = JsonGet(reversealias, 1, "aliases");
HashMapSet(response, "aliases", JsonValueDuplicate(val));
finish:
DbUnlock(db, ref);
JsonFree(request);
UserUnlock(user);
return response;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -47,8 +48,8 @@ ROUTE_IMPL(RouteStaticResources, path, argp)
"function findGetParameter(parameterName) {"
" var result = null;"
" var tmp = [];"
" var items = location.search.substr(1).split(\"&\");"
" for (var index = 0; index < items.length; index++) {"
" var items = location.search.substr(1).split(\"&\");"
" for (var index = 0; index < items.length; index++) {"
" tmp = items[index].split(\"=\");"
" if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);"
" }"

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -41,13 +42,15 @@ ROUTE_IMPL(RouteTokenValid, path, argp)
RegTokenInfo *info = NULL;
char *tokenstr;
char *msg;
(void) path;
if (HttpRequestMethodGet(args->context) != HTTP_GET)
{
msg = "This route only accepts GET.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
}
request = JsonDecode(HttpServerStream(args->context));

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -36,6 +37,8 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
char *authType = ArrayGet(path, 0);
char *sessionId;
char *msg;
if (!authType)
{
/* This should never happen */
@ -49,22 +52,22 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
HashMap *request;
HashMap *response;
int uiaResult;
Config *config;
Config config;
Array *flows;
Array *flow;
config = ConfigLock(args->matrixArgs->db);
if (!config)
ConfigLock(args->matrixArgs->db, &config);
if (!config.ok)
{
Log(LOG_ERR, "UIA fallback failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL);
return MatrixErrorCreate(M_UNKNOWN, config.err);
}
request = JsonDecode(HttpServerStream(args->context));
if (!request)
{
ConfigUnlock(config);
ConfigUnlock(&config);
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_NOT_JSON, NULL);
}
@ -88,20 +91,22 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
}
JsonFree(request);
ConfigUnlock(config);
ConfigUnlock(&config);
return response;
}
else if (HttpRequestMethodGet(args->context) != HTTP_GET)
{
msg = "Route only supports GET.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
}
sessionId = HashMapGet(requestParams, "session");
if (!sessionId)
{
msg = "'session' parameter is unset.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(M_MISSING_PARAM, NULL);
return MatrixErrorCreate(M_MISSING_PARAM, msg);
}
HttpResponseHeader(args->context, "Content-Type", "text/html");
@ -121,25 +126,25 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
HtmlEndForm(stream);
HtmlBeginJs(stream);
StreamPrintf(stream,
"function buildRequest() {"
"function buildRequest() {"
" let user = document.getElementById('user').value;"
" let pass = document.getElementById('password').value;"
" if (!user || !pass) {"
" setFormError('Please specify a username and password.');"
" return false;"
" }"
" return {"
" auth: {"
" type: '%s',"
" identifier: {"
" type: 'm.id.user',"
" user: user"
" },"
" password: pass,"
" session: '%s'"
" }"
" };"
"}", authType, sessionId);
" let pass = document.getElementById('password').value;"
" if (!user || !pass) {"
" setFormError('Please specify a username and password.');"
" return false;"
" }"
" return {"
" auth: {"
" type: '%s',"
" identifier: {"
" type: 'm.id.user',"
" user: user"
" },"
" password: pass,"
" session: '%s'"
" }"
" };"
"}", authType, sessionId);
HtmlEndJs(stream);
}
else if (StrEquals(authType, "m.login.registration_token"))
@ -186,10 +191,10 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
"function processResponse(xhr) {"
" let r = JSON.parse(xhr.responseText);"
" console.log(r);"
" if (xhr.status == 200 || r.completed.includes('%s')) {"
" if (xhr.status == 200 || r.completed.includes('%s')) {"
" if (window.onAuthDone) {"
" window.onAuthDone();"
" } else if (window.opener && window.opener.postMessage) {"
" } else if (window.opener && window.opener.postMessage) {"
" window.opener.postMessage('authDone', '*');"
" } else {"
" setFormError('Client error.');"

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -39,7 +40,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
HashMap *request = NULL;
HashMap *response = NULL;
UserId *userId = NULL;
CommonID *userId = NULL;
User *user = NULL;
char *serverName;
@ -48,30 +49,37 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
char *token = NULL;
char *value = NULL;
Config *config = ConfigLock(db);
char *msg;
if (!config)
Config config;
ConfigLock(db, &config);
if (!config.ok)
{
Log(LOG_ERR, "User profile endpoint failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL);
return MatrixErrorCreate(M_UNKNOWN, config.err);
}
serverName = config->serverName;
serverName = config.serverName;
username = ArrayGet(path, 0);
userId = UserIdParse(username, serverName);
if (!userId)
{
msg = "Invalid user ID.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
goto finish;
}
if (strcmp(userId->server, serverName))
if (!ParserServerNameEquals(userId->server, serverName))
{
/* TODO: Implement lookup over federation. */
msg = "User profile endpoint currently doesn't support lookup over "
"federation.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
@ -79,11 +87,12 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
switch (HttpRequestMethodGet(args->context))
{
case HTTP_GET:
user = UserLock(db, userId->localpart);
user = UserLock(db, userId->local);
if (!user)
{
msg = "Couldn't lock user.";
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
response = MatrixErrorCreate(M_NOT_FOUND, msg);
goto finish;
}
@ -138,24 +147,26 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
StrEquals(entry, "avatar_url"))
{
/* Check if user has privilege to do that action. */
if (strcmp(userId->localpart, UserGetName(user)) == 0)
if (StrEquals(userId->local, UserGetName(user)))
{
value = JsonValueAsString(HashMapGet(request, entry));
/* TODO: Make UserSetProfile notify other
* parties of the change */
/* TODO: Make UserSetProfile notify other parties of
* the change */
UserSetProfile(user, entry, value);
response = HashMapCreate();
goto finish;
}
/* User is not allowed to carry-on the action */
msg = "Cannot change another user's profile.";
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
response = MatrixErrorCreate(M_FORBIDDEN, msg);
goto finish;
}
else
{
msg = "Invalid property being changed.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
goto finish;
}
}
@ -166,17 +177,14 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
goto finish;
}
default:
msg = "Route only accepts GET and PUT.";
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNKNOWN, NULL);
response = MatrixErrorCreate(M_UNKNOWN, msg);
break;
}
finish:
ConfigUnlock(config);
ConfigUnlock(&config);
/* Username is handled by the router, freeing it *will* cause issues
* (see #33). I honestly don't know how it didn't come to bite us sooner.
Free(username); */
Free(entry);
UserIdFree(userId);
UserUnlock(user);
JsonFree(request);

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -36,9 +37,14 @@ ROUTE_IMPL(RouteVersions, path, argp)
(void) argp;
#define DECLARE_SPEC_VERSION(x) ArrayAdd(versions, JsonValueString(x))
DECLARE_SPEC_VERSION("v1.2");
DECLARE_SPEC_VERSION("v1.3");
DECLARE_SPEC_VERSION("v1.4");
DECLARE_SPEC_VERSION("v1.5");
DECLARE_SPEC_VERSION("v1.6");
/* The curently supported version. */
DECLARE_SPEC_VERSION("v1.7");
/* Declare additional spec version support here. */
#undef DECLARE_SPEC_VERSION

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -35,18 +36,19 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
RouteArgs *args = argp;
HashMap *response;
Config *config = ConfigLock(args->matrixArgs->db);
Config config;
if (!config)
ConfigLock(args->matrixArgs->db, &config);
if (!config.ok)
{
Log(LOG_ERR, "Well-known endpoint failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL);
return MatrixErrorCreate(M_UNKNOWN, config.err);
}
if (StrEquals(ArrayGet(path, 0), "client"))
{
response = MatrixClientWellKnown(config->baseUrl, config->identityServer);
response = MatrixClientWellKnown(config.baseUrl, config.identityServer);
}
else
{
@ -54,6 +56,6 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
}
ConfigUnlock(config);
ConfigUnlock(&config);
return response;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -43,13 +44,15 @@ ROUTE_IMPL(RouteWhoami, path, argp)
char *userID;
char *deviceID;
Config *config = ConfigLock(db);
Config config;
if (!config)
ConfigLock(db, &config);
if (!config.ok)
{
Log(LOG_ERR, "Who am I endpoint failed to lock configuration.");
Log(LOG_ERR, "%s", config.err);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
return MatrixErrorCreate(M_UNKNOWN, NULL);
return MatrixErrorCreate(M_UNKNOWN, config.err);
}
(void) path;
@ -73,7 +76,7 @@ ROUTE_IMPL(RouteWhoami, path, argp)
response = HashMapCreate();
userID = StrConcat(4, "@", UserGetName(user), ":", config->serverName);
userID = StrConcat(4, "@", UserGetName(user), ":", config.serverName);
deviceID = StrDuplicate(UserGetDeviceId(user));
UserUnlock(user);
@ -85,6 +88,6 @@ ROUTE_IMPL(RouteWhoami, path, argp)
Free(deviceID);
finish:
ConfigUnlock(config);
ConfigUnlock(&config);
return response;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -132,10 +133,10 @@ TelodendriaPrintHeader(void)
Log(LOG_INFO, "%s", TelodendriaHeader[i]);
}
Log(LOG_INFO, "Telodendria v" TELODENDRIA_VERSION " (%s v%s)", CytoplasmGetName(), CytoplasmGetVersion());
Log(LOG_INFO, "Telodendria v" TELODENDRIA_VERSION " (Cytoplasm v%s)", CytoplasmGetVersionStr());
Log(LOG_INFO, "");
Log(LOG_INFO,
"Copyright (C) 2023 Jordan Bancino <@jordan:bancino.net>");
"Copyright (C) 2024 Jordan Bancino <@jordan:bancino.net>");
Log(LOG_INFO,
"Documentation/Support: https://telodendria.io");
Log(LOG_INFO, "");

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -134,7 +135,7 @@ BuildResponse(Array * flows, Db * db, HashMap ** response, char *session, DbRef
json = DbJson(ref);
HashMapSet(json, "completed", JsonValueArray(ArrayCreate()));
HashMapSet(json, "last_access", JsonValueInteger(UtilServerTs()));
HashMapSet(json, "last_access", JsonValueInteger(UtilTsMillis()));
DbUnlock(db, ref);
HashMapSet(*response, "completed", JsonValueArray(ArrayCreate()));
@ -205,7 +206,7 @@ UiaStageBuild(char *type, HashMap * params)
int
UiaComplete(Array * flows, HttpServerContext * context, Db * db,
HashMap * request, HashMap ** response, Config * config)
HashMap * request, HashMap ** response, Config config)
{
JsonValue *val;
HashMap *auth;
@ -222,6 +223,8 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
HashMap *dbJson;
int ret;
char *msg;
if (!flows)
{
return -1;
@ -242,8 +245,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
if (JsonValueType(val) != JSON_OBJECT)
{
msg = "'auth' is not an object.";
HttpResponseStatus(context, HTTP_BAD_REQUEST);
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
*response = MatrixErrorCreate(M_BAD_JSON, msg);
return 0;
}
@ -252,8 +256,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
if (!val || JsonValueType(val) != JSON_STRING)
{
msg = "'auth->session' is unset or not a string.";
HttpResponseStatus(context, HTTP_BAD_REQUEST);
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
*response = MatrixErrorCreate(M_BAD_JSON, msg);
return 0;
}
@ -311,8 +316,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
if (!val || JsonValueType(val) != JSON_STRING)
{
msg = "'auth->type' is unset or not a string.";
HttpResponseStatus(context, HTTP_BAD_REQUEST);
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
*response = MatrixErrorCreate(M_BAD_JSON, msg);
ret = 0;
goto finish;
}
@ -345,7 +351,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
char *password = JsonValueAsString(HashMapGet(auth, "password"));
HashMap *identifier = JsonValueAsObject(HashMapGet(auth, "identifier"));
char *type;
UserId *userId;
CommonID *userId;
User *user;
if (!password || !identifier)
@ -357,10 +363,11 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
type = JsonValueAsString(HashMapGet(identifier, "type"));
userId = UserIdParse(JsonValueAsString(HashMapGet(identifier, "user")),
config->serverName);
config.serverName);
if (!type || !StrEquals(type, "m.id.user")
|| !userId || !StrEquals(userId->server, config->serverName))
|| !userId
|| !ParserServerNameEquals(userId->server, config.serverName))
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
ret = BuildResponse(flows, db, response, session, dbRef);
@ -368,7 +375,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
goto finish;
}
user = UserLock(db, userId->localpart);
user = UserLock(db, userId->local);
if (!user)
{
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
@ -445,7 +452,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
finish:
ArrayFree(possibleNext);
JsonValueFree(HashMapSet(dbJson, "last_access", JsonValueInteger(UtilServerTs())));
JsonValueFree(HashMapSet(dbJson, "last_access", JsonValueInteger(UtilTsMillis())));
DbUnlock(db, dbRef);
return ret;
}
@ -491,7 +498,7 @@ UiaCleanup(MatrixHttpHandlerArgs * args)
char *session = ArrayGet(sessions, i);
DbRef *ref = DbLock(args->db, 2, "user_interactive", session);
UInt64 lastAccess;
uint64_t lastAccess;
if (!ref)
{
@ -506,7 +513,7 @@ UiaCleanup(MatrixHttpHandlerArgs * args)
/* If last access was greater than 15 minutes ago, remove this
* session */
if (UInt64Gt(UInt64Sub(UtilServerTs(), lastAccess), UInt64Create(0, 1000 * 60 * 15)))
if ((UtilTsMillis() - lastAccess) > (1000 * 60 * 15))
{
DbDelete(args->db, 2, "user_interactive", session);
Log(LOG_DEBUG, "Deleted session %s", session);

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -27,8 +28,8 @@
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Sha.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Int64.h>
#include <Cytoplasm/UInt64.h>
#include <Parser.h>
#include <string.h>
@ -41,7 +42,7 @@ struct User
char *deviceId;
};
int
bool
UserValidate(char *localpart, char *domain)
{
size_t maxLen = 255 - strlen(domain) - 1;
@ -53,23 +54,23 @@ UserValidate(char *localpart, char *domain)
if (i > maxLen)
{
return 0;
return false;
}
if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
(c == '.') || (c == '_') || (c == '=') || (c == '-') ||
(c == '/')))
{
return 0;
return false;
}
i++;
}
return 1;
return true;
}
int
bool
UserHistoricalValidate(char *localpart, char *domain)
{
size_t maxLen = 255 - strlen(domain) - 1;
@ -81,21 +82,21 @@ UserHistoricalValidate(char *localpart, char *domain)
if (i > maxLen)
{
return 0;
return false;
}
if (!((c >= 0x21 && c <= 0x39) || (c >= 0x3B && c <= 0x7E)))
{
return 0;
return false;
}
i++;
}
return 1;
return true;
}
int
bool
UserExists(Db * db, char *name)
{
return DbExists(db, 2, "users", name);
@ -130,7 +131,7 @@ UserAuthenticate(Db * db, char *accessToken)
char *userName;
char *deviceId;
UInt64 expires;
uint64_t expires;
if (!db || !accessToken)
{
@ -154,8 +155,7 @@ UserAuthenticate(Db * db, char *accessToken)
return NULL;
}
if (UInt64Neq(expires, UInt64Create(0, 0)) &&
UInt64Geq(UtilServerTs(), expires))
if (expires && UtilTsMillis() >= expires)
{
UserUnlock(user);
DbUnlock(db, atRef);
@ -168,14 +168,14 @@ UserAuthenticate(Db * db, char *accessToken)
return user;
}
int
bool
UserUnlock(User * user)
{
int ret;
bool ret;
if (!user)
{
return 0;
return false;
}
Free(user->name);
@ -193,7 +193,7 @@ UserCreate(Db * db, char *name, char *password)
User *user = NULL;
HashMap *json = NULL;
UInt64 ts = UtilServerTs();
uint64_t ts = UtilTsMillis();
/* TODO: Put some sort of password policy(like for example at least
* 8 chars, or maybe check it's entropy)? */
@ -230,7 +230,7 @@ UserCreate(Db * db, char *name, char *password)
json = DbJson(user->ref);
HashMapSet(json, "createdOn", JsonValueInteger(ts));
HashMapSet(json, "deactivated", JsonValueBoolean(0));
HashMapSet(json, "deactivated", JsonValueBoolean(false));
return user;
}
@ -353,7 +353,7 @@ UserGetDeviceId(User * user)
return user ? user->deviceId : NULL;
}
int
bool
UserCheckPassword(User * user, char *password)
{
HashMap *json;
@ -365,11 +365,11 @@ UserCheckPassword(User * user, char *password)
char *hashedPwd;
char *tmp;
int result;
bool result;
if (!user || !password)
{
return 0;
return false;
}
json = DbJson(user->ref);
@ -379,7 +379,7 @@ UserCheckPassword(User * user, char *password)
if (!storedHash || !salt)
{
return 0;
return false;
}
tmp = StrConcat(2, password, salt);
@ -395,7 +395,7 @@ UserCheckPassword(User * user, char *password)
return result;
}
int
bool
UserSetPassword(User * user, char *password)
{
HashMap *json;
@ -407,7 +407,7 @@ UserSetPassword(User * user, char *password)
if (!user || !password)
{
return 0;
return false;
}
json = DbJson(user->ref);
@ -425,10 +425,10 @@ UserSetPassword(User * user, char *password)
Free(hashBytes);
Free(tmpstr);
return 1;
return true;
}
int
bool
UserDeactivate(User * user, char * from, char * reason)
{
HashMap *json;
@ -436,7 +436,7 @@ UserDeactivate(User * user, char * from, char * reason)
if (!user)
{
return 0;
return false;
}
/* By default, it's the target's username */
@ -447,7 +447,7 @@ UserDeactivate(User * user, char * from, char * reason)
json = DbJson(user->ref);
JsonValueFree(HashMapSet(json, "deactivated", JsonValueBoolean(1)));
JsonValueFree(HashMapSet(json, "deactivated", JsonValueBoolean(true)));
val = JsonValueString(from);
JsonValueFree(JsonSet(json, val, 2, "deactivate", "by"));
@ -457,38 +457,38 @@ UserDeactivate(User * user, char * from, char * reason)
JsonValueFree(JsonSet(json, val, 2, "deactivate", "reason"));
}
return 1;
return true;
}
int
bool
UserReactivate(User * user)
{
HashMap *json;
if (!user)
{
return 0;
return false;
}
json = DbJson(user->ref);
JsonValueFree(HashMapSet(json, "deactivated", JsonValueBoolean(0)));
JsonValueFree(HashMapSet(json, "deactivated", JsonValueBoolean(false)));
JsonValueFree(HashMapDelete(json, "deactivate"));
return 1;
return true;
}
int
bool
UserDeactivated(User * user)
{
HashMap *json;
if (!user)
{
return 1;
return true;
}
json = DbJson(user->ref);
@ -534,17 +534,17 @@ UserAccessTokenGenerate(User * user, char *deviceId, int withRefresh)
if (withRefresh)
{
token->lifetime = Int64Create(0, 1000 * 60 * 60 * 24 * 7); /* 1 Week */
token->lifetime = 1000 * 60 * 60 * 24 * 7; /* 1 Week */
}
else
{
token->lifetime = Int64Create(0, 0);
token->lifetime = 0;
}
return token;
}
int
bool
UserAccessTokenSave(Db * db, UserAccessToken * token)
{
DbRef *ref;
@ -552,14 +552,14 @@ UserAccessTokenSave(Db * db, UserAccessToken * token)
if (!token)
{
return 0;
return false;
}
ref = DbCreate(db, 3, "tokens", "access", token->string);
if (!ref)
{
return 0;
return false;
}
json = DbJson(ref);
@ -567,9 +567,9 @@ UserAccessTokenSave(Db * db, UserAccessToken * token)
HashMapSet(json, "user", JsonValueString(token->user));
HashMapSet(json, "device", JsonValueString(token->deviceId));
if (Int64Neq(token->lifetime, Int64Create(0, 0)))
if (token->lifetime)
{
HashMapSet(json, "expires", JsonValueInteger(UInt64Add(UtilServerTs(), token->lifetime)));
HashMapSet(json, "expires", JsonValueInteger(UtilTsMillis() + token->lifetime));
}
return DbUnlock(db, ref);
@ -589,7 +589,7 @@ UserAccessTokenFree(UserAccessToken * token)
Free(token);
}
int
bool
UserDeleteToken(User * user, char *token)
{
char *username;
@ -607,14 +607,14 @@ UserDeleteToken(User * user, char *token)
if (!user || !token)
{
return 0;
return false;
}
db = user->db;
/* First check if the token even exists */
if (!DbExists(db, 3, "tokens", "access", token))
{
return 0;
return false;
}
/* If it does, get it's username. */
@ -622,7 +622,7 @@ UserDeleteToken(User * user, char *token)
if (!tokenRef)
{
return 0;
return false;
}
tokenJson = DbJson(tokenRef);
username = JsonValueAsString(HashMapGet(tokenJson, "user"));
@ -632,7 +632,7 @@ UserDeleteToken(User * user, char *token)
{
/* Token does not match user, do not delete it */
DbUnlock(db, tokenRef);
return 0;
return false;
}
userJson = DbJson(user->ref);
@ -640,7 +640,7 @@ UserDeleteToken(User * user, char *token)
if (!deviceObj)
{
return 0;
return false;
}
/* Delete refresh token, if present */
@ -654,17 +654,17 @@ UserDeleteToken(User * user, char *token)
deletedVal = HashMapDelete(deviceObj, deviceId);
if (!deletedVal)
{
return 0;
return false;
}
JsonValueFree(deletedVal);
/* Delete the access token. */
if (!DbUnlock(db, tokenRef) || !DbDelete(db, 3, "tokens", "access", token))
{
return 0;
return false;
}
return 1;
return true;
}
char *
@ -696,7 +696,7 @@ UserSetProfile(User * user, char *name, char *val)
JsonValueFree(JsonSet(json, JsonValueString(val), 2, "profile", name));
}
int
bool
UserDeleteTokens(User * user, char *exempt)
{
HashMap *devices;
@ -705,13 +705,13 @@ UserDeleteTokens(User * user, char *exempt)
if (!user)
{
return 0;
return false;
}
devices = JsonValueAsObject(HashMapGet(DbJson(user->ref), "devices"));
if (!devices)
{
return 0;
return false;
}
while (HashMapIterate(devices, &deviceId, (void **) &deviceObj))
@ -738,7 +738,7 @@ UserDeleteTokens(User * user, char *exempt)
JsonValueFree(HashMapDelete(devices, deviceId));
}
return 1;
return true;
}
int
@ -752,30 +752,30 @@ UserGetPrivileges(User * user)
return UserDecodePrivileges(JsonValueAsArray(HashMapGet(DbJson(user->ref), "privileges")));
}
int
bool
UserSetPrivileges(User * user, int privileges)
{
JsonValue *val;
if (!user)
{
return 0;
return false;
}
if (!privileges)
{
JsonValueFree(HashMapDelete(DbJson(user->ref), "privileges"));
return 1;
return true;
}
val = JsonValueArray(UserEncodePrivileges(privileges));
if (!val)
{
return 0;
return false;
}
JsonValueFree(HashMapSet(DbJson(user->ref), "privileges", val));
return 1;
return true;
}
int
@ -881,10 +881,11 @@ finish:
return arr;
}
UserId *
CommonID *
UserIdParse(char *id, char *defaultServer)
{
UserId *userId;
CommonID *userId;
char *server;
if (!id)
{
@ -897,48 +898,38 @@ UserIdParse(char *id, char *defaultServer)
return NULL;
}
userId = Malloc(sizeof(UserId));
userId = Malloc(sizeof(CommonID));
if (!userId)
{
goto finish;
}
memset(userId, 0, sizeof(CommonID));
/* Fully-qualified user ID */
if (*id == '@')
{
char *localStart = id + 1;
char *serverStart = localStart;
while (*serverStart != ':' && *serverStart != '\0')
if (!ParseCommonID(id, userId) || !userId->server.hostname)
{
serverStart++;
}
UserIdFree(userId);
if (*serverStart == '\0')
{
Free(userId);
userId = NULL;
goto finish;
}
*serverStart = '\0';
serverStart++;
userId->localpart = StrDuplicate(localStart);
userId->server = StrDuplicate(serverStart);
}
else
{
/* Treat it as just a localpart */
userId->localpart = StrDuplicate(id);
userId->server = StrDuplicate(defaultServer);
userId->local = StrDuplicate(id);
ParseServerPart(defaultServer, &userId->server);
}
if (!UserHistoricalValidate(userId->localpart, userId->server))
server = ParserRecomposeServerPart(userId->server);
if (!UserHistoricalValidate(userId->local, server))
{
UserIdFree(userId);
userId = NULL;
}
Free(server);
finish:
Free(id);
@ -946,12 +937,11 @@ finish:
}
void
UserIdFree(UserId * id)
UserIdFree(CommonID * id)
{
if (id)
{
Free(id->localpart);
Free(id->server);
CommonIDFree(*id);
Free(id);
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -48,68 +49,12 @@
* .Xr telodendria-config 7 .
*/
#include <Schema/Config.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Db.h>
/**
* Bit flags that can be set in the flags field of the configuration
* structure.
*/
typedef enum ConfigFlag
{
CONFIG_FEDERATION = (1 << 0),
CONFIG_REGISTRATION = (1 << 1),
CONFIG_LOG_COLOR = (1 << 2),
CONFIG_LOG_FILE = (1 << 3),
CONFIG_LOG_STDOUT = (1 << 4),
CONFIG_LOG_SYSLOG = (1 << 5)
} ConfigFlag;
/**
* The configuration structure is not opaque like many of the other
* structures present in the other public APIs. This is intentional;
* defining functions for all of the fields would simply add too much
* unnecessary overhead.
*/
typedef struct Config
{
/*
* These are used internally and should not be touched outside of
* the functions defined in this API.
*/
Db *db;
DbRef *ref;
/*
* Whether or not the parsing was successful. If this boolean
* value is 0, then read the error message and assume that all
* other fields are invalid.
*/
int ok;
char *err;
char *serverName;
char *baseUrl;
char *identityServer;
char *uid;
char *gid;
unsigned int flags;
size_t maxCache;
char *logTimestamp;
int logLevel;
/*
* An array of HttpServerConfig structures. Consult the HttpServer
* API.
*/
Array *servers;
} Config;
/**
* Parse a JSON object, extracting the necessary values, validating
* them, and adding them to the configuration structure for use by the
@ -120,14 +65,7 @@ typedef struct Config
* set the ok flag to 0. The caller should always check the ok flag,
* and if there is an error, it should display the error to the user.
*/
extern Config * ConfigParse(HashMap *);
/**
* Free all the values inside of the given configuration structure,
* as well as the structure itself, such that it is completely invalid
* when this function returns.
*/
extern void ConfigFree(Config *);
extern void ConfigParse(HashMap *, Config *);
/**
* Check whether or not the configuration exists in the database,
@ -150,7 +88,7 @@ extern int ConfigCreateDefault(Db *);
* The return value of this function is the same as
* .Fn ConfigParse .
*/
extern Config * ConfigLock(Db *);
extern void ConfigLock(Db *, Config *);
/**
* Unlock the specified configuration, returning it back to the
@ -160,4 +98,9 @@ extern Config * ConfigLock(Db *);
*/
extern int ConfigUnlock(Config *);
/**
* Converts a ConfigLogLevel into a valid syslog level.
*/
extern int ConfigLogLevelToSyslog(ConfigLogLevel);
#endif /* TELODENDRIA_CONFIG_H */

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

108
src/include/Parser.h Normal file
View File

@ -0,0 +1,108 @@
/*
* 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.
*/
#ifndef TELODENDRIA_PARSER_H
#define TELODENDRIA_PARSER_H
#include <stdbool.h>
/***
* @Nm Parser
* @Nd Functions for dealing with grammars found in Matrix
* @Dd November 25 2023
* @Xr User
*
* The
* .Nm
* API provides an interface for parsing grammars within the
* Matrix specification
*/
/**
* The host[:port] format in a servername.
*/
typedef struct ServerPart {
char *hostname;
char *port;
} ServerPart;
/**
* A common identifier in the form '&local[:server]', where & determines the
* *type* of the identifier.
*/
typedef struct CommonID {
char sigil;
char *local;
ServerPart server;
} CommonID;
/**
* Parses a common identifier, as per the Common Identifier Format as defined
* by the [matrix] specification.
*/
extern bool ParseCommonID(char *, CommonID *);
/**
* Parses the server part in a common identifier.
*/
extern bool ParseServerPart(char *, ServerPart *);
/**
* Checks whenever the string is a valid common ID with the correct sigil.
*/
extern bool ValidCommonID(char *, char);
/**
* Frees a CommonID's values. Note that it doesn't free the CommonID itself.
*/
extern void CommonIDFree(CommonID);
/**
* Frees a ServerPart's values. Note that it doesn't free the ServerPart
* itself, and that
* .Fn CommonIDFree
* automatically deals with its server part.
*/
extern void ServerPartFree(ServerPart);
/**
* Recompose a Common ID into a string which lives in the heap, and must
* therefore be freed with
* .Fn Free .
*/
extern char * ParserRecomposeCommonID(CommonID);
/**
* Recompose a server part into a string which lives in the heap, and must
* therefore be freed with
* .Fn Free .
*/
extern char * ParserRecomposeServerPart(ServerPart);
/**
* Compares whenever a ServerName is equivalent to a server name string.
*/
extern bool ParserServerNameEquals(ServerPart, char *);
#endif /* TELODENDRIA_PARSER_H */

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -40,7 +41,6 @@
*/
#include <Cytoplasm/Db.h>
#include <Cytoplasm/Int64.h>
#include <Schema/RegToken.h>
@ -77,7 +77,7 @@ extern RegTokenInfo * RegTokenGetInfo(Db *, char *);
* structure will be returned. Otherwise, NULL will be returned.
*/
extern RegTokenInfo *
RegTokenCreate(Db *, char *, char *, UInt64, Int64, int);
RegTokenCreate(Db *, char *, char *, uint64_t, int64_t, int);
/**
* Free the memory associated with the registration token. This should

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -132,7 +133,7 @@ extern void UiaCleanup(MatrixHttpHandlerArgs *);
* the caller proceed with its logic.
*/
extern int
UiaComplete(Array *, HttpServerContext *, Db *, HashMap *, HashMap **, Config *);
UiaComplete(Array *, HttpServerContext *, Db *, HashMap *, HashMap **, Config);
/**
* Free an array of flows, as described above. Even though the caller

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -38,10 +39,13 @@
* users, among many other tasks.
*/
#include <Cytoplasm/Int64.h>
#include <Cytoplasm/Db.h>
#include <Cytoplasm/Json.h>
#include <Parser.h>
#include <stdbool.h>
/**
* Many functions here operate on an opaque user structure.
*/
@ -74,7 +78,7 @@ typedef struct UserAccessToken
char *user;
char *string;
char *deviceId;
Int64 lifetime;
uint64_t lifetime;
} UserAccessToken;
/**
@ -87,15 +91,6 @@ typedef struct UserLoginInfo
char *refreshToken;
} UserLoginInfo;
/**
* A description of a Matrix user ID.
*/
typedef struct UserId
{
char *localpart;
char *server;
} UserId;
/**
* Take a localpart and domain as separate parameters and validate them
* against the rules of the Matrix specification. The reasion the
@ -104,7 +99,7 @@ typedef struct UserId
* the local part is allowed to be. This function is used to ensure
* that client-provided Matrix IDs are valid on this server.
*/
extern int UserValidate(char *, char *);
extern bool UserValidate(char *, char *);
/**
* This function behaves just like
@ -115,13 +110,13 @@ extern int UserValidate(char *, char *);
* spec compliant but remain in use since before the new restrictions
* were put in place.
*/
extern int UserHistoricalValidate(char *, char *);
extern bool UserHistoricalValidate(char *, char *);
/**
* Determine whether the user identified by the specified localpart
* exists in the database.
*/
extern int UserExists(Db *, char *);
extern bool UserExists(Db *, char *);
/**
* Create a new user with the specified localpart and password, in
@ -152,7 +147,7 @@ extern User * UserAuthenticate(Db *, char *);
* .Fn DbUnlock
* under the hood.
*/
extern int UserUnlock(User *);
extern bool UserUnlock(User *);
/**
* Log in a user. This function takes the user's password, desired
@ -185,13 +180,13 @@ extern char * UserGetDeviceId(User *);
* does not store passwords in plain text, so this function hashes the
* password and checks it against what is stored in the database.
*/
extern int UserCheckPassword(User *, char *);
extern bool UserCheckPassword(User *, char *);
/**
* Reset the given user's password by hashing a plain text password and
* storing it in the database.
*/
extern int UserSetPassword(User *, char *);
extern bool UserSetPassword(User *, char *);
/**
* Immediately deactivate the given user account such that it can no
@ -204,21 +199,21 @@ extern int UserSetPassword(User *, char *);
* responsible for deactivating the target user is NULL, then it is
* set to the target's own name.
*/
extern int UserDeactivate(User *, char *, char *);
extern bool UserDeactivate(User *, char *, char *);
/**
* Reactivates the given user account if it has been deactvated with
* .Fn UserDeactivate ,
* otherwise, it simply doesn't do anything.
*/
extern int UserReactivate(User *);
extern bool UserReactivate(User *);
/**
* Return a boolean value indicating whether or not the user was
* deactivated using
* .Fn UserDeactivate .
*/
extern int UserDeactivated(User *);
extern bool UserDeactivated(User *);
/**
* Fetches the devices that belong to the user, in JSON format,
@ -239,7 +234,7 @@ extern UserAccessToken * UserAccessTokenGenerate(User *, char *, int);
* Write the specified access token to the database, returning a
* boolean value indicating success.
*/
extern int UserAccessTokenSave(Db *, UserAccessToken *);
extern bool UserAccessTokenSave(Db *, UserAccessToken *);
/**
* Free the memory associated with the given access token.
@ -249,7 +244,7 @@ extern void UserAccessTokenFree(UserAccessToken *);
/**
* Delete a specific access token by name.
*/
extern int UserDeleteToken(User *, char *);
extern bool UserDeleteToken(User *, char *);
/**
* Get a string property from the user's profile given the specified
@ -268,7 +263,7 @@ extern void UserSetProfile(User *, char *, char *);
* except for the one provided by name, unless NULL is provided for
* the name.
*/
extern int UserDeleteTokens(User *, char *);
extern bool UserDeleteTokens(User *, char *);
/**
* Get the current privileges of the user as a packed bit field. Use
@ -280,7 +275,7 @@ extern int UserGetPrivileges(User *);
/**
* Set the privileges of the user.
*/
extern int UserSetPrivileges(User *, int);
extern bool UserSetPrivileges(User *, int);
/**
* Decode the JSON that represents the user privileges into a packed
@ -302,15 +297,15 @@ extern Array *UserEncodePrivileges(int);
extern int UserDecodePrivilege(const char *);
/**
* Parse either a localpart or a fully qualified Matrix ID. If the
* Parse either a localpart or a fully qualified Matrix common ID. If the
* first argument is a localpart, then the second argument is used as
* the server name.
*/
extern UserId * UserIdParse(char *, char *);
extern CommonID * UserIdParse(char *, char *);
/**
* Free the memory associated with the parsed Matrix ID.
* Frees the user's common ID and the memory allocated for it.
*/
extern void UserIdFree(UserId *);
extern void UserIdFree(CommonID *);
#endif /* TELODENDRIA_USER_H */

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2022-2023 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
@ -63,47 +64,23 @@ query(char *select, HashMap * json, int canonical)
int expectArr = 0;
int func = 0;
expectArr = (sscanf(key, "%127[^[][%lu]", keyName, &arrInd) == 2);
expectArr = (sscanf(key, "%127[^[][%zu]", keyName, &arrInd) == 2);
if (keyName[0] == '@')
{
if (StrEquals(keyName + 1, "length"))
{
UInt64 len;
uint64_t len;
switch (JsonValueType(val))
{
case JSON_ARRAY:
if (sizeof(size_t) == sizeof(UInt64))
{
size_t slen = ArraySize(JsonValueAsArray(val));
UInt32 high = slen >> 32;
UInt32 low = slen;
len = UInt64Create(high, low);
}
else
{
len = UInt64Create(0, ArraySize(JsonValueAsArray(val)));
}
len = ArraySize(JsonValueAsArray(val));
val = JsonValueInteger(len);
ArrayAdd(cleanUp, val);
break;
case JSON_STRING:
if (sizeof(size_t) == sizeof(UInt64))
{
size_t slen = strlen(JsonValueAsString(val));
UInt32 high = slen >> 32;
UInt32 low = slen;
len = UInt64Create(high, low);
}
else
{
len = UInt64Create(0, strlen(JsonValueAsString(val)));
}
len = strlen(JsonValueAsString(val));
val = JsonValueInteger(len);
ArrayAdd(cleanUp, val);
break;
@ -153,7 +130,7 @@ query(char *select, HashMap * json, int canonical)
{
size_t i;
if (sscanf(keyName + 1, "%lu", &i) == 1)
if (sscanf(keyName + 1, "%zu", &i) == 1)
{
JsonValueFree(ArrayDelete(JsonValueAsArray(val), i));
}