forked from lda/telodendria
Compare commits
97 commits
refactor-6
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
d5941986ba | ||
71491ab6fc | |||
5041e0c991 | |||
66c237727b | |||
505d01cea1 | |||
c7433bb745 | |||
8546ddb909 | |||
0e946b2422 | |||
12e11f74a1 | |||
3304f8c08f | |||
59e3a6d3e2 | |||
46dcf8ab59 | |||
21b830e514 | |||
42223c94bb | |||
e083ece2ed | |||
2fcd51e810 | |||
775b1d9571 | |||
|
1e544a4927 | ||
|
4bc7ae92d6 | ||
6dd4440125 | |||
e263eca5dc | |||
ac9372a30a | |||
a8924b8437 | |||
b72f18538d | |||
ff85b72899 | |||
bccbb3bcac | |||
f815f653b0 | |||
dede82ad33 | |||
fde2b26857 | |||
6305f5d76e | |||
95a5f6f087 | |||
129802fe94 | |||
c7d44866c3 | |||
85672985eb | |||
19443a1c24 | |||
15fb6d8c2a | |||
cd22aea772 | |||
ae0724f01c | |||
e62389aa14 | |||
f2a4a64b27 | |||
258f509413 | |||
2e92daeb00 | |||
ac7ea4dec1 | |||
54420f0036 | |||
243de4f1a0 | |||
35b9ef51f9 | |||
83eb69f2cd | |||
22d0e88dde | |||
f83be63d53 | |||
af4fd5dceb | |||
8231e8a078 | |||
12061afe62 | |||
2c6d5194d2 | |||
18488d463e | |||
184a0ef1de | |||
e8d6f0f857 | |||
829b9bd6b4 | |||
0a91a0c40b | |||
d3dcf334f0 | |||
3dae19e82d | |||
2d30719be4 | |||
503bfb6104 | |||
77800e4117 | |||
db4ae0408c | |||
edee1288d8 | |||
4b90800a2b | |||
6e7f170768 | |||
1f02f3c2a2 | |||
582c79b608 | |||
f4cb10804a | |||
3f69954ca7 | |||
|
afc9e0e5dc | ||
5067b5bcf0 | |||
36c07ed17d | |||
d7afd6285d | |||
69e7837fb4 | |||
6f9ac042b5 | |||
9b65558a5a | |||
16a45e67e6 | |||
afde23df70 | |||
b8c99a2b1f | |||
c781950fff | |||
60ebbccf7d | |||
73dd12ef7e | |||
20c4a4d0fb | |||
4168557c4b | |||
5ad199353b | |||
b32aababb3 | |||
82d4711f7f | |||
66537d1b0c | |||
6eb16af9b5 | |||
cb6963299c | |||
c2294723e6 | |||
5c7ce30b15 | |||
63a6bcce1a | |||
6f14d0eef3 | |||
d9217946f3 |
91 changed files with 2854 additions and 1994 deletions
27
.forgejo/workflows/compile.yaml
Normal file
27
.forgejo/workflows/compile.yaml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
name: Compile Telodendria
|
||||||
|
run-name: Compile Telodendria on ${{ forgejo.actor }}
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'ma*'
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
"Compile Telodendria":
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [alpine]
|
||||||
|
arch: [aarch64]
|
||||||
|
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Configure Telodendria
|
||||||
|
run: ./configure
|
||||||
|
- name: Configure & Build Cytoplasm
|
||||||
|
run: make cytoplasm
|
||||||
|
- name: Build Telodendria
|
||||||
|
run: make
|
44
.forgejo/workflows/release.yaml
Normal file
44
.forgejo/workflows/release.yaml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
name: Release Telodendria
|
||||||
|
run-name: Release Telodendria on ${{ forgejo.actor }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
"Release Telodendria":
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [alpine]
|
||||||
|
arch: [aarch64]
|
||||||
|
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Archive submodules
|
||||||
|
run: git submodule foreach --recursive 'git archive --format tar --prefix=$displaypath/ -o submodule.tar HEAD'
|
||||||
|
- name: Archive repository
|
||||||
|
run: git archive --format tar -o release.tar HEAD
|
||||||
|
- name: Produce release archive
|
||||||
|
run: |
|
||||||
|
TOPDIR=$(pwd) git submodule --quiet foreach --recursive 'cd $TOPDIR; tar --concatenate --file=release.tar $displaypath/submodule.tar; rm -fv $displaypath/submodule.tar'
|
||||||
|
gzip release.tar
|
||||||
|
mkdir release
|
||||||
|
mv release.tar.gz release/Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||||
|
path: release/Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||||
|
- name: Publish release
|
||||||
|
uses: actions/forgejo-release@v2
|
||||||
|
with:
|
||||||
|
tag: $GITHUB_REF_NAME
|
||||||
|
title: "Telodendria $GITHUB_REF_NAME"
|
||||||
|
release-dir: release/
|
||||||
|
release-notes: "docs/CHANGELOG.md"
|
||||||
|
direction: upload
|
||||||
|
prerelease: true
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "Cytoplasm"]
|
||||||
|
path = Cytoplasm
|
||||||
|
url = https://git.telodendria.io/Telodendria/Cytoplasm.git
|
16
CONTRIBUTORS.txt
Normal file
16
CONTRIBUTORS.txt
Normal 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
1
Cytoplasm
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 4f316ff7b3a955b831ca4aefb8679ddf3396a7d0
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
25
README.md
25
README.md
|
@ -1,7 +1,6 @@
|
||||||
<p align="center"><img src="https://telodendria.io/assets/Telodendria-500x500.png"></p>
|
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
|
||||||
<h1 align="center">Telodendria</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
|
chat server designed to be easy to install and configure. Powered by
|
||||||
the [Matrix](https://matrix.org) protocol, Telodendria empowers
|
the [Matrix](https://matrix.org) protocol, Telodendria empowers
|
||||||
everyone to run their own chat server on ordinary hardware, including
|
everyone to run their own chat server on ordinary hardware, including
|
||||||
|
@ -12,7 +11,7 @@ hosting a complicated, high-maintenance homeserver or joining an
|
||||||
existing homeserver for privacy or other reasons, then Telodendria
|
existing homeserver for privacy or other reasons, then Telodendria
|
||||||
might be for you.
|
might be for you.
|
||||||
|
|
||||||
> **Note:** Telodendria still in development. See [Status](#status).
|
!!!! **Note:** Telodendria still in development. See **Status** below.
|
||||||
|
|
||||||
## What is Matrix?
|
## What is Matrix?
|
||||||
|
|
||||||
|
@ -64,12 +63,13 @@ incredibly outdated. Telodendria, on the other hand, aims to be stable.
|
||||||
It should *just work* for long periods of time between upgrades, and
|
It should *just work* for long periods of time between upgrades, and
|
||||||
you should never feel like Telodendria is going to change significantly
|
you should never feel like Telodendria is going to change significantly
|
||||||
between upgrades.
|
between upgrades.
|
||||||
|
- **Well-Documented:** Telodendria places as much emphasis on documentation as on code, which means you can be sure that the documentation will always remain up-to-date, accurate, and most importantly, reasonably exhaustive.
|
||||||
|
|
||||||
[Read Technical Rationale →](docs/dev/rationale.md)
|
[Read Technical Rationale →](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/dev/rationale.md)
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
Check out the [Documentation](docs/README.md) to get started with
|
Check out the [Documentation](https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md) to get started with
|
||||||
Telodendria.
|
Telodendria.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
@ -79,13 +79,13 @@ not yet deliver on all of its promises. Currently, Telodendria is not
|
||||||
ready for end-users yet. While it features very basic user
|
ready for end-users yet. While it features very basic user
|
||||||
authentication, it does not actually work as a chat server yet.
|
authentication, it does not actually work as a chat server yet.
|
||||||
|
|
||||||
We are hoping to ship Telodendria `v0.4.0` 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**
|
release should be usable for communication between **local users**
|
||||||
only. Additional features, including federation with other Matrix
|
only. Additional features, including federation with other Matrix
|
||||||
homeservers will be added in future releases.
|
homeservers will be added in future releases.
|
||||||
|
|
||||||
You can help speed up development by [sponsoring](#sponsorship)
|
You can help speed up development by **sponsoring**
|
||||||
Telodendria or [getting involved](docs/CONTRIBUTING.md).
|
Telodendria or [getting involved](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/CONTRIBUTING.md).
|
||||||
|
|
||||||
## Sponsorship
|
## Sponsorship
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ Telodendria's long-term success, please consider sponsoring the
|
||||||
project.
|
project.
|
||||||
|
|
||||||
You can make a recurring donation to Telodendria using
|
You can make a recurring donation to Telodendria using
|
||||||
[LiberaPay](https://bancino.net/Telodendria/donate). You can also make
|
[LiberaPay](https://liberapay.com/Telodendria/donate). You can also make
|
||||||
one-time donations using
|
one-time donations using
|
||||||
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
|
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
|
||||||
like to make a recurring donation larger than that allowed by
|
like to make a recurring donation larger than that allowed by
|
||||||
|
@ -108,7 +108,7 @@ LiberaPay, please contact Jordan Bancino over Matrix at
|
||||||
While there are no set sponsorship tiers at this time, sponsoring
|
While there are no set sponsorship tiers at this time, sponsoring
|
||||||
Telodendria is a mutually beneficial relationship. Depending on the
|
Telodendria is a mutually beneficial relationship. Depending on the
|
||||||
amount you donate, you can get your name, logo, and website links
|
amount you donate, you can get your name, logo, and website links
|
||||||
on the [Sponsors](docs/SPONSORS.md) page, the project `README`, or the
|
on the [Sponsors](../sponsors) page, the project `README`, or the
|
||||||
main website.
|
main website.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
@ -116,7 +116,7 @@ main website.
|
||||||
All of the code and documentation for Telodendria is licensed under a
|
All of the code and documentation for Telodendria is licensed under a
|
||||||
modified MIT license. The MIT license is an extremely permissive
|
modified MIT license. The MIT license is an extremely permissive
|
||||||
license that has very few restrictions. Please consult the
|
license that has very few restrictions. Please consult the
|
||||||
[`LICENSE.txt`](LICENSE.txt) file for the actual license text. It is
|
[`LICENSE.txt`](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/LICENSE.txt) file for the actual license text. It is
|
||||||
important to note that the Telodendria license text differs from the
|
important to note that the Telodendria license text differs from the
|
||||||
original MIT license in the following ways:
|
original MIT license in the following ways:
|
||||||
|
|
||||||
|
@ -133,4 +133,3 @@ to use the logo in any way as long as it represents or links to the
|
||||||
official project. If Telodendria is forked, the logo must be removed
|
official project. If Telodendria is forked, the logo must be removed
|
||||||
completely from the project, and optionally replaced by a different
|
completely from the project, and optionally replaced by a different
|
||||||
one.
|
one.
|
||||||
|
|
||||||
|
|
88
Schema/Config.json
Normal file
88
Schema/Config.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"guard": "TELODENDRIA_SCHEMA_FILTER_H",
|
||||||
"header": "Schema\/Filter.h",
|
"header": "Schema\/Filter.h",
|
||||||
"types": {
|
"types": {
|
||||||
"FilterRoom": {
|
"FilterRoom": {
|
||||||
|
@ -116,6 +117,5 @@
|
||||||
},
|
},
|
||||||
"type": "struct"
|
"type": "struct"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"guard": "TELODENDRIA_SCHEMA_FILTER_H"
|
|
||||||
}
|
}
|
||||||
|
|
38
Schema/LoginRequest.json
Normal file
38
Schema/LoginRequest.json
Normal 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"
|
||||||
|
}
|
||||||
|
|
49
Schema/RegToken.json
Normal file
49
Schema/RegToken.json
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"guard": "TELODENDRIA_SCHEMA_REGTOKEN_H",
|
||||||
|
"header": "Schema\/RegToken.h",
|
||||||
|
"include": [
|
||||||
|
"Cytoplasm\/Db.h"
|
||||||
|
],
|
||||||
|
"types": {
|
||||||
|
"Db *": {
|
||||||
|
"type": "extern"
|
||||||
|
},
|
||||||
|
"DbRef *": {
|
||||||
|
"type": "extern"
|
||||||
|
},
|
||||||
|
"RegTokenInfo": {
|
||||||
|
"fields": {
|
||||||
|
"db": {
|
||||||
|
"type": "Db *",
|
||||||
|
"ignore": true
|
||||||
|
},
|
||||||
|
"ref": {
|
||||||
|
"type": "DbRef *",
|
||||||
|
"ignore": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_on": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"expires_on": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"used": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"uses": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"grants": {
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "struct"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
Schema/Registration.json
Normal file
17
Schema/Registration.json
Normal 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
21
Schema/RequestToken.json
Normal 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"
|
||||||
|
}
|
13
Schema/UserDirectoryRequest.json
Normal file
13
Schema/UserDirectoryRequest.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"header": "Schema\/UserDirectoryRequest.h",
|
||||||
|
"types": {
|
||||||
|
"UserDirectoryRequest": {
|
||||||
|
"fields": {
|
||||||
|
"search_term": { "type": "string" },
|
||||||
|
"limit": { "type": "integer" }
|
||||||
|
},
|
||||||
|
"type": "struct"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"guard": "TELODENDRIA_SCHEMA_USERDIRECTORYREQUEST_H"
|
||||||
|
}
|
154
configure
vendored
154
configure
vendored
|
@ -13,13 +13,40 @@ SRC="src"
|
||||||
INCLUDE="src/include"
|
INCLUDE="src/include"
|
||||||
TOOLS="tools/src"
|
TOOLS="tools/src"
|
||||||
SCHEMA="Schema"
|
SCHEMA="Schema"
|
||||||
|
CYTOPLASM="Cytoplasm"
|
||||||
|
|
||||||
CFLAGS="-Wall -Wextra -pedantic -std=c89 -O3 -pipe -D_DEFAULT_SOURCE -I${INCLUDE}"
|
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
|
||||||
LIBS="-lm -pthread -lCytoplasm"
|
LIBS="-lm -pthread -lCytoplasm"
|
||||||
|
|
||||||
|
|
||||||
# Set default args for all platforms
|
# Set default args for all platforms
|
||||||
SCRIPT_ARGS="--prefix=/usr/local --enable-ld-extra --bin-name=telodendria --version=0.4.0 --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|Darwin)
|
||||||
|
# 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 "Processing options..."
|
||||||
echo "Ran with arguments: $SCRIPT_ARGS"
|
echo "Ran with arguments: $SCRIPT_ARGS"
|
||||||
|
@ -27,15 +54,20 @@ echo "Ran with arguments: $SCRIPT_ARGS"
|
||||||
# Process all arguments
|
# Process all arguments
|
||||||
for arg in $SCRIPT_ARGS; do
|
for arg in $SCRIPT_ARGS; do
|
||||||
case "$arg" in
|
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=*)
|
||||||
PREFIX=$(echo "$arg" | cut -d '=' -f 2-)
|
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=*)
|
||||||
BIN_NAME=$(echo "$arg" | cut -d '=' -f 2-)
|
BIN_NAME=$(echo "$arg" | cut -d '=' -f 2-)
|
||||||
;;
|
;;
|
||||||
|
@ -44,30 +76,32 @@ for arg in $SCRIPT_ARGS; do
|
||||||
;;
|
;;
|
||||||
--enable-debug)
|
--enable-debug)
|
||||||
DEBUG="-O0 -g"
|
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)
|
--disable-debug)
|
||||||
DEBUG=""
|
DEBUG=""
|
||||||
;;
|
;;
|
||||||
--static)
|
--cytoplasm=*)
|
||||||
STATIC="-static -Wl,-static"
|
CYTOPLASM=$(echo "$arg" | cut -d '=' -f 2-)
|
||||||
;;
|
if [ -n "${CYTOPLASM}" ]; then
|
||||||
--no-static)
|
if [ ! -f "${CYTOPLASM}/configure" ]; then
|
||||||
STATIC=""
|
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: $1"
|
echo "Invalid argument: $arg"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
CFLAGS="${CFLAGS} '-DTELODENDRIA_VERSION=\"${VERSION}\"' ${DEBUG}"
|
CFLAGS="${CFLAGS} '-DTELODENDRIA_VERSION=\"${VERSION}\"' ${DEBUG}"
|
||||||
LDFLAGS="${LIBS} ${LD_EXTRA}"
|
LDFLAGS="${LDFLAGS} ${LIBS}"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Makefile generation
|
# Makefile generation
|
||||||
|
@ -100,6 +134,16 @@ prefix() {
|
||||||
done
|
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() {
|
print_src() {
|
||||||
printf '%s ' "$1"
|
printf '%s ' "$1"
|
||||||
}
|
}
|
||||||
|
@ -108,12 +152,28 @@ print_obj() {
|
||||||
printf '%s ' "$2"
|
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() {
|
compile_obj() {
|
||||||
src="$1"
|
src="$1"
|
||||||
obj="$2"
|
obj="$2"
|
||||||
|
|
||||||
pref=$(cc -I${INCLUDE} -MM -MT "${obj}" "${src}")
|
pref="${obj}: $(get_deps ${src})"
|
||||||
echo "$pref $(collect ${SCHEMA}/ .json .h ${INCLUDE}/Schema/ print_obj)"
|
echo "$pref $(collect ${SCHEMA}/ .json .h ${BUILD}/Schema/ print_obj)"
|
||||||
echo "${TAB}@mkdir -p $(dirname ${obj})"
|
echo "${TAB}@mkdir -p $(dirname ${obj})"
|
||||||
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
|
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
|
||||||
}
|
}
|
||||||
|
@ -126,7 +186,7 @@ compile_bin() {
|
||||||
|
|
||||||
echo "${out}: ${src}"
|
echo "${out}: ${src}"
|
||||||
echo "${TAB}@mkdir -p ${OUT}/bin"
|
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() {
|
compile_doc() {
|
||||||
|
@ -139,7 +199,7 @@ compile_doc() {
|
||||||
|
|
||||||
echo "${out}: ${src}"
|
echo "${out}: ${src}"
|
||||||
echo "${TAB}@mkdir -p ${OUT}/man/man3"
|
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() {
|
print_doc() {
|
||||||
|
@ -153,27 +213,37 @@ compile_schema() {
|
||||||
src="$1"
|
src="$1"
|
||||||
out="$2"
|
out="$2"
|
||||||
|
|
||||||
echo "${INCLUDE}/Schema/${out}.h:"
|
obj="${BUILD}/Schema/${out}.o"
|
||||||
echo "${TAB}@mkdir -p ${INCLUDE}/Schema ${SRC}/Schema"
|
|
||||||
echo "${TAB}j2s -s \"${src}\" -h \"${INCLUDE}/Schema/${out}.h\" -c \"${SRC}/Schema/${out}.c\""
|
|
||||||
|
|
||||||
echo "${SRC}/Schema/${out}.c:"
|
echo "${BUILD}/Schema/${out}.h:"
|
||||||
echo "${TAB}@mkdir -p ${INCLUDE}/Schema ${SRC}/Schema"
|
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||||
echo "${TAB}j2s -s \"${src}\" -h \"${INCLUDE}/Schema/${out}.h\" -c \"${SRC}/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}$(cytoplasm_tool j2s) -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
|
||||||
|
|
||||||
|
echo "${obj}: ${src} ${BUILD}/Schema/${out}.c"
|
||||||
|
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||||
|
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${BUILD}/Schema/${out}.c\""
|
||||||
}
|
}
|
||||||
|
|
||||||
install_out() {
|
install_out() {
|
||||||
src="$1"
|
src="$1"
|
||||||
out="$2"
|
out="$2"
|
||||||
|
dir=$(dirname "$out")
|
||||||
|
|
||||||
echo "${TAB}install -D \"$src\" \"$out\""
|
echo "${TAB}mkdir -p \"$dir\""
|
||||||
|
echo "${TAB}cp \"$src\" \"$out\""
|
||||||
}
|
}
|
||||||
|
|
||||||
install_man() {
|
install_man() {
|
||||||
src="${OUT}/man/man3/${BIN_NAME}-$(basename $1 .h).3"
|
src="${OUT}/man/man3/${BIN_NAME}-$(basename $1 .h).3"
|
||||||
out="$2"
|
out="$2"
|
||||||
|
dir=$(dirname "$out")
|
||||||
|
|
||||||
echo "${TAB}install -D \"$src\" \"$out\""
|
echo "${TAB}mkdir -p \"$dir\""
|
||||||
|
echo "${TAB}cp \"$src\" \"$out\""
|
||||||
}
|
}
|
||||||
|
|
||||||
uninstall_out() {
|
uninstall_out() {
|
||||||
|
@ -185,22 +255,16 @@ uninstall_out() {
|
||||||
|
|
||||||
echo "Generating Makefile..."
|
echo "Generating Makefile..."
|
||||||
|
|
||||||
OBJS=$(collect ${SRC}/ .c .o ${BUILD}/ print_obj)
|
OBJS="$(collect ${SRC}/ .c .o ${BUILD}/ print_obj) $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
|
||||||
TAB=$(printf '\t')
|
TAB=$(printf '\t')
|
||||||
|
|
||||||
# If objects don't include the schema (this is the first configure),
|
|
||||||
# then include them manually.
|
|
||||||
if ! echo "${OBJS}" | grep "Schema" > /dev/null; then
|
|
||||||
OBJS="${OBJS} $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat << EOF > Makefile
|
cat << EOF > Makefile
|
||||||
.POSIX:
|
.POSIX:
|
||||||
|
|
||||||
# Generated by '$0' on $(date).
|
# Generated by '$0' on $(date).
|
||||||
# This file should generally not be manually edited.
|
# This file should generally not be manually edited.
|
||||||
|
|
||||||
CC = cc
|
CC = ${CC}
|
||||||
PREFIX = ${PREFIX}
|
PREFIX = ${PREFIX}
|
||||||
CFLAGS = ${CFLAGS}
|
CFLAGS = ${CFLAGS}
|
||||||
LDFLAGS = ${LDFLAGS}
|
LDFLAGS = ${LDFLAGS}
|
||||||
|
@ -227,7 +291,8 @@ ${TAB}done
|
||||||
${BIN_NAME}: ${OUT}/bin/${BIN_NAME}
|
${BIN_NAME}: ${OUT}/bin/${BIN_NAME}
|
||||||
|
|
||||||
install: ${BIN_NAME}
|
install: ${BIN_NAME}
|
||||||
${TAB}install -D ${OUT}/bin/${BIN_NAME} \$(PREFIX)/bin/${BIN_NAME}
|
${TAB}mkdir -p \$(PREFIX)/bin
|
||||||
|
${TAB}cp ${OUT}/bin/${BIN_NAME} \$(PREFIX)/bin/${BIN_NAME}
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
${TAB}rm \$(PREFIX)/bin/${BIN_NAME}
|
${TAB}rm \$(PREFIX)/bin/${BIN_NAME}
|
||||||
|
@ -244,6 +309,13 @@ $(collect ${SRC}/ .c .o ${BUILD}/ compile_obj)
|
||||||
$(collect ${TOOLS}/ .c '' ${OUT}/bin/ compile_bin)
|
$(collect ${TOOLS}/ .c '' ${OUT}/bin/ compile_bin)
|
||||||
$(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${BIN_NAME}- compile_doc)
|
$(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
|
EOF
|
||||||
|
|
||||||
echo "Done. Run 'make' to build ${BIN_NAME}."
|
echo "Done. Run 'make' to build ${BIN_NAME}."
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -5,12 +5,22 @@ It is intended to be updated with every commit that makes a user-facing change w
|
||||||
reporting in the change log. As such, it changes frequently between releases. Final
|
reporting in the change log. As such, it changes frequently between releases. Final
|
||||||
change log entries are published as [Releases](releases).
|
change log entries are published as [Releases](releases).
|
||||||
|
|
||||||
## v0.4.0
|
## v1.7.0-alpha4
|
||||||
|
|
||||||
**Not Released Yet.**
|
**Not Released Yet.**
|
||||||
|
|
||||||
This release brings filters, rooms, and events! The core of the Matrix protocol architecture
|
This release brings filters, rooms, and events! The core of the Matrix
|
||||||
is not in place.
|
protocol architecture is now in place.
|
||||||
|
|
||||||
|
Note that the versioning scheme has changed from `v0.X.0` to
|
||||||
|
`v1.7.0-alphaX`. This is so that Telodendria releases correspond to the
|
||||||
|
Matrix specification that they implement, in accordance with
|
||||||
|
[this blog post](https://telodendria.io/blog/on-matrixs-release-cadence-and-state-resolution-v1).
|
||||||
|
This versioning scheme change does not indicate a drastic leap forward
|
||||||
|
in Telodendria's development—the `-alpha4` suffix indicates that
|
||||||
|
this is the 4th pre-release, with the target being a stable `v1.7.0`.
|
||||||
|
Note also that we still have a *long* way to go before we reach that
|
||||||
|
stable release.
|
||||||
|
|
||||||
### Matrix Specification
|
### Matrix Specification
|
||||||
|
|
||||||
|
@ -21,19 +31,39 @@ The following endpoints were added:
|
||||||
|
|
||||||
### Bug Fixes & General Improvements
|
### Bug Fixes & General Improvements
|
||||||
|
|
||||||
- Fixed a double-free in `RouteUserProfile()` that would cause errors with certain
|
- Use `j2s` for parsing the configuration
|
||||||
Matrix clients. (#35)
|
- Fixed a double-free in `RouteUserProfile()` that would cause errors
|
||||||
|
with certain Matrix clients. (#35)
|
||||||
- Improved compatibility with NetBSD on various platforms.
|
- Improved compatibility with NetBSD on various platforms.
|
||||||
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository.
|
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository. It
|
||||||
- Use a `configure` script and `make` to build Telodendria instead of custom scripts.
|
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
|
### New Features
|
||||||
|
|
||||||
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins to be able to
|
- Implemented a `"pid"` option in the configuration, allowing Telodendria
|
||||||
deactivate users.
|
to write its process ID to a specified file.
|
||||||
- Moved all administrator API endpoints to `/_telodendria/admin/v1`, because later revisions
|
- Moved all administrator API endpoints to `/_telodendria/admin/v1`,
|
||||||
of the administrator API may break clients, so we want a way to give those breaking revisions
|
because later revisions of the administrator API may break clients, so
|
||||||
new endpoints.
|
we want a way to give those breaking revisions new endpoints.
|
||||||
|
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins
|
||||||
|
to be able to deactivate users.
|
||||||
|
- Added a **PUT** option to `/_telodendria/admin/v1/config` that gives
|
||||||
|
the ability to change only a subset of the configuration.
|
||||||
|
- Implemented the following APIs for managing registration tokens:
|
||||||
|
- **GET** `/_telodendria/admin/tokens`
|
||||||
|
- **GET** `/_telodendria/admin/tokens/[token]`
|
||||||
|
- **POST** `/_telodendria/admin/tokens`
|
||||||
|
- **DELETE** `/_telodendria/admin/tokens/[token]`
|
||||||
|
- **GET** `/_matrix/client/v3/directory/room/[alias]`
|
||||||
|
- **PUT** `/_matrix/client/v3/directory/room/[alias]`
|
||||||
|
- **DELETE** `/_matrix/client/v3/directory/room/[alias]`
|
||||||
|
- **GET** `/_matrix/client/v3/rooms/[id]/aliases`
|
||||||
|
|
||||||
## v0.3.0
|
## v0.3.0
|
||||||
|
|
||||||
|
|
|
@ -213,3 +213,44 @@ comments to the appropriate header.
|
||||||
If your pull request does not also include proper documentation, it
|
If your pull request does not also include proper documentation, it
|
||||||
will likely be rejected.
|
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
94
docs/dev/hosting.md
Normal 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.
|
|
@ -19,6 +19,7 @@ request.
|
||||||
- [Configuration](config.md)
|
- [Configuration](config.md)
|
||||||
- [Server Statistics](stats.md)
|
- [Server Statistics](stats.md)
|
||||||
- [Process Control](proc.md)
|
- [Process Control](proc.md)
|
||||||
|
- [Registration Tokens](tokens.md)
|
||||||
|
|
||||||
## API Conventions
|
## API Conventions
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ As mentioned in [Setup](../setup.md), Telodendria's configuration is
|
||||||
intended to be managed via the configuration API. Consult the
|
intended to be managed via the configuration API. Consult the
|
||||||
[Configuration](../config.md) document for a complete list of supported
|
[Configuration](../config.md) document for a complete list of supported
|
||||||
configuration options. This document simply describes the API used to
|
configuration options. This document simply describes the API used to
|
||||||
update the configuration.
|
update the configuration described in that document.
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### **GET** `/_telodendria/admin/config`
|
### **GET** `/_telodendria/admin/v1/config`
|
||||||
|
|
||||||
Retrieve the current configuration.
|
Retrieve the current configuration.
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ Retrieve the current configuration.
|
||||||
|---------------|-------------|
|
|---------------|-------------|
|
||||||
| 200 | The current configuration was successfully retrieved.|
|
| 200 | The current configuration was successfully retrieved.|
|
||||||
|
|
||||||
### **POST** `/_telodendria/admin/config`
|
### **POST** `/_telodendria/admin/v1/config`
|
||||||
|
|
||||||
Installs a new configuration. This endpoint validates the request body,
|
Installs a new configuration. This endpoint validates the request body,
|
||||||
ensuring it is a proper configuration, then it replaces the existing
|
ensuring it is a proper configuration, then it replaces the existing
|
||||||
|
@ -40,3 +40,23 @@ configuration with the new one.
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
||||||
|
|
||||||
|
### **PUT** `/_telodendria/admin/v1/config`
|
||||||
|
|
||||||
|
Update the currently installed configuration instead of completely replacing it. This endpoint
|
||||||
|
validates the request body, merges it on top of the current configuration, validates the resulting
|
||||||
|
configuration, then updates it in the database. This is useful when only one or two properties
|
||||||
|
in the configuration needs to be changed.
|
||||||
|
|
||||||
|
| Requires Token | Rate Limited |
|
||||||
|
|----------------|--------------|
|
||||||
|
| Yes | Yes |
|
||||||
|
|
||||||
|
| Response Code | Description |
|
||||||
|
|---------------|-------------|
|
||||||
|
| 200 | The new configuration was successfully installed.|
|
||||||
|
|
||||||
|
#### 200 Response Format
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
||||||
|
|
|
@ -16,10 +16,10 @@ registration tokens.
|
||||||
configuration.
|
configuration.
|
||||||
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own
|
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own
|
||||||
privileges or the privileges of other local users.
|
privileges or the privileges of other local users.
|
||||||
- **ALIAS:** Allows a user to modify room aliases created by other
|
- **ALIAS:** Allows a user to modify and see room aliases created by
|
||||||
users. By default, users can only manage their own room aliases, but
|
other users. By default, users can only manage their own room aliases,
|
||||||
an administrator may wish to take over an alias or remove an offensive
|
but an administrator may wish to take over an alias or remove an
|
||||||
alias.
|
offensive alias.
|
||||||
- **PROC_CONTROL:** Allows a user to get statistics on the running
|
- **PROC_CONTROL:** Allows a user to get statistics on the running
|
||||||
process, as well as shutdown and resetart the Telodendria daemon
|
process, as well as shutdown and resetart the Telodendria daemon
|
||||||
itself. Typically this will pair well with **CONFIG**, because there
|
itself. Typically this will pair well with **CONFIG**, because there
|
||||||
|
@ -41,7 +41,7 @@ this privilege level.
|
||||||
|
|
||||||
The following API endpoints are implemented for managing privileges.
|
The following API endpoints are implemented for managing privileges.
|
||||||
|
|
||||||
### **GET** `/_telodendria/admin/privileges/[localpart]`
|
### **GET** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||||
|
|
||||||
Retrieve the permissions for a user. If the localpart is omitted, then
|
Retrieve the permissions for a user. If the localpart is omitted, then
|
||||||
retrieve the privileges for the user that owns the access token being
|
retrieve the privileges for the user that owns the access token being
|
||||||
|
@ -62,7 +62,7 @@ used. Note that the owner of the access token must have the
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||||
|
|
||||||
### **POST** `/_telodendria/admin/privileges/[localpart]`
|
### **POST** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||||
|
|
||||||
Update the privileges of a local user by replacing the privileges array
|
Update the privileges of a local user by replacing the privileges array
|
||||||
with the one specified in the request. Like the **GET** version of this
|
with the one specified in the request. Like the **GET** version of this
|
||||||
|
@ -89,7 +89,7 @@ owns the access token.
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||||
|
|
||||||
### **PUT** `/_telodendria/admin/privileges/[localpart]`
|
### **PUT** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||||
|
|
||||||
Update the privileges of a local user by adding the privileges
|
Update the privileges of a local user by adding the privileges
|
||||||
specified in the request to the users existing privileges.
|
specified in the request to the users existing privileges.
|
||||||
|
@ -114,7 +114,7 @@ specified in the request to the users existing privileges.
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||||
|
|
||||||
### **DELETE** `/_telodendria/admin/privileges/[localpart]`
|
### **DELETE** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||||
|
|
||||||
Update the privileges of a local user by removing the privileges
|
Update the privileges of a local user by removing the privileges
|
||||||
specified in the request from the user's existing privileges.
|
specified in the request from the user's existing privileges.
|
||||||
|
|
|
@ -5,7 +5,7 @@ administrator to manage the Telodendria process itself.
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### **POST** `/_telodendria/admin/restart`
|
### **POST** `/_telodendria/admin/v1/restart`
|
||||||
|
|
||||||
Restart the Telodendria daemon cleanly. This endpoint will respond
|
Restart the Telodendria daemon cleanly. This endpoint will respond
|
||||||
immediately after signaling to the daemon that it should be restarted
|
immediately after signaling to the daemon that it should be restarted
|
||||||
|
@ -26,7 +26,7 @@ starts over.
|
||||||
|
|
||||||
On success, this endpoint simply returns an empty JSON object.
|
On success, this endpoint simply returns an empty JSON object.
|
||||||
|
|
||||||
### **POST** `/_telodendria/admin/shutdown`
|
### **POST** `/_telodendria/admin/v1/shutdown`
|
||||||
|
|
||||||
Shut down the Telodendria process cleanly. This endpoint will respond
|
Shut down the Telodendria process cleanly. This endpoint will respond
|
||||||
immediately after signalling to the daemon that it should be shut
|
immediately after signalling to the daemon that it should be shut
|
||||||
|
|
|
@ -5,7 +5,7 @@ information about how the server process is performing.
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### **GET** `/_telodendria/admin/stats`
|
### **GET** `/_telodendria/admin/v1/stats`
|
||||||
|
|
||||||
Retrieve basic statistics about the currently running Telodendria
|
Retrieve basic statistics about the currently running Telodendria
|
||||||
process.
|
process.
|
||||||
|
|
106
docs/user/admin/tokens.md
Normal file
106
docs/user/admin/tokens.md
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# Administrator API: Registration Tokens
|
||||||
|
|
||||||
|
Telodendria implements registration tokens as specified by the Matrix
|
||||||
|
specification. These tokens can be used for registration using the
|
||||||
|
`m.login.registration_token` login type. This API provides a Telodendria
|
||||||
|
administrator with a mechanism for generating and managing these tokens,
|
||||||
|
which allows controlled registration on the homeserver.
|
||||||
|
|
||||||
|
It is generally safer than completely open registration to use
|
||||||
|
registration tokens that either expire after a short period of time, or
|
||||||
|
have a limited number of uses.
|
||||||
|
|
||||||
|
## Registration Token
|
||||||
|
|
||||||
|
A registration token is represented by the following `RegToken` JSON
|
||||||
|
object:
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `name` | `String` | The token identifier; what is used when registering. |
|
||||||
|
| `created_by` | `String` | The localpart of the user that created this token. |
|
||||||
|
| `created_on` | `Integer` | A timestamp of when the token was created. |
|
||||||
|
| `expires_on` | `Integer` | An expiration stamp, or 0 if the token never expires. |
|
||||||
|
| `used` | `Integer` | The number of times the token has been used. |
|
||||||
|
| `uses` | `Integer` | The total number of allowed uses, or -1 for unlimited. |
|
||||||
|
| `grants` | `[String]` | An array of privileges to grant users that register with this token as described in [Privileges](privileges.md). |
|
||||||
|
|
||||||
|
All endpoints in this API will operate on some variation of this
|
||||||
|
structure. The remaining number of uses can be computed by performing
|
||||||
|
the subtraction: `uses - used`. `used` should never be greater than
|
||||||
|
`uses` or less than `0`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "q34jgapo8uq34hg",
|
||||||
|
"created_by": "admin",
|
||||||
|
"created_on": 1699467640000,
|
||||||
|
"expires_on": 0,
|
||||||
|
"used": 3,
|
||||||
|
"uses": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### **GET** `/_telodendria/admin/v1/tokens`
|
||||||
|
|
||||||
|
Get a list of all registration tokens and information about them.
|
||||||
|
|
||||||
|
#### 200 Response Format
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `tokens` | `[RegToken]` | An array of registration tokens. |
|
||||||
|
|
||||||
|
### **GET** `/_telodendria/admin/v1/tokens/[name]`
|
||||||
|
|
||||||
|
Get information about the specified registration token.
|
||||||
|
|
||||||
|
#### Request Parameters
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `name` | `String` | The name of the token, as it would be used to register a user. |
|
||||||
|
|
||||||
|
#### 200 Response Format
|
||||||
|
|
||||||
|
This endpoint returns a `RegToken` object that represents the server's
|
||||||
|
record of the registration token.
|
||||||
|
|
||||||
|
### **POST** `/_telodendria/admin/v1/tokens`
|
||||||
|
|
||||||
|
Create a new registration token.
|
||||||
|
|
||||||
|
#### Request Format
|
||||||
|
|
||||||
|
This endpoint accepts a `RegToken` object, as described above. If no
|
||||||
|
`name` is provided, one will be randomly generated. Note that the fields
|
||||||
|
`created_by`, `created_on`, and `used` are ignored and set by the server
|
||||||
|
when this request is made. All other fields may be set by the request
|
||||||
|
body.
|
||||||
|
|
||||||
|
#### 200 Response Format
|
||||||
|
|
||||||
|
If the creation of the registration token was successful, a `RegToken`
|
||||||
|
that represents the server's record of it is returned.
|
||||||
|
|
||||||
|
### **DELETE** `/_telodendria/admin/v1/tokens/[name]`
|
||||||
|
|
||||||
|
Delete the specified registration token. It will no longer be usable for
|
||||||
|
the registration of users. Any users that have completed the
|
||||||
|
`m.login.registration_token` step but have not yet created their account
|
||||||
|
should still be able to do so until their user-interactive auth session
|
||||||
|
expires.
|
||||||
|
|
||||||
|
#### Request Parameters
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `name` | `String` | The name of the token, as it would be used to register a user. |
|
||||||
|
|
||||||
|
#### 200 Response Format
|
||||||
|
|
||||||
|
On success, this endpoint returns an empty JSON object.
|
|
@ -19,8 +19,7 @@ key-value form:
|
||||||
"serverName": "telodendria.io",
|
"serverName": "telodendria.io",
|
||||||
"listen": [
|
"listen": [
|
||||||
{
|
{
|
||||||
"port": 8008,
|
"port": 8008
|
||||||
"tls": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ Here are the top-level directives:
|
||||||
this is a concern, a reverse-proxy such as `relayd` can be placed
|
this is a concern, a reverse-proxy such as `relayd` can be placed
|
||||||
in front of Telodendria to block access to undesired APIs.
|
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
|
Telodendria can be compiled with TLS support. If it is, then a
|
||||||
particular listener can be set to use TLS for connections. If
|
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
|
or you want to start over. **serverName** should be a DNS name that
|
||||||
can be publicly resolved. This directive is required.
|
can be publicly resolved. This directive is required.
|
||||||
|
|
||||||
|
- **pid:** `String`
|
||||||
|
Configure the file Telodendria writes its PID to.
|
||||||
|
|
||||||
- **baseUrl:** `String`
|
- **baseUrl:** `String`
|
||||||
|
|
||||||
Set the server's base URL. **baseUrl** should be a valid URL,
|
Set the server's base URL. **baseUrl** should be a valid URL,
|
||||||
|
|
138
site/index.html
138
site/index.html
|
@ -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-ə-'den-drē-ə:</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>
|
|
||||||
© 2023 Jordan Bancino <@jordan:bancino.net>
|
|
||||||
<br>
|
|
||||||
Updated on ${DATE}.
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
368
site/mandoc.css
368
site/mandoc.css
|
@ -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; }
|
|
||||||
}
|
|
114
site/style.css
114
site/style.css
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
untrusted comment: signify public key
|
|
||||||
RWTPPnWvnpee8NlygSggQqk5V5oghl6Ikq99bZl5IRQwiRMLaJnq82mw
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
490
src/Config.c
490
src/Config.c
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* 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
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include <Config.h>
|
#include <Schema/Config.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
#include <Cytoplasm/Memory.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Array.h>
|
#include <Cytoplasm/Array.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
#include <Cytoplasm/Db.h>
|
#include <Cytoplasm/Db.h>
|
||||||
#include <Cytoplasm/HttpServer.h>
|
|
||||||
#include <Cytoplasm/Log.h>
|
#include <Cytoplasm/Log.h>
|
||||||
#include <Cytoplasm/Int64.h>
|
#include <Cytoplasm/Util.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <grp.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
#ifndef HOST_NAME_MAX
|
#ifndef HOST_NAME_MAX
|
||||||
#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
|
#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
|
||||||
#endif
|
#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)
|
|
||||||
{
|
|
||||||
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
|
void
|
||||||
ConfigFree(Config * tConfig)
|
ConfigParse(HashMap * config, 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;
|
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)
|
if (!config)
|
||||||
{
|
{
|
||||||
return NULL;
|
tConfig->ok = 0;
|
||||||
}
|
tConfig->err = "Invalid object given as config.";
|
||||||
|
return;
|
||||||
tConfig = Malloc(sizeof(Config));
|
|
||||||
if (!tConfig)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(tConfig, 0, sizeof(Config));
|
memset(tConfig, 0, sizeof(Config));
|
||||||
|
|
||||||
CONFIG_REQUIRE("listen", JSON_ARRAY);
|
tConfig->maxCache = 0;
|
||||||
if (!ConfigParseListen(tConfig, JsonValueAsArray(value)))
|
|
||||||
|
if (!ConfigFromJson(config, tConfig, &tConfig->err))
|
||||||
{
|
{
|
||||||
|
ConfigFree(tConfig);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
if (!tConfig->baseUrl)
|
||||||
CONFIG_REQUIRE("serverName", JSON_STRING);
|
|
||||||
CONFIG_COPY_STRING(tConfig->serverName);
|
|
||||||
|
|
||||||
value = HashMapGet(config, "baseUrl");
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
CONFIG_COPY_STRING(tConfig->baseUrl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
size_t len = strlen(tConfig->serverName) + 10;
|
size_t len = strlen(tConfig->serverName) + 10;
|
||||||
|
|
||||||
tConfig->baseUrl = Malloc(len);
|
tConfig->baseUrl = Malloc(len);
|
||||||
if (!tConfig->baseUrl)
|
if (!tConfig->baseUrl)
|
||||||
{
|
{
|
||||||
tConfig->err = "Error allocating memory for default config value 'baseUrl'.";
|
tConfig->err = "Couldn't allocate enough memory for 'baseUrl'.";
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
snprintf(tConfig->baseUrl, len, "https://%s/", tConfig->serverName);
|
||||||
snprintf(tConfig->baseUrl, len, "https://%s", tConfig->serverName);
|
|
||||||
}
|
}
|
||||||
|
if (!tConfig->log.timestampFormat)
|
||||||
CONFIG_OPTIONAL_STRING(tConfig->identityServer, "identityServer", NULL);
|
|
||||||
|
|
||||||
value = HashMapGet(config, "runAs");
|
|
||||||
if (value && JsonValueType(value) != JSON_NULL)
|
|
||||||
{
|
{
|
||||||
if (JsonValueType(value) == JSON_OBJECT)
|
tConfig->log.timestampFormat = StrDuplicate("default");
|
||||||
|
}
|
||||||
|
for (i = 0; i < ArraySize(tConfig->listen); i++)
|
||||||
{
|
{
|
||||||
if (!ConfigParseRunAs(tConfig, JsonValueAsObject(value)))
|
ConfigListener *listener = ArrayGet(tConfig->listen, i);
|
||||||
|
if (!listener->maxConnections)
|
||||||
{
|
{
|
||||||
goto error;
|
listener->maxConnections = 32;
|
||||||
}
|
}
|
||||||
}
|
if (!listener->threads)
|
||||||
else
|
|
||||||
{
|
{
|
||||||
tConfig->err = "Config directive 'runAs' should be a JSON object that contains a 'uid' and 'gid'.";
|
listener->threads = 4;
|
||||||
goto error;
|
|
||||||
}
|
}
|
||||||
}
|
if (!listener->port)
|
||||||
|
|
||||||
CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", 0);
|
|
||||||
|
|
||||||
CONFIG_REQUIRE("federation", JSON_BOOLEAN);
|
|
||||||
if (JsonValueAsBoolean(value))
|
|
||||||
{
|
{
|
||||||
tConfig->flags |= CONFIG_FEDERATION;
|
listener->port = 8008;
|
||||||
}
|
}
|
||||||
|
|
||||||
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->ok = 1;
|
||||||
tConfig->err = NULL;
|
tConfig->err = NULL;
|
||||||
return tConfig;
|
return;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
tConfig->ok = 0;
|
tConfig->ok = 0;
|
||||||
return tConfig;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -431,75 +115,91 @@ ConfigExists(Db * db)
|
||||||
int
|
int
|
||||||
ConfigCreateDefault(Db * db)
|
ConfigCreateDefault(Db * db)
|
||||||
{
|
{
|
||||||
DbRef *ref;
|
Config config;
|
||||||
|
ConfigListener *listener;
|
||||||
|
|
||||||
HashMap *json;
|
HashMap *json;
|
||||||
Array *listeners;
|
JsonValue *val;
|
||||||
HashMap *listen;
|
|
||||||
|
|
||||||
char hostname[HOST_NAME_MAX + 1];
|
DbRef *ref;
|
||||||
|
|
||||||
if (!db)
|
size_t len;
|
||||||
{
|
|
||||||
return 0;
|
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");
|
ref = DbCreate(db, 1, "config");
|
||||||
if (!ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
|
ConfigFree(&config);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
DbJsonSet(ref, json);
|
||||||
|
DbUnlock(db, ref);
|
||||||
|
|
||||||
json = DbJson(ref);
|
ConfigFree(&config);
|
||||||
|
JsonFree(json);
|
||||||
|
|
||||||
JsonSet(json, JsonValueString("file"), 2, "log", "output");
|
return 1;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Config *
|
void
|
||||||
ConfigLock(Db * db)
|
ConfigLock(Db * db, Config *config)
|
||||||
{
|
{
|
||||||
Config *config;
|
|
||||||
DbRef *ref = DbLock(db, 1, "config");
|
DbRef *ref = DbLock(db, 1, "config");
|
||||||
|
|
||||||
if (!ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
return NULL;
|
config->ok = 0;
|
||||||
|
config->err = "Couldn't lock configuration.";
|
||||||
}
|
}
|
||||||
|
|
||||||
config = ConfigParse(DbJson(ref));
|
ConfigParse(DbJson(ref), config);
|
||||||
if (config)
|
if (config->ok)
|
||||||
{
|
{
|
||||||
config->db = db;
|
config->db = db;
|
||||||
config->ref = ref;
|
config->ref = ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ConfigUnlock(Config * config)
|
ConfigUnlock(Config *config)
|
||||||
{
|
{
|
||||||
Db *db;
|
Db *db;
|
||||||
DbRef *dbRef;
|
DbRef *dbRef;
|
||||||
|
|
||||||
if (!config)
|
if (!config->ok)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -508,5 +208,25 @@ ConfigUnlock(Config * config)
|
||||||
dbRef = config->ref;
|
dbRef = config->ref;
|
||||||
|
|
||||||
ConfigFree(config);
|
ConfigFree(config);
|
||||||
|
config->ok = 0;
|
||||||
|
|
||||||
return DbUnlock(db, dbRef);
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -28,5 +29,7 @@
|
||||||
HashMap *
|
HashMap *
|
||||||
FilterApply(Filter * filter, HashMap * event)
|
FilterApply(Filter * filter, HashMap * event)
|
||||||
{
|
{
|
||||||
|
(void) filter;
|
||||||
|
(void) event;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
157
src/Main.c
157
src/Main.c
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -35,14 +36,12 @@
|
||||||
|
|
||||||
#include <Cytoplasm/Args.h>
|
#include <Cytoplasm/Args.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
#include <Cytoplasm/Memory.h>
|
||||||
#include <Config.h>
|
|
||||||
#include <Cytoplasm/Log.h>
|
#include <Cytoplasm/Log.h>
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/HttpServer.h>
|
#include <Cytoplasm/HttpServer.h>
|
||||||
#include <Cytoplasm/Db.h>
|
#include <Cytoplasm/Db.h>
|
||||||
#include <Cytoplasm/Cron.h>
|
#include <Cytoplasm/Cron.h>
|
||||||
#include <Uia.h>
|
|
||||||
#include <Cytoplasm/Util.h>
|
#include <Cytoplasm/Util.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
|
@ -51,6 +50,9 @@
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
#include <RegToken.h>
|
#include <RegToken.h>
|
||||||
#include <Routes.h>
|
#include <Routes.h>
|
||||||
|
#include <Uia.h>
|
||||||
|
#include <Config.h>
|
||||||
|
|
||||||
|
|
||||||
static Array *httpServers;
|
static Array *httpServers;
|
||||||
static volatile int restart;
|
static volatile int restart;
|
||||||
|
@ -102,8 +104,11 @@ Main(Array * args)
|
||||||
char *dbPath;
|
char *dbPath;
|
||||||
|
|
||||||
/* Program configuration */
|
/* Program configuration */
|
||||||
Config *tConfig;
|
Config tConfig;
|
||||||
Stream *logFile;
|
Stream *logFile;
|
||||||
|
Stream *pidFile;
|
||||||
|
|
||||||
|
char *pidPath;
|
||||||
|
|
||||||
/* User validation */
|
/* User validation */
|
||||||
struct passwd *userInfo;
|
struct passwd *userInfo;
|
||||||
|
@ -128,16 +133,13 @@ start:
|
||||||
httpServers = NULL;
|
httpServers = NULL;
|
||||||
restart = 0;
|
restart = 0;
|
||||||
|
|
||||||
/* For getopt() */
|
|
||||||
opterr = 1;
|
|
||||||
optind = 1;
|
|
||||||
|
|
||||||
/* Local variables */
|
/* Local variables */
|
||||||
exit = EXIT_SUCCESS;
|
exit = EXIT_SUCCESS;
|
||||||
flags = 0;
|
flags = 0;
|
||||||
dbPath = NULL;
|
dbPath = NULL;
|
||||||
tConfig = NULL;
|
|
||||||
logFile = NULL;
|
logFile = NULL;
|
||||||
|
pidFile = NULL;
|
||||||
|
pidPath = NULL;
|
||||||
userInfo = NULL;
|
userInfo = NULL;
|
||||||
groupInfo = NULL;
|
groupInfo = NULL;
|
||||||
cron = NULL;
|
cron = NULL;
|
||||||
|
@ -247,7 +249,7 @@ start:
|
||||||
}
|
}
|
||||||
|
|
||||||
token = StrRandom(32);
|
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)
|
if (!info)
|
||||||
{
|
{
|
||||||
Free(token);
|
Free(token);
|
||||||
|
@ -265,33 +267,20 @@ start:
|
||||||
|
|
||||||
Log(LOG_NOTICE, "Loading configuration...");
|
Log(LOG_NOTICE, "Loading configuration...");
|
||||||
|
|
||||||
tConfig = ConfigLock(matrixArgs.db);
|
ConfigLock(matrixArgs.db, &tConfig);
|
||||||
if (!tConfig)
|
if (!tConfig.ok)
|
||||||
{
|
{
|
||||||
Log(LOG_ERR, "Error locking the configuration.");
|
Log(LOG_ERR, tConfig.err);
|
||||||
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);
|
|
||||||
exit = EXIT_FAILURE;
|
exit = EXIT_FAILURE;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tConfig->logTimestamp || !StrEquals(tConfig->logTimestamp, "default"))
|
if (!tConfig.log.timestampFormat || !StrEquals(tConfig.log.timestampFormat, "default"))
|
||||||
{
|
{
|
||||||
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig->logTimestamp);
|
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig.log.timestampFormat);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Free(tConfig->logTimestamp);
|
|
||||||
tConfig->logTimestamp = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tConfig->flags & CONFIG_LOG_COLOR)
|
if (tConfig.log.color)
|
||||||
{
|
{
|
||||||
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR);
|
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR);
|
||||||
}
|
}
|
||||||
|
@ -300,9 +289,13 @@ start:
|
||||||
LogConfigFlagClear(LogConfigGlobal(), LOG_FLAG_COLOR);
|
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");
|
logFile = StreamOpen("telodendria.log", "a");
|
||||||
|
|
||||||
|
@ -310,18 +303,18 @@ start:
|
||||||
{
|
{
|
||||||
Log(LOG_ERR, "Unable to open log file for appending.");
|
Log(LOG_ERR, "Unable to open log file for appending.");
|
||||||
exit = EXIT_FAILURE;
|
exit = EXIT_FAILURE;
|
||||||
tConfig->flags &= CONFIG_LOG_STDOUT;
|
tConfig.log.output = CONFIG_LOG_OUTPUT_STDOUT;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log(LOG_INFO, "Logging to the log file. Check there for all future messages.");
|
Log(LOG_INFO, "Logging to the log file. Check there for all future messages.");
|
||||||
LogConfigOutputSet(LogConfigGlobal(), logFile);
|
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.");
|
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.");
|
Log(LOG_INFO, "Logging to the syslog. Check there for all future messages.");
|
||||||
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_SYSLOG);
|
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_SYSLOG);
|
||||||
|
@ -331,13 +324,6 @@ start:
|
||||||
* messages get passed to the syslog */
|
* messages get passed to the syslog */
|
||||||
setlogmask(LOG_UPTO(LOG_DEBUG));
|
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
|
/* If a token was created with a default config, print it to the
|
||||||
* log */
|
* log */
|
||||||
|
@ -347,14 +333,30 @@ start:
|
||||||
Free(token);
|
Free(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tConfig.pid)
|
||||||
|
{
|
||||||
|
pidFile = StreamOpen(tConfig.pid, "w+");
|
||||||
|
if (!pidFile)
|
||||||
|
{
|
||||||
|
char *msg = "Couldn't lock PID file at '%s'";
|
||||||
|
Log(LOG_ERR, msg, tConfig.pid);
|
||||||
|
exit = EXIT_FAILURE;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
pidPath = StrDuplicate(tConfig.pid);
|
||||||
|
StreamPrintf(pidFile, "%ld", (long) getpid());
|
||||||
|
StreamClose(pidFile);
|
||||||
|
}
|
||||||
|
|
||||||
Log(LOG_DEBUG, "Configuration:");
|
Log(LOG_DEBUG, "Configuration:");
|
||||||
LogConfigIndent(LogConfigGlobal());
|
LogConfigIndent(LogConfigGlobal());
|
||||||
Log(LOG_DEBUG, "Server Name: %s", tConfig->serverName);
|
Log(LOG_DEBUG, "Server Name: %s", tConfig.serverName);
|
||||||
Log(LOG_DEBUG, "Base URL: %s", tConfig->baseUrl);
|
Log(LOG_DEBUG, "Base URL: %s", tConfig.baseUrl);
|
||||||
Log(LOG_DEBUG, "Identity Server: %s", tConfig->identityServer);
|
Log(LOG_DEBUG, "Identity Server: %s", tConfig.identityServer);
|
||||||
Log(LOG_DEBUG, "Run As: %s:%s", tConfig->uid, tConfig->gid);
|
Log(LOG_DEBUG, "Run As: %s:%s", tConfig.runAs.uid, tConfig.runAs.gid);
|
||||||
Log(LOG_DEBUG, "Max Cache: %ld", tConfig->maxCache);
|
Log(LOG_DEBUG, "Max Cache: %ld", tConfig.maxCache);
|
||||||
Log(LOG_DEBUG, "Flags: %x", tConfig->flags);
|
Log(LOG_DEBUG, "Registration: %s", tConfig.registration ? "true" : "false");
|
||||||
|
Log(LOG_DEBUG, "Federation: %s", tConfig.federation ? "true" : "false");
|
||||||
LogConfigUnindent(LogConfigGlobal());
|
LogConfigUnindent(LogConfigGlobal());
|
||||||
|
|
||||||
httpServers = ArrayCreate();
|
httpServers = ArrayCreate();
|
||||||
|
@ -366,41 +368,51 @@ start:
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bind servers before possibly dropping permissions. */
|
/* 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);
|
Log(LOG_DEBUG, "HTTP listener: %lu", i);
|
||||||
LogConfigIndent(LogConfigGlobal());
|
LogConfigIndent(LogConfigGlobal());
|
||||||
Log(LOG_DEBUG, "Port: %hu", serverCfg->port);
|
Log(LOG_DEBUG, "Port: %hu", serverCfg->port);
|
||||||
Log(LOG_DEBUG, "Threads: %u", serverCfg->threads);
|
Log(LOG_DEBUG, "Threads: %u", serverCfg->threads);
|
||||||
Log(LOG_DEBUG, "Max Connections: %u", serverCfg->maxConnections);
|
Log(LOG_DEBUG, "Max Connections: %u", serverCfg->maxConnections);
|
||||||
Log(LOG_DEBUG, "Flags: %d", serverCfg->flags);
|
Log(LOG_DEBUG, "Flags: %d", args.flags);
|
||||||
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tlsCert);
|
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tls.cert);
|
||||||
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tlsKey);
|
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tls.key);
|
||||||
LogConfigUnindent(LogConfigGlobal());
|
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;
|
exit = EXIT_FAILURE;
|
||||||
goto finish;
|
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;
|
exit = EXIT_FAILURE;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server = HttpServerCreate(serverCfg);
|
server = HttpServerCreate(&args);
|
||||||
if (!server)
|
if (!server)
|
||||||
{
|
{
|
||||||
Log(LOG_ERR, "Unable to create HTTP server on port %d: %s",
|
Log(LOG_ERR, "Unable to create HTTP server on port %d: %s",
|
||||||
|
@ -421,10 +433,10 @@ start:
|
||||||
|
|
||||||
Log(LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid());
|
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);
|
userInfo = getpwnam(tConfig.runAs.uid);
|
||||||
groupInfo = getgrnam(tConfig->gid);
|
groupInfo = getgrnam(tConfig.runAs.gid);
|
||||||
|
|
||||||
if (!userInfo || !groupInfo)
|
if (!userInfo || !groupInfo)
|
||||||
{
|
{
|
||||||
|
@ -454,7 +466,7 @@ start:
|
||||||
}
|
}
|
||||||
else
|
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
|
else
|
||||||
|
@ -466,7 +478,7 @@ start:
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (tConfig->uid && tConfig->gid)
|
if (tConfig.runAs.uid && tConfig.runAs.gid)
|
||||||
{
|
{
|
||||||
if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid)
|
if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid)
|
||||||
{
|
{
|
||||||
|
@ -479,17 +491,16 @@ start:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tConfig->maxCache)
|
if (!tConfig.maxCache)
|
||||||
{
|
{
|
||||||
Log(LOG_WARNING, "Database caching is disabled.");
|
Log(LOG_WARNING, "Database caching is disabled.");
|
||||||
Log(LOG_WARNING, "If this is not what you intended, check the config file");
|
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.");
|
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);
|
ConfigUnlock(&tConfig);
|
||||||
tConfig = NULL;
|
|
||||||
|
|
||||||
cron = CronCreate(60 * 1000); /* 1-minute tick */
|
cron = CronCreate(60 * 1000); /* 1-minute tick */
|
||||||
if (!cron)
|
if (!cron)
|
||||||
|
@ -596,7 +607,7 @@ finish:
|
||||||
Log(LOG_DEBUG, "Stopped and freed job scheduler.");
|
Log(LOG_DEBUG, "Stopped and freed job scheduler.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigUnlock(tConfig);
|
ConfigUnlock(&tConfig);
|
||||||
Log(LOG_DEBUG, "Unlocked configuration.");
|
Log(LOG_DEBUG, "Unlocked configuration.");
|
||||||
|
|
||||||
DbClose(matrixArgs.db);
|
DbClose(matrixArgs.db);
|
||||||
|
@ -605,6 +616,12 @@ finish:
|
||||||
HttpRouterFree(matrixArgs.router);
|
HttpRouterFree(matrixArgs.router);
|
||||||
Log(LOG_DEBUG, "Freed routing tree.");
|
Log(LOG_DEBUG, "Freed routing tree.");
|
||||||
|
|
||||||
|
if (pidPath)
|
||||||
|
{
|
||||||
|
remove(pidPath);
|
||||||
|
Free(pidPath);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Uninstall the memory hook because it uses the Log
|
* Uninstall the memory hook because it uses the Log
|
||||||
* API, whose configuration is being freed now, so it
|
* API, whose configuration is being freed now, so it
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
516
src/Parser.c
Normal file
516
src/Parser.c
Normal 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;
|
||||||
|
}
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -30,16 +31,17 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Util.h>
|
#include <Cytoplasm/Util.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
#include <Cytoplasm/Log.h>
|
||||||
|
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
#include <Cytoplasm/Int64.h>
|
|
||||||
|
|
||||||
int
|
int
|
||||||
RegTokenValid(RegTokenInfo * token)
|
RegTokenValid(RegTokenInfo * token)
|
||||||
{
|
{
|
||||||
HashMap *tokenJson;
|
HashMap *tokenJson;
|
||||||
Int64 uses, used;
|
int64_t uses, used;
|
||||||
|
|
||||||
UInt64 expiration;
|
uint64_t expiration;
|
||||||
|
|
||||||
if (!token || !RegTokenExists(token->db, token->name))
|
if (!token || !RegTokenExists(token->db, token->name))
|
||||||
{
|
{
|
||||||
|
@ -51,9 +53,7 @@ RegTokenValid(RegTokenInfo * token)
|
||||||
used = JsonValueAsInteger(HashMapGet(tokenJson, "used"));
|
used = JsonValueAsInteger(HashMapGet(tokenJson, "used"));
|
||||||
expiration = JsonValueAsInteger(HashMapGet(tokenJson, "expires_on"));
|
expiration = JsonValueAsInteger(HashMapGet(tokenJson, "expires_on"));
|
||||||
|
|
||||||
return (UInt64Eq(expiration, UInt64Create(0, 0)) ||
|
return (!expiration || (UtilTsMillis() < expiration)) && (uses == -1 || used < uses);
|
||||||
UInt64Geq(UtilServerTs(), expiration)) &&
|
|
||||||
(Int64Eq(uses, Int64Neg(Int64Create(0, 1))) || Int64Lt(used, uses));
|
|
||||||
}
|
}
|
||||||
void
|
void
|
||||||
RegTokenUse(RegTokenInfo * token)
|
RegTokenUse(RegTokenInfo * token)
|
||||||
|
@ -65,13 +65,12 @@ RegTokenUse(RegTokenInfo * token)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Int64Geq(token->uses, Int64Create(0, 0)) &&
|
if (token->uses >= 0 && token->used >= token->uses)
|
||||||
Int64Geq(token->used, token->uses))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
token->used = Int64Add(token->used, Int64Create(0, 1));
|
token->used++;
|
||||||
|
|
||||||
/* Write the information to the hashmap */
|
/* Write the information to the hashmap */
|
||||||
tokenJson = DbJson(token->ref);
|
tokenJson = DbJson(token->ref);
|
||||||
|
@ -99,8 +98,7 @@ RegTokenDelete(RegTokenInfo * token)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Free(token->name);
|
RegTokenInfoFree(token);
|
||||||
Free(token->owner);
|
|
||||||
Free(token);
|
Free(token);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +111,8 @@ RegTokenGetInfo(Db * db, char *token)
|
||||||
DbRef *tokenRef;
|
DbRef *tokenRef;
|
||||||
HashMap *tokenJson;
|
HashMap *tokenJson;
|
||||||
|
|
||||||
|
char *errp = NULL;
|
||||||
|
|
||||||
if (!RegTokenExists(db, token))
|
if (!RegTokenExists(db, token))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -126,47 +126,43 @@ RegTokenGetInfo(Db * db, char *token)
|
||||||
tokenJson = DbJson(tokenRef);
|
tokenJson = DbJson(tokenRef);
|
||||||
ret = Malloc(sizeof(RegTokenInfo));
|
ret = Malloc(sizeof(RegTokenInfo));
|
||||||
|
|
||||||
|
if (!RegTokenInfoFromJson(tokenJson, ret, &errp))
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "RegTokenGetInfo(): Database decoding error: %s", errp);
|
||||||
|
RegTokenFree(ret);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
ret->db = db;
|
ret->db = db;
|
||||||
ret->ref = tokenRef;
|
ret->ref = tokenRef;
|
||||||
|
|
||||||
ret->owner =
|
|
||||||
StrDuplicate(JsonValueAsString(HashMapGet(tokenJson, "created_by")));
|
|
||||||
ret->name = StrDuplicate(token);
|
|
||||||
|
|
||||||
ret->expires =
|
|
||||||
JsonValueAsInteger(HashMapGet(tokenJson, "expires_on"));
|
|
||||||
ret->created =
|
|
||||||
JsonValueAsInteger(HashMapGet(tokenJson, "created_on"));
|
|
||||||
|
|
||||||
ret->uses =
|
|
||||||
JsonValueAsInteger(HashMapGet(tokenJson, "uses"));
|
|
||||||
ret->used =
|
|
||||||
JsonValueAsInteger(HashMapGet(tokenJson, "used"));
|
|
||||||
|
|
||||||
ret->grants =
|
|
||||||
UserDecodePrivileges(HashMapGet(tokenJson, "grants"));
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
RegTokenFree(RegTokenInfo * tokeninfo)
|
RegTokenFree(RegTokenInfo *tokeninfo)
|
||||||
{
|
{
|
||||||
if (tokeninfo)
|
if (tokeninfo)
|
||||||
{
|
{
|
||||||
Free(tokeninfo->name);
|
RegTokenInfoFree(tokeninfo);
|
||||||
Free(tokeninfo->owner);
|
|
||||||
Free(tokeninfo);
|
Free(tokeninfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int
|
int
|
||||||
RegTokenClose(RegTokenInfo * tokeninfo)
|
RegTokenClose(RegTokenInfo * tokeninfo)
|
||||||
{
|
{
|
||||||
|
HashMap *json;
|
||||||
|
|
||||||
if (!tokeninfo)
|
if (!tokeninfo)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Write object to database. */
|
||||||
|
json = RegTokenInfoToJson(tokeninfo);
|
||||||
|
DbJsonSet(tokeninfo->ref, json); /* Copies json into internal structure. */
|
||||||
|
JsonFree(json);
|
||||||
|
|
||||||
return DbUnlock(tokeninfo->db, tokeninfo->ref);
|
return DbUnlock(tokeninfo->db, tokeninfo->ref);
|
||||||
}
|
}
|
||||||
static int
|
static int
|
||||||
|
@ -199,12 +195,11 @@ RegTokenVerify(char *token)
|
||||||
}
|
}
|
||||||
|
|
||||||
RegTokenInfo *
|
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;
|
RegTokenInfo *ret;
|
||||||
HashMap *tokenJson;
|
|
||||||
|
|
||||||
UInt64 timestamp = UtilServerTs();
|
uint64_t timestamp = UtilTsMillis();
|
||||||
|
|
||||||
if (!db || !name)
|
if (!db || !name)
|
||||||
{
|
{
|
||||||
|
@ -214,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
|
/* -1 indicates infinite uses; zero and all positive values are a
|
||||||
* valid number of uses; althought zero would be rather useless.
|
* valid number of uses; althought zero would be rather useless.
|
||||||
* Anything less than -1 doesn't make sense. */
|
* Anything less than -1 doesn't make sense. */
|
||||||
if (Int64Lt(uses, Int64Neg(Int64Create(0, 1))))
|
if (uses < -1)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Verify the token */
|
/* Verify the token */
|
||||||
if (!RegTokenVerify(name) || (UInt64Gt(expires, UInt64Create(0, 0)) && UInt64Lt(expires, timestamp)))
|
if (!RegTokenVerify(name) || ((expires > 0) && (expires < timestamp)))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -235,26 +230,12 @@ RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
ret->name = StrDuplicate(name);
|
ret->name = StrDuplicate(name);
|
||||||
ret->owner = StrDuplicate(owner);
|
ret->created_by = StrDuplicate(owner);
|
||||||
ret->used = Int64Create(0, 0);
|
ret->used = 0;
|
||||||
ret->uses = uses;
|
ret->uses = uses;
|
||||||
ret->created = timestamp;
|
ret->created_on = timestamp;
|
||||||
ret->expires = expires;
|
ret->expires_on = expires;
|
||||||
ret->grants = privileges;
|
ret->grants = UserEncodePrivileges(privileges);
|
||||||
|
|
||||||
/* Write user info to database. */
|
|
||||||
tokenJson = DbJson(ret->ref);
|
|
||||||
HashMapSet(tokenJson, "created_by",
|
|
||||||
JsonValueString(ret->owner));
|
|
||||||
HashMapSet(tokenJson, "created_on",
|
|
||||||
JsonValueInteger(ret->created));
|
|
||||||
HashMapSet(tokenJson, "expires_on",
|
|
||||||
JsonValueInteger(ret->expires));
|
|
||||||
HashMapSet(tokenJson, "used",
|
|
||||||
JsonValueInteger(ret->used));
|
|
||||||
HashMapSet(tokenJson, "uses",
|
|
||||||
JsonValueInteger(ret->uses));
|
|
||||||
HashMapSet(tokenJson, "grants", UserEncodePrivileges(privileges));
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -42,6 +43,8 @@ struct Room
|
||||||
Room *
|
Room *
|
||||||
RoomCreate(Db * db, RoomCreateRequest * req)
|
RoomCreate(Db * db, RoomCreateRequest * req)
|
||||||
{
|
{
|
||||||
|
(void) db;
|
||||||
|
(void) req;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -71,6 +72,7 @@ RouterBuild(void)
|
||||||
|
|
||||||
R("/_matrix/client/v3/profile/(.*)", RouteUserProfile);
|
R("/_matrix/client/v3/profile/(.*)", RouteUserProfile);
|
||||||
R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile);
|
R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile);
|
||||||
|
R("/_matrix/client/v3/user_directory/search", RouteUserDirectory);
|
||||||
|
|
||||||
R("/_matrix/client/v3/user/(.*)/filter", RouteFilter);
|
R("/_matrix/client/v3/user/(.*)/filter", RouteFilter);
|
||||||
R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter);
|
R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter);
|
||||||
|
@ -87,6 +89,8 @@ RouterBuild(void)
|
||||||
R("/_telodendria/admin/v1/privileges", RoutePrivileges);
|
R("/_telodendria/admin/v1/privileges", RoutePrivileges);
|
||||||
R("/_telodendria/admin/v1/privileges/(.*)", RoutePrivileges);
|
R("/_telodendria/admin/v1/privileges/(.*)", RoutePrivileges);
|
||||||
R("/_telodendria/admin/v1/deactivate/(.*)", RouteAdminDeactivate);
|
R("/_telodendria/admin/v1/deactivate/(.*)", RouteAdminDeactivate);
|
||||||
|
R("/_telodendria/admin/v1/tokens/(.*)", RouteAdminTokens);
|
||||||
|
R("/_telodendria/admin/v1/tokens", RouteAdminTokens);
|
||||||
|
|
||||||
#undef R
|
#undef R
|
||||||
|
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -28,7 +29,6 @@
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
#include <Cytoplasm/Log.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteAdminDeactivate, path, argp)
|
ROUTE_IMPL(RouteAdminDeactivate, path, argp)
|
||||||
{
|
{
|
||||||
|
|
208
src/Routes/RouteAdminTokens.c
Normal file
208
src/Routes/RouteAdminTokens.c
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||||
|
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include <Routes.h>
|
||||||
|
|
||||||
|
#include <Cytoplasm/Json.h>
|
||||||
|
#include <Cytoplasm/HashMap.h>
|
||||||
|
#include <Cytoplasm/Str.h>
|
||||||
|
#include <Cytoplasm/Memory.h>
|
||||||
|
|
||||||
|
#include <RegToken.h>
|
||||||
|
#include <User.h>
|
||||||
|
|
||||||
|
ROUTE_IMPL(RouteAdminTokens, path, argp)
|
||||||
|
{
|
||||||
|
RouteArgs *args = argp;
|
||||||
|
HashMap *request = NULL;
|
||||||
|
HashMap *response = NULL;
|
||||||
|
|
||||||
|
char *token;
|
||||||
|
char *msg;
|
||||||
|
|
||||||
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
|
User *user = NULL;
|
||||||
|
|
||||||
|
HttpRequestMethod method = HttpRequestMethodGet(args->context);
|
||||||
|
|
||||||
|
Array *tokensarray;
|
||||||
|
Array *tokens;
|
||||||
|
|
||||||
|
RegTokenInfo *info;
|
||||||
|
|
||||||
|
RegTokenInfo *req;
|
||||||
|
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (method != HTTP_GET && method != HTTP_POST && method != HTTP_DELETE)
|
||||||
|
{
|
||||||
|
msg = "Route only supports GET, POST, and DELETE";
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
response = MatrixGetAccessToken(args->context, &token);
|
||||||
|
if (response)
|
||||||
|
{
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
user = UserAuthenticate(db, token);
|
||||||
|
if (!user)
|
||||||
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(UserGetPrivileges(user) & USER_ISSUE_TOKENS))
|
||||||
|
{
|
||||||
|
msg = "User doesn't have the ISSUE_TOKENS privilege.";
|
||||||
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (method)
|
||||||
|
{
|
||||||
|
case HTTP_GET:
|
||||||
|
if (ArraySize(path) == 0)
|
||||||
|
{
|
||||||
|
tokensarray = ArrayCreate();
|
||||||
|
|
||||||
|
/* Get all registration tokens */
|
||||||
|
tokens = DbList(db, 2, "tokens", "registration");
|
||||||
|
|
||||||
|
response = HashMapCreate();
|
||||||
|
|
||||||
|
for (i = 0; i < ArraySize(tokens); i++)
|
||||||
|
{
|
||||||
|
char *tokenname = ArrayGet(tokens, i);
|
||||||
|
HashMap *jsoninfo;
|
||||||
|
|
||||||
|
info = RegTokenGetInfo(db, tokenname);
|
||||||
|
jsoninfo = RegTokenInfoToJson(info);
|
||||||
|
|
||||||
|
RegTokenClose(info);
|
||||||
|
RegTokenFree(info);
|
||||||
|
|
||||||
|
ArrayAdd(tokensarray, JsonValueObject(jsoninfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonSet(response, JsonValueArray(tokensarray), 1, "tokens");
|
||||||
|
|
||||||
|
DbListFree(tokens);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
info = RegTokenGetInfo(db, ArrayGet(path, 0));
|
||||||
|
if (!info)
|
||||||
|
{
|
||||||
|
msg = "Token doesn't exist.";
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = RegTokenInfoToJson(info);
|
||||||
|
|
||||||
|
RegTokenClose(info);
|
||||||
|
RegTokenFree(info);
|
||||||
|
break;
|
||||||
|
case HTTP_POST:
|
||||||
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
if (!request)
|
||||||
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
req = Malloc(sizeof(RegTokenInfo));
|
||||||
|
memset(req, 0, sizeof(RegTokenInfo));
|
||||||
|
|
||||||
|
if (!RegTokenInfoFromJson(request, req, &msg))
|
||||||
|
{
|
||||||
|
RegTokenInfoFree(req);
|
||||||
|
Free(req);
|
||||||
|
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
|
goto finish;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req->name)
|
||||||
|
{
|
||||||
|
req->name = StrRandom(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the actual token that will be stored. */
|
||||||
|
info = RegTokenCreate(db, req->name, UserGetName(user),
|
||||||
|
req->expires_on, req->uses,
|
||||||
|
UserDecodePrivileges(req->grants));
|
||||||
|
if (!info)
|
||||||
|
{
|
||||||
|
RegTokenClose(info);
|
||||||
|
RegTokenFree(info);
|
||||||
|
RegTokenInfoFree(req);
|
||||||
|
Free(req);
|
||||||
|
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
msg = "Cannot create token.";
|
||||||
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = RegTokenInfoToJson(info);
|
||||||
|
|
||||||
|
RegTokenClose(info);
|
||||||
|
RegTokenFree(info);
|
||||||
|
RegTokenInfoFree(req);
|
||||||
|
Free(req);
|
||||||
|
break;
|
||||||
|
case HTTP_DELETE:
|
||||||
|
if (ArraySize(path) == 0)
|
||||||
|
{
|
||||||
|
msg = "No registration token given to DELETE /tokens/[token].";
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
info = RegTokenGetInfo(db, ArrayGet(path, 0));
|
||||||
|
RegTokenDelete(info);
|
||||||
|
|
||||||
|
response = HashMapCreate();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Should not be possible. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
finish:
|
||||||
|
UserUnlock(user);
|
||||||
|
JsonFree(request);
|
||||||
|
return response;
|
||||||
|
}
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -24,8 +25,12 @@
|
||||||
|
|
||||||
#include <Routes.h>
|
#include <Routes.h>
|
||||||
|
|
||||||
|
#include <Cytoplasm/Memory.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
|
#include <Config.h>
|
||||||
|
#include <Parser.h>
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
|
|
||||||
ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
||||||
|
@ -37,20 +42,40 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
||||||
HashMap *response;
|
HashMap *response;
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
DbRef *ref;
|
DbRef *ref = NULL;
|
||||||
HashMap *aliases;
|
HashMap *aliases;
|
||||||
|
HashMap *idObject;
|
||||||
JsonValue *val;
|
JsonValue *val;
|
||||||
|
Array *arr;
|
||||||
|
|
||||||
char *token;
|
char *token;
|
||||||
|
char *msg;
|
||||||
User *user = NULL;
|
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");
|
ref = DbLock(db, 1, "aliases");
|
||||||
if (!ref && !(ref = DbCreate(db, 1, "aliases")))
|
if (!ref && !(ref = DbCreate(db, 1, "aliases")))
|
||||||
{
|
{
|
||||||
|
msg = "Unable to access alias database.",
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, "Unable to access alias database.");
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +93,9 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
msg = "There is no mapped room ID for this room alias.";
|
||||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
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;
|
break;
|
||||||
case HTTP_PUT:
|
case HTTP_PUT:
|
||||||
|
@ -91,9 +117,21 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
||||||
if (HttpRequestMethodGet(args->context) == HTTP_PUT)
|
if (HttpRequestMethodGet(args->context) == HTTP_PUT)
|
||||||
{
|
{
|
||||||
HashMap *newAlias;
|
HashMap *newAlias;
|
||||||
|
char *id;
|
||||||
|
char *serverPart;
|
||||||
|
|
||||||
/* TODO: Validate alias domain and make sure it matches
|
serverPart = ParserRecomposeServerPart(aliasID.server);
|
||||||
* server name and is well formed. */
|
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))
|
if (JsonGet(aliases, 2, "alias", alias))
|
||||||
{
|
{
|
||||||
|
@ -110,40 +148,81 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!JsonValueAsString(HashMapGet(request, "room_id")))
|
id = JsonValueAsString(HashMapGet(request, "room_id"));
|
||||||
|
if (!id)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, "Missing or invalid room_id.");
|
response = MatrixErrorCreate(M_BAD_JSON, "Missing or invalid room_id.");
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Validate room ID to make sure it is well
|
if (!ValidCommonID(id, '!'))
|
||||||
* formed. */
|
{
|
||||||
|
msg = "Invalid room ID.";
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
newAlias = HashMapCreate();
|
newAlias = HashMapCreate();
|
||||||
HashMapSet(newAlias, "createdBy", JsonValueString(UserGetName(user)));
|
HashMapSet(newAlias, "createdBy", JsonValueString(UserGetName(user)));
|
||||||
HashMapSet(newAlias, "id", JsonValueDuplicate(HashMapGet(request, "room_id")));
|
HashMapSet(newAlias, "id", JsonValueString(id));
|
||||||
HashMapSet(newAlias, "servers", JsonValueArray(ArrayCreate()));
|
HashMapSet(newAlias, "servers", JsonValueArray(ArrayCreate()));
|
||||||
|
|
||||||
JsonSet(aliases, JsonValueObject(newAlias), 2, "alias", alias);
|
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
|
else
|
||||||
{
|
{
|
||||||
if (!JsonGet(aliases, 2, "alias", alias))
|
HashMap *roomAlias;
|
||||||
|
char *id;
|
||||||
|
|
||||||
|
if (!(val = JsonGet(aliases, 2, "alias", alias)))
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||||
response = MatrixErrorCreate(M_NOT_FOUND, "Room alias not found.");
|
response = MatrixErrorCreate(M_NOT_FOUND, "Room alias not found.");
|
||||||
goto finish;
|
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);
|
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
||||||
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
|
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
|
||||||
|
Free(id);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonValueFree(HashMapDelete(JsonValueAsObject(HashMapGet(aliases, "alias")), alias));
|
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();
|
response = HashMapCreate();
|
||||||
|
|
||||||
|
@ -155,6 +234,8 @@ ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
|
CommonIDFree(aliasID);
|
||||||
|
ConfigUnlock(&config);
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
DbUnlock(db, ref);
|
DbUnlock(db, ref);
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -65,21 +66,25 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
||||||
char *token;
|
char *token;
|
||||||
char *newPassword;
|
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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
return MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
|
msg = "Route only supports POST.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,9 +123,10 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
||||||
newPassword = JsonValueAsString(HashMapGet(request, "new_password"));
|
newPassword = JsonValueAsString(HashMapGet(request, "new_password"));
|
||||||
if (!newPassword)
|
if (!newPassword)
|
||||||
{
|
{
|
||||||
|
msg = "'new_password' is unset or not a string.";
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +158,7 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -33,12 +34,14 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
HashMap *response;
|
HashMap *response;
|
||||||
char *token;
|
char *token;
|
||||||
|
char *msg;
|
||||||
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
Config *config = NULL;
|
Config config;
|
||||||
|
|
||||||
HashMap *request = NULL;
|
HashMap *request = NULL;
|
||||||
Config *newConf;
|
Config newConf;
|
||||||
|
HashMap *newJson = NULL;
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
|
@ -58,24 +61,25 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
|
|
||||||
if (!(UserGetPrivileges(user) & USER_CONFIG))
|
if (!(UserGetPrivileges(user) & USER_CONFIG))
|
||||||
{
|
{
|
||||||
|
msg = "User does not have the 'CONFIG' privilege.";
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
config = ConfigLock(args->matrixArgs->db);
|
ConfigLock(args->matrixArgs->db, &config);
|
||||||
if (!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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_GET:
|
case HTTP_GET:
|
||||||
response = JsonDuplicate(DbJson(config->ref));
|
response = JsonDuplicate(DbJson(config.ref));
|
||||||
break;
|
break;
|
||||||
case HTTP_POST:
|
case HTTP_POST:
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
@ -86,17 +90,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
newConf = ConfigParse(request);
|
ConfigParse(request, &newConf);
|
||||||
if (!newConf)
|
if (newConf.ok)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
if (DbJsonSet(config.ref, request))
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newConf->ok)
|
|
||||||
{
|
|
||||||
if (DbJsonSet(config->ref, request))
|
|
||||||
{
|
{
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
/*
|
/*
|
||||||
|
@ -107,29 +104,71 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
msg = "Internal server error while writing the config.";
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
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(request);
|
||||||
break;
|
break;
|
||||||
case HTTP_PUT:
|
case HTTP_PUT:
|
||||||
/* TODO: Support incremental changes to the config */
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
default:
|
if (!request)
|
||||||
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
newJson = JsonDuplicate(DbJson(config.ref));
|
||||||
|
JsonMerge(newJson, request);
|
||||||
|
|
||||||
|
ConfigParse(newJson, &newConf);
|
||||||
|
|
||||||
|
if (newConf.ok)
|
||||||
|
{
|
||||||
|
if (DbJsonSet(config.ref, newJson))
|
||||||
|
{
|
||||||
|
response = HashMapCreate();
|
||||||
|
/*
|
||||||
|
* TODO: Apply configuration and set this only if a main
|
||||||
|
* component was reconfigured, such as the listeners.
|
||||||
|
*/
|
||||||
|
HashMapSet(response, "restart_required", JsonValueBoolean(1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg = "Internal server error while writing the config.";
|
||||||
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_BAD_JSON, newConf.err);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigFree(&newConf);
|
||||||
|
JsonFree(request);
|
||||||
|
JsonFree(newJson);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msg = "Unknown request method.";
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* 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)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
|
err = "Unknown request method.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, "Unknown request method.");
|
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -45,22 +46,27 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
Config *config = ConfigLock(db);
|
Config config;
|
||||||
|
|
||||||
|
|
||||||
|
char *msg;
|
||||||
|
|
||||||
(void) path;
|
(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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
|
msg = "Route only accepts POST.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,8 +134,9 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
||||||
|
|
||||||
if (!UserDeleteTokens(user, NULL) || !UserDeactivate(user, NULL, NULL))
|
if (!UserDeleteTokens(user, NULL) || !UserDeactivate(user, NULL, NULL))
|
||||||
{
|
{
|
||||||
|
msg = "Internal server error: couldn't remove user properly.";
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +151,6 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
||||||
finish:
|
finish:
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -39,16 +40,17 @@ GetServerName(Db * db)
|
||||||
{
|
{
|
||||||
char *name;
|
char *name;
|
||||||
|
|
||||||
Config *config = ConfigLock(db);
|
Config config;
|
||||||
|
|
||||||
if (!config)
|
ConfigLock(db, &config);
|
||||||
|
if (!config.ok)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = StrDuplicate(config->serverName);
|
name = StrDuplicate(config.serverName);
|
||||||
|
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -62,13 +64,15 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
HashMap *response = NULL;
|
HashMap *response = NULL;
|
||||||
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
UserId *id = NULL;
|
CommonID *id = NULL;
|
||||||
char *token = NULL;
|
char *token = NULL;
|
||||||
|
|
||||||
char *serverName = NULL;
|
char *serverName = NULL;
|
||||||
|
|
||||||
char *userParam = ArrayGet(path, 0);
|
char *userParam = ArrayGet(path, 0);
|
||||||
|
|
||||||
|
char *msg;
|
||||||
|
|
||||||
if (!userParam)
|
if (!userParam)
|
||||||
{
|
{
|
||||||
/* Should be impossible */
|
/* Should be impossible */
|
||||||
|
@ -87,15 +91,17 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
id = UserIdParse(userParam, serverName);
|
id = UserIdParse(userParam, serverName);
|
||||||
if (!id)
|
if (!id)
|
||||||
{
|
{
|
||||||
|
msg = "Invalid user ID.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
goto finish;
|
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);
|
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
||||||
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
|
response = MatrixErrorCreate(M_UNAUTHORIZED, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,10 +119,11 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StrEquals(id->localpart, UserGetName(user)))
|
if (!StrEquals(id->local, UserGetName(user)))
|
||||||
{
|
{
|
||||||
|
msg = "Unauthorized to use /filter.";
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +133,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
|
|
||||||
if (!ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
|
msg = "The filter for this user was not found.";
|
||||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||||
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
response = MatrixErrorCreate(M_NOT_FOUND, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,8 +169,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
filterId = StrRandom(12);
|
filterId = StrRandom(12);
|
||||||
if (!filterId)
|
if (!filterId)
|
||||||
{
|
{
|
||||||
|
msg = "Couldn't generate random filter ID; this is unintended.";
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,8 +179,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
if (!ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
Free(filterId);
|
Free(filterId);
|
||||||
|
msg = "Couldn't write filter to the database, this is unintended.";
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -25,6 +26,8 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <Schema/LoginRequest.h>
|
||||||
|
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
@ -43,13 +46,10 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
HashMap *identifier;
|
HashMap *identifier;
|
||||||
|
|
||||||
char *deviceId = NULL;
|
LoginRequest loginRequest;
|
||||||
char *initialDeviceDisplayName = NULL;
|
LoginRequestUserIdentifier userIdentifier;
|
||||||
int refreshToken = 0;
|
|
||||||
|
|
||||||
char *password;
|
CommonID *userId = NULL;
|
||||||
char *type;
|
|
||||||
UserId *userId = NULL;
|
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
|
@ -57,17 +57,29 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
UserLoginInfo *loginInfo;
|
UserLoginInfo *loginInfo;
|
||||||
char *fullUsername;
|
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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
return MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
|
memset(&loginRequest, 0, sizeof(LoginRequest));
|
||||||
|
memset(&userIdentifier, 0, sizeof(LoginRequestUserIdentifier));
|
||||||
|
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_GET:
|
case HTTP_GET:
|
||||||
|
@ -88,49 +100,27 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = HashMapGet(request, "type");
|
if (!LoginRequestFromJson(request, &loginRequest, &msg))
|
||||||
if (!val)
|
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JsonValueType(val) != JSON_STRING)
|
if (loginRequest.type != REQUEST_TYPE_PASSWORD)
|
||||||
{
|
{
|
||||||
|
msg = "Unsupported login type.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
type = JsonValueAsString(val);
|
identifier = loginRequest.identifier;
|
||||||
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);
|
|
||||||
|
|
||||||
val = HashMapGet(identifier, "type");
|
val = HashMapGet(identifier, "type");
|
||||||
if (!val)
|
if (!val)
|
||||||
{
|
{
|
||||||
|
msg = "No login identifier type set.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||||
break;
|
break;
|
||||||
|
@ -138,112 +128,60 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
if (JsonValueType(val) != JSON_STRING)
|
if (JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
|
msg = "Invalid login identifier type.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
type = JsonValueAsString(val);
|
type = JsonValueAsString(val);
|
||||||
if (!StrEquals(type, "m.id.user"))
|
if (!StrEquals(type, "m.id.user"))
|
||||||
{
|
{
|
||||||
|
msg = "Invalid login identifier type.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!LoginRequestUserIdentifierFromJson(identifier,
|
||||||
val = HashMapGet(identifier, "user");
|
&userIdentifier, &msg))
|
||||||
if (!val)
|
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
break;
|
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)
|
if (!userId)
|
||||||
{
|
{
|
||||||
|
msg = "Invalid user ID.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StrEquals(userId->server, config->serverName)
|
if (!ParserServerNameEquals(userId->server, config.serverName)
|
||||||
|| !UserExists(db, userId->localpart))
|
|| !UserExists(db, userId->local))
|
||||||
{
|
{
|
||||||
|
msg = "Unknown user ID.";
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = HashMapGet(request, "device_id");
|
deviceId = loginRequest.device_id;
|
||||||
if (val)
|
|
||||||
{
|
|
||||||
if (JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceId = JsonValueAsString(val);
|
initialDeviceDisplayName =loginRequest.initial_device_display_name;
|
||||||
}
|
password = loginRequest.password;
|
||||||
|
refreshToken = loginRequest.refresh_token;
|
||||||
|
|
||||||
val = HashMapGet(request, "initial_device_display_name");
|
user = UserLock(db, userId->local);
|
||||||
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);
|
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
|
msg = "Couldn't lock user.";
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,10 +199,11 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
if (!loginInfo)
|
if (!loginInfo)
|
||||||
{
|
{
|
||||||
|
msg = "Invalid creditentials for user.";
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,13 +223,13 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullUsername = StrConcat(4, "@", UserGetName(user), ":",
|
fullUsername = StrConcat(4, "@", UserGetName(user), ":",
|
||||||
config->serverName);
|
config.serverName);
|
||||||
HashMapSet(response, "user_id", JsonValueString(fullUsername));
|
HashMapSet(response, "user_id", JsonValueString(fullUsername));
|
||||||
Free(fullUsername);
|
Free(fullUsername);
|
||||||
|
|
||||||
HashMapSet(response, "well_known",
|
HashMapSet(response, "well_known",
|
||||||
JsonValueObject(
|
JsonValueObject(
|
||||||
MatrixClientWellKnown(config->baseUrl, config->identityServer)));
|
MatrixClientWellKnown(config.baseUrl, config.identityServer)));
|
||||||
|
|
||||||
UserAccessTokenFree(loginInfo->accessToken);
|
UserAccessTokenFree(loginInfo->accessToken);
|
||||||
Free(loginInfo->refreshToken);
|
Free(loginInfo->refreshToken);
|
||||||
|
@ -300,14 +239,18 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
msg = "Route only accepts GET and POST.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserIdFree(userId);
|
UserIdFree(userId);
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
|
|
||||||
|
LoginRequestFree(&loginRequest);
|
||||||
|
LoginRequestUserIdentifierFree(&userIdentifier);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -38,14 +39,17 @@ ROUTE_IMPL(RouteLogout, path, argp)
|
||||||
|
|
||||||
char *tokenstr;
|
char *tokenstr;
|
||||||
|
|
||||||
|
char *msg;
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
User *user;
|
User *user;
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
|
msg = "This route only accepts POST.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &tokenstr);
|
response = MatrixGetAccessToken(args->context, &tokenstr);
|
||||||
|
@ -84,8 +88,9 @@ ROUTE_IMPL(RouteLogout, path, argp)
|
||||||
{
|
{
|
||||||
if (!UserDeleteToken(user, tokenstr))
|
if (!UserDeleteToken(user, tokenstr))
|
||||||
{
|
{
|
||||||
|
msg = "Internal server error: couldn't delete token.";
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -39,6 +40,8 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
JsonValue *val;
|
JsonValue *val;
|
||||||
int privileges;
|
int privileges;
|
||||||
|
|
||||||
|
char *msg;
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
response = MatrixGetAccessToken(args->context, &token);
|
||||||
if (response)
|
if (response)
|
||||||
{
|
{
|
||||||
|
@ -55,8 +58,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
|
|
||||||
if (!(UserGetPrivileges(user) & USER_GRANT_PRIVILEGES))
|
if (!(UserGetPrivileges(user) & USER_GRANT_PRIVILEGES))
|
||||||
{
|
{
|
||||||
|
msg = "User doesn't have the GRANT_PRIVILEGES privilege";
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +72,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
user = UserLock(args->matrixArgs->db, ArrayGet(path, 0));
|
user = UserLock(args->matrixArgs->db, ArrayGet(path, 0));
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
|
msg = "Unknown user.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,23 +95,24 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
val = HashMapGet(request, "privileges");
|
val = HashMapGet(request, "privileges");
|
||||||
if (!val || JsonValueType(val) != JSON_ARRAY)
|
if (!val || JsonValueType(val) != JSON_ARRAY)
|
||||||
{
|
{
|
||||||
|
msg = "'privileges' is unset or not an array.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_POST:
|
case HTTP_POST:
|
||||||
privileges = UserDecodePrivileges(val);
|
privileges = UserDecodePrivileges(JsonValueAsArray(val));
|
||||||
break;
|
break;
|
||||||
case HTTP_PUT:
|
case HTTP_PUT:
|
||||||
privileges = UserGetPrivileges(user);
|
privileges = UserGetPrivileges(user);
|
||||||
privileges |= UserDecodePrivileges(val);
|
privileges |= UserDecodePrivileges(JsonValueAsArray(val));
|
||||||
break;
|
break;
|
||||||
case HTTP_DELETE:
|
case HTTP_DELETE:
|
||||||
privileges = UserGetPrivileges(user);
|
privileges = UserGetPrivileges(user);
|
||||||
privileges &= ~UserDecodePrivileges(val);
|
privileges &= ~UserDecodePrivileges(JsonValueAsArray(val));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* Impossible */
|
/* Impossible */
|
||||||
|
@ -116,19 +122,21 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
|
|
||||||
if (!UserSetPrivileges(user, privileges))
|
if (!UserSetPrivileges(user, privileges))
|
||||||
{
|
{
|
||||||
|
msg = "Internal server error: couldn't set privileges.";
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fall through */
|
/* Fall through */
|
||||||
case HTTP_GET:
|
case HTTP_GET:
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
HashMapSet(response, "privileges", UserEncodePrivileges(UserGetPrivileges(user)));
|
HashMapSet(response, "privileges", JsonValueArray(UserEncodePrivileges(UserGetPrivileges(user))));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
msg = "Route only accepts POST, PUT, DELETE, and GET.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -23,7 +24,6 @@
|
||||||
*/
|
*/
|
||||||
#include <Routes.h>
|
#include <Routes.h>
|
||||||
|
|
||||||
#include <Cytoplasm/Int64.h>
|
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
#include <Cytoplasm/Memory.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
@ -37,6 +37,7 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
||||||
char *op = ArrayGet(path, 0);
|
char *op = ArrayGet(path, 0);
|
||||||
HashMap *response;
|
HashMap *response;
|
||||||
char *token;
|
char *token;
|
||||||
|
char *msg;
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
response = MatrixGetAccessToken(args->context, &token);
|
||||||
|
@ -55,11 +56,13 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
||||||
|
|
||||||
if (!(UserGetPrivileges(user) & USER_PROC_CONTROL))
|
if (!(UserGetPrivileges(user) & USER_PROC_CONTROL))
|
||||||
{
|
{
|
||||||
|
msg = "User doesn't have PROC_CONTROL privilege.";
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg = "Unknown operation.";
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_POST:
|
case HTTP_POST:
|
||||||
|
@ -74,7 +77,7 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -82,36 +85,23 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
||||||
if (StrEquals(op, "stats"))
|
if (StrEquals(op, "stats"))
|
||||||
{
|
{
|
||||||
size_t allocated = MemoryAllocated();
|
size_t allocated = MemoryAllocated();
|
||||||
Int64 a;
|
|
||||||
|
|
||||||
response = HashMapCreate();
|
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, "version", JsonValueString(TELODENDRIA_VERSION));
|
||||||
HashMapSet(response, "memory_allocated", JsonValueInteger(a));
|
HashMapSet(response, "memory_allocated", JsonValueInteger(allocated));
|
||||||
|
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -45,6 +46,8 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
||||||
UserAccessToken *newAccessToken;
|
UserAccessToken *newAccessToken;
|
||||||
char *deviceId;
|
char *deviceId;
|
||||||
|
|
||||||
|
char *msg;
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
|
@ -55,8 +58,9 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
|
msg = "This route only accepts POST.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
@ -69,8 +73,9 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
||||||
val = HashMapGet(request, "refresh_token");
|
val = HashMapGet(request, "refresh_token");
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
|
msg = "'refresh_token' is unset or not a string.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -30,6 +31,8 @@
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
#include <Cytoplasm/Memory.h>
|
||||||
|
|
||||||
|
#include <Schema/Registration.h>
|
||||||
|
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
#include <Uia.h>
|
#include <Uia.h>
|
||||||
#include <RegToken.h>
|
#include <RegToken.h>
|
||||||
|
@ -55,35 +58,36 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
HashMap *request = NULL;
|
HashMap *request = NULL;
|
||||||
HashMap *response = NULL;
|
HashMap *response = NULL;
|
||||||
|
|
||||||
JsonValue *val;
|
RegistrationRequest regReq;
|
||||||
|
|
||||||
char *kind;
|
char *kind;
|
||||||
|
|
||||||
char *username = NULL;
|
|
||||||
char *password = NULL;
|
|
||||||
char *initialDeviceDisplayName = NULL;
|
|
||||||
int refreshToken = 0;
|
|
||||||
int inhibitLogin = 0;
|
|
||||||
char *deviceId = NULL;
|
|
||||||
char *fullUsername;
|
char *fullUsername;
|
||||||
|
char *msg;
|
||||||
|
char *username;
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
|
|
||||||
Array *uiaFlows = NULL;
|
Array *uiaFlows = NULL;
|
||||||
int uiaResult;
|
int uiaResult;
|
||||||
|
|
||||||
char *session;
|
char *session;
|
||||||
DbRef *sessionRef;
|
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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
return MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ArraySize(path) == 0)
|
if (ArraySize(path) == 0)
|
||||||
|
@ -102,26 +106,23 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
if (!RegistrationRequestFromJson(request, ®Req, &msg))
|
||||||
val = HashMapGet(request, "username");
|
|
||||||
if (val)
|
|
||||||
{
|
|
||||||
if (JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_NOT_JSON, msg);
|
||||||
goto finish;
|
goto end;
|
||||||
}
|
}
|
||||||
username = StrDuplicate(JsonValueAsString(val));
|
|
||||||
|
|
||||||
if (!UserValidate(username, config->serverName))
|
if (regReq.username)
|
||||||
|
{
|
||||||
|
if (!UserValidate(regReq.username, config.serverName))
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
|
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UserExists(db, username))
|
if (UserExists(db, regReq.username))
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_USER_IN_USE, NULL);
|
response = MatrixErrorCreate(M_USER_IN_USE, NULL);
|
||||||
|
@ -132,7 +133,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
uiaFlows = ArrayCreate();
|
uiaFlows = ArrayCreate();
|
||||||
ArrayAdd(uiaFlows, RouteRegisterRegFlow());
|
ArrayAdd(uiaFlows, RouteRegisterRegFlow());
|
||||||
|
|
||||||
if (config->flags & CONFIG_REGISTRATION)
|
if (config.registration)
|
||||||
{
|
{
|
||||||
ArrayAdd(uiaFlows, UiaDummyFlow());
|
ArrayAdd(uiaFlows, UiaDummyFlow());
|
||||||
}
|
}
|
||||||
|
@ -158,99 +159,44 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
/* We don't support guest accounts yet */
|
/* We don't support guest accounts yet */
|
||||||
if (kind && !StrEquals(kind, "user"))
|
if (kind && !StrEquals(kind, "user"))
|
||||||
{
|
{
|
||||||
|
msg = "Guest accounts are currently not supported";
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = HashMapGet(request, "password");
|
if (!regReq.password)
|
||||||
if (!val)
|
|
||||||
{
|
{
|
||||||
|
msg = "'password' field is unset";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
response = MatrixErrorCreate(M_MISSING_PARAM, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JsonValueType(val) != JSON_STRING)
|
/* All of the other fields are optional, we don't have to check
|
||||||
{
|
* them. */
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
password = StrDuplicate(JsonValueAsString(val));
|
user = UserCreate(db, regReq.username, regReq.password);
|
||||||
|
|
||||||
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);
|
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
|
|
||||||
fullUsername = StrConcat(4, "@", UserGetName(user), ":", config->serverName);
|
fullUsername = StrConcat(4,
|
||||||
|
"@", UserGetName(user), ":", config.serverName);
|
||||||
HashMapSet(response, "user_id", JsonValueString(fullUsername));
|
HashMapSet(response, "user_id", JsonValueString(fullUsername));
|
||||||
Free(fullUsername);
|
Free(fullUsername);
|
||||||
|
|
||||||
HttpResponseStatus(args->context, HTTP_OK);
|
HttpResponseStatus(args->context, HTTP_OK);
|
||||||
if (!inhibitLogin)
|
if (!regReq.inhibit_login)
|
||||||
{
|
{
|
||||||
UserLoginInfo *loginInfo = UserLogin(user, password, deviceId,
|
UserLoginInfo *loginInfo = UserLogin(user, regReq.password,
|
||||||
initialDeviceDisplayName, refreshToken);
|
regReq.device_id, regReq.initial_device_display_name,
|
||||||
|
regReq.refresh_token);
|
||||||
|
|
||||||
HashMapSet(response, "access_token",
|
HashMapSet(response, "access_token",
|
||||||
JsonValueString(loginInfo->accessToken->string));
|
JsonValueString(loginInfo->accessToken->string));
|
||||||
HashMapSet(response, "device_id",
|
HashMapSet(response, "device_id",
|
||||||
JsonValueString(loginInfo->accessToken->deviceId));
|
JsonValueString(loginInfo->accessToken->deviceId));
|
||||||
|
|
||||||
if (refreshToken)
|
if (regReq.refresh_token)
|
||||||
{
|
{
|
||||||
HashMapSet(response, "expires_in_ms",
|
HashMapSet(response, "expires_in_ms",
|
||||||
JsonValueInteger(loginInfo->accessToken->lifetime));
|
JsonValueInteger(loginInfo->accessToken->lifetime));
|
||||||
|
@ -276,7 +222,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
|
|
||||||
if (info)
|
if (info)
|
||||||
{
|
{
|
||||||
UserSetPrivileges(user, info->grants);
|
UserSetPrivileges(user, UserDecodePrivileges(info->grants));
|
||||||
RegTokenClose(info);
|
RegTokenClose(info);
|
||||||
RegTokenFree(info);
|
RegTokenFree(info);
|
||||||
}
|
}
|
||||||
|
@ -294,10 +240,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
finish:
|
finish:
|
||||||
UiaFlowsFree(uiaFlows);
|
UiaFlowsFree(uiaFlows);
|
||||||
Free(username);
|
RegistrationRequestFree(®Req);
|
||||||
Free(password);
|
|
||||||
Free(deviceId);
|
|
||||||
Free(initialDeviceDisplayName);
|
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -310,10 +253,11 @@ finish:
|
||||||
|
|
||||||
if (!username)
|
if (!username)
|
||||||
{
|
{
|
||||||
|
msg = "'username' path parameter is not set.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
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);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
|
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
|
||||||
|
@ -337,6 +281,6 @@ finish:
|
||||||
}
|
}
|
||||||
|
|
||||||
end:
|
end:
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -26,19 +27,35 @@
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
|
|
||||||
|
#include <Schema/RequestToken.h>
|
||||||
|
|
||||||
ROUTE_IMPL(RouteRequestToken, path, argp)
|
ROUTE_IMPL(RouteRequestToken, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
char *type = ArrayGet(path, 0);
|
char *type = ArrayGet(path, 0);
|
||||||
HashMap *request;
|
HashMap *request;
|
||||||
HashMap *response;
|
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)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
|
msg = "This route only accepts POST.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
@ -48,87 +65,92 @@ ROUTE_IMPL(RouteRequestToken, path, argp)
|
||||||
return MatrixErrorCreate(M_NOT_JSON, NULL);
|
return MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
val = HashMapGet(request, "client_secret");
|
if (!RequestTokenFromJson(request, &reqTok, &msg))
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
str = JsonValueAsString(val);
|
if (!reqTok.client_secret)
|
||||||
if (strlen(str) > 255 || StrBlank(str))
|
|
||||||
{
|
{
|
||||||
|
msg = "'client_secret' is not set";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = HashMapGet(request, "send_attempt");
|
if (strlen(reqTok.client_secret) > 255 || StrBlank(reqTok.client_secret))
|
||||||
if (!val || JsonValueType(val) != JSON_INTEGER)
|
|
||||||
{
|
{
|
||||||
|
msg = "'client_secret' is blank or too long";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = HashMapGet(request, "next_link");
|
if (reqTok.send_attempt == -1)
|
||||||
if (val && JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
{
|
||||||
|
msg = "Invalid or inexistent 'send_attempt'";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = HashMapGet(request, "id_access_token");
|
if (!reqTok.next_link)
|
||||||
if (val && JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
{
|
||||||
|
msg = "'next_link' is not set";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
if (!reqTok.id_access_token)
|
||||||
val = HashMapGet(request, "id_server");
|
|
||||||
if (val && JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
{
|
||||||
|
msg = "'id_access_token' is not set";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
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;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StrEquals(type, "email"))
|
if (StrEquals(type, "email"))
|
||||||
{
|
{
|
||||||
val = HashMapGet(request, "email");
|
if (!reqTok.email)
|
||||||
if (val && JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
{
|
||||||
|
msg = "Type is set to 'email' yet none was set";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (StrEquals(type, "msisdn"))
|
else if (StrEquals(type, "msisdn"))
|
||||||
{
|
{
|
||||||
val = HashMapGet(request, "country");
|
if (!reqTok.country)
|
||||||
if (val && JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
{
|
||||||
|
msg = "Type is set to 'msisdn' yet no country is set";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
str = JsonValueAsString(val);
|
if (strlen(reqTok.country) != 2)
|
||||||
if (strlen(str) != 2)
|
|
||||||
{
|
{
|
||||||
|
msg = "Invalid country tag, length must be 2";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = HashMapGet(request, "phone_number");
|
if (!reqTok.phone_number)
|
||||||
if (val && JsonValueType(val) != JSON_STRING)
|
|
||||||
{
|
{
|
||||||
|
msg = "Type is set to 'msisdn' yet phone_number is unset";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,5 +167,6 @@ ROUTE_IMPL(RouteRequestToken, path, argp)
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
|
RequestTokenFree(&reqTok);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -24,21 +25,80 @@
|
||||||
|
|
||||||
#include <Routes.h>
|
#include <Routes.h>
|
||||||
|
|
||||||
|
#include <Cytoplasm/Memory.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
|
#include <Cytoplasm/Db.h>
|
||||||
|
|
||||||
|
#include <Matrix.h>
|
||||||
|
#include <User.h>
|
||||||
|
|
||||||
ROUTE_IMPL(RouteRoomAliases, path, argp)
|
ROUTE_IMPL(RouteRoomAliases, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
char *roomId = ArrayGet(path, 0);
|
char *roomId = ArrayGet(path, 0);
|
||||||
|
char *token;
|
||||||
|
char *msg;
|
||||||
|
|
||||||
HashMap *request = NULL;
|
|
||||||
HashMap *response = NULL;
|
HashMap *response = NULL;
|
||||||
|
HashMap *aliases = NULL;
|
||||||
|
HashMap *reversealias = NULL;
|
||||||
|
|
||||||
|
JsonValue *val;
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
DbRef *ref = NULL;
|
DbRef *ref = NULL;
|
||||||
|
|
||||||
|
User *user = NULL;
|
||||||
|
|
||||||
|
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:
|
finish:
|
||||||
DbUnlock(db, ref);
|
DbUnlock(db, ref);
|
||||||
JsonFree(request);
|
UserUnlock(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -41,13 +42,15 @@ ROUTE_IMPL(RouteTokenValid, path, argp)
|
||||||
RegTokenInfo *info = NULL;
|
RegTokenInfo *info = NULL;
|
||||||
|
|
||||||
char *tokenstr;
|
char *tokenstr;
|
||||||
|
char *msg;
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
||||||
{
|
{
|
||||||
|
msg = "This route only accepts GET.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* 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 *authType = ArrayGet(path, 0);
|
||||||
char *sessionId;
|
char *sessionId;
|
||||||
|
|
||||||
|
char *msg;
|
||||||
|
|
||||||
if (!authType)
|
if (!authType)
|
||||||
{
|
{
|
||||||
/* This should never happen */
|
/* This should never happen */
|
||||||
|
@ -49,22 +52,22 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
||||||
HashMap *request;
|
HashMap *request;
|
||||||
HashMap *response;
|
HashMap *response;
|
||||||
int uiaResult;
|
int uiaResult;
|
||||||
Config *config;
|
Config config;
|
||||||
Array *flows;
|
Array *flows;
|
||||||
Array *flow;
|
Array *flow;
|
||||||
|
|
||||||
config = ConfigLock(args->matrixArgs->db);
|
ConfigLock(args->matrixArgs->db, &config);
|
||||||
if (!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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
return MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
}
|
}
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
if (!request)
|
if (!request)
|
||||||
{
|
{
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_NOT_JSON, NULL);
|
return MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
}
|
}
|
||||||
|
@ -88,20 +91,22 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
else if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
else if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
||||||
{
|
{
|
||||||
|
msg = "Route only supports GET.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionId = HashMapGet(requestParams, "session");
|
sessionId = HashMapGet(requestParams, "session");
|
||||||
if (!sessionId)
|
if (!sessionId)
|
||||||
{
|
{
|
||||||
|
msg = "'session' parameter is unset.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
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");
|
HttpResponseHeader(args->context, "Content-Type", "text/html");
|
||||||
|
|
199
src/Routes/RouteUserDirectory.c
Normal file
199
src/Routes/RouteUserDirectory.c
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person
|
||||||
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
* (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without dirRequest.limitation the rights to use, copy, modify, merge,
|
||||||
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
* and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include <Routes.h>
|
||||||
|
|
||||||
|
#include <Cytoplasm/Array.h>
|
||||||
|
#include <Cytoplasm/HashMap.h>
|
||||||
|
#include <Cytoplasm/Json.h>
|
||||||
|
#include <Cytoplasm/Str.h>
|
||||||
|
#include <Cytoplasm/Memory.h>
|
||||||
|
#include <Cytoplasm/Db.h>
|
||||||
|
|
||||||
|
#include <Schema/UserDirectoryRequest.h>
|
||||||
|
|
||||||
|
#include <User.h>
|
||||||
|
|
||||||
|
ROUTE_IMPL(RouteUserDirectory, path, argp)
|
||||||
|
{
|
||||||
|
RouteArgs *args = argp;
|
||||||
|
HashMap *response = NULL;
|
||||||
|
HashMap *request = NULL;
|
||||||
|
|
||||||
|
Array *users = NULL;
|
||||||
|
Array *results = NULL;
|
||||||
|
|
||||||
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
|
Config config = { .ok = 0 };
|
||||||
|
|
||||||
|
User *user = NULL;
|
||||||
|
|
||||||
|
char *token = NULL;
|
||||||
|
char *requesterName = NULL;
|
||||||
|
char *msg = NULL;
|
||||||
|
|
||||||
|
UserDirectoryRequest dirRequest;
|
||||||
|
|
||||||
|
size_t i, included;
|
||||||
|
|
||||||
|
(void) path;
|
||||||
|
|
||||||
|
dirRequest.search_term = NULL;
|
||||||
|
dirRequest.limit = 10;
|
||||||
|
|
||||||
|
|
||||||
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
|
{
|
||||||
|
msg = "Request supports only POST.";
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
if (!request)
|
||||||
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
if (!UserDirectoryRequestFromJson(request, &dirRequest, &msg))
|
||||||
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
if (!dirRequest.search_term)
|
||||||
|
{
|
||||||
|
msg = "Field 'search_term' not set.";
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
|
goto finish;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
response = MatrixGetAccessToken(args->context, &token);
|
||||||
|
if (response)
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Actually use information related to the user. */
|
||||||
|
user = UserAuthenticate(db, token);
|
||||||
|
if (!user)
|
||||||
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
requesterName = UserGetName(user);
|
||||||
|
|
||||||
|
response = HashMapCreate();
|
||||||
|
results = ArrayCreate();
|
||||||
|
|
||||||
|
/* TODO: Check for users matching search term and users outside our
|
||||||
|
* local server. */
|
||||||
|
users = DbList(db, 1, "users");
|
||||||
|
|
||||||
|
ConfigLock(db, &config);
|
||||||
|
if (!config.ok)
|
||||||
|
{
|
||||||
|
Log(LOG_ERR, "Directory endpoint failed to lock configuration.");
|
||||||
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
response = MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
|
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IncludedLtLimit ((int64_t) included < dirRequest.limit)
|
||||||
|
for (i = 0, included = 0; i < ArraySize(users) && IncludedLtLimit; i++)
|
||||||
|
#undef IncludedLtLimit
|
||||||
|
{
|
||||||
|
HashMap *obj;
|
||||||
|
User *currentUser;
|
||||||
|
char *name = ArrayGet(users, i);
|
||||||
|
char *displayName;
|
||||||
|
char *lowerDisplayName;
|
||||||
|
char *avatarUrl;
|
||||||
|
|
||||||
|
if (!StrEquals(name, requesterName))
|
||||||
|
{
|
||||||
|
currentUser = UserLock(db, name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentUser = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName = UserGetProfile(currentUser, "displayname");
|
||||||
|
lowerDisplayName = StrLower(displayName);
|
||||||
|
avatarUrl = UserGetProfile(currentUser, "avatar_url");
|
||||||
|
|
||||||
|
/* Check for the user ID and display name. */
|
||||||
|
if (strstr(name, dirRequest.search_term) ||
|
||||||
|
(lowerDisplayName &&
|
||||||
|
strstr(lowerDisplayName, dirRequest.search_term)))
|
||||||
|
{
|
||||||
|
included++;
|
||||||
|
|
||||||
|
obj = HashMapCreate();
|
||||||
|
if (displayName)
|
||||||
|
{
|
||||||
|
JsonSet(obj, JsonValueString(displayName), 1, "display_name");
|
||||||
|
}
|
||||||
|
if (avatarUrl)
|
||||||
|
{
|
||||||
|
JsonSet(obj, JsonValueString(displayName), 1, "avatar_url");
|
||||||
|
}
|
||||||
|
if (name)
|
||||||
|
{
|
||||||
|
char *uID = StrConcat(4, "@", name, ":", config.serverName);
|
||||||
|
JsonSet(obj, JsonValueString(uID), 1, "user_id");
|
||||||
|
Free(uID);
|
||||||
|
}
|
||||||
|
ArrayAdd(results, JsonValueObject(obj));
|
||||||
|
}
|
||||||
|
if (lowerDisplayName)
|
||||||
|
{
|
||||||
|
Free(lowerDisplayName);
|
||||||
|
}
|
||||||
|
if (!StrEquals(name, requesterName))
|
||||||
|
{
|
||||||
|
UserUnlock(currentUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonSet(response, JsonValueArray(results), 1, "results");
|
||||||
|
JsonSet(response,
|
||||||
|
JsonValueBoolean((int64_t) included == dirRequest.limit),
|
||||||
|
1, "limited"
|
||||||
|
);
|
||||||
|
|
||||||
|
finish:
|
||||||
|
UserUnlock(user);
|
||||||
|
JsonFree(request);
|
||||||
|
DbListFree(users);
|
||||||
|
ConfigUnlock(&config);
|
||||||
|
UserDirectoryRequestFree(&dirRequest);
|
||||||
|
return response;
|
||||||
|
}
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -39,7 +40,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
HashMap *request = NULL;
|
HashMap *request = NULL;
|
||||||
HashMap *response = NULL;
|
HashMap *response = NULL;
|
||||||
|
|
||||||
UserId *userId = NULL;
|
CommonID *userId = NULL;
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
|
|
||||||
char *serverName;
|
char *serverName;
|
||||||
|
@ -48,30 +49,37 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
char *token = NULL;
|
char *token = NULL;
|
||||||
char *value = 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);
|
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);
|
username = ArrayGet(path, 0);
|
||||||
userId = UserIdParse(username, serverName);
|
userId = UserIdParse(username, serverName);
|
||||||
if (!userId)
|
if (!userId)
|
||||||
{
|
{
|
||||||
|
msg = "Invalid user ID.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
if (strcmp(userId->server, serverName))
|
if (!ParserServerNameEquals(userId->server, serverName))
|
||||||
{
|
{
|
||||||
/* TODO: Implement lookup over federation. */
|
/* TODO: Implement lookup over federation. */
|
||||||
|
msg = "User profile endpoint currently doesn't support lookup over "
|
||||||
|
"federation.";
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,11 +87,12 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_GET:
|
case HTTP_GET:
|
||||||
user = UserLock(db, userId->localpart);
|
user = UserLock(db, userId->local);
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
|
msg = "Couldn't lock user.";
|
||||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||||
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
response = MatrixErrorCreate(M_NOT_FOUND, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,24 +147,26 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
StrEquals(entry, "avatar_url"))
|
StrEquals(entry, "avatar_url"))
|
||||||
{
|
{
|
||||||
/* Check if user has privilege to do that action. */
|
/* 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));
|
value = JsonValueAsString(HashMapGet(request, entry));
|
||||||
/* TODO: Make UserSetProfile notify other
|
/* TODO: Make UserSetProfile notify other parties of
|
||||||
* parties of the change */
|
* the change */
|
||||||
UserSetProfile(user, entry, value);
|
UserSetProfile(user, entry, value);
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
/* User is not allowed to carry-on the action */
|
/* User is not allowed to carry-on the action */
|
||||||
|
msg = "Cannot change another user's profile.";
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
msg = "Invalid property being changed.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,17 +177,14 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
msg = "Route only accepts GET and PUT.";
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
finish:
|
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);
|
UserIdFree(userId);
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -35,7 +36,17 @@ ROUTE_IMPL(RouteVersions, path, argp)
|
||||||
(void) path;
|
(void) path;
|
||||||
(void) argp;
|
(void) argp;
|
||||||
|
|
||||||
ArrayAdd(versions, JsonValueString("v1.6"));
|
#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");
|
||||||
|
|
||||||
|
#undef DECLARE_SPEC_VERSION
|
||||||
|
|
||||||
HashMapSet(response, "versions", JsonValueArray(versions));
|
HashMapSet(response, "versions", JsonValueArray(versions));
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -35,18 +36,19 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
HashMap *response;
|
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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
return MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StrEquals(ArrayGet(path, 0), "client"))
|
if (StrEquals(ArrayGet(path, 0), "client"))
|
||||||
{
|
{
|
||||||
response = MatrixClientWellKnown(config->baseUrl, config->identityServer);
|
response = MatrixClientWellKnown(config.baseUrl, config.identityServer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -54,6 +56,6 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
|
||||||
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -43,13 +44,15 @@ ROUTE_IMPL(RouteWhoami, path, argp)
|
||||||
char *userID;
|
char *userID;
|
||||||
char *deviceID;
|
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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
return MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
@ -73,7 +76,7 @@ ROUTE_IMPL(RouteWhoami, path, argp)
|
||||||
|
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
|
|
||||||
userID = StrConcat(4, "@", UserGetName(user), ":", config->serverName);
|
userID = StrConcat(4, "@", UserGetName(user), ":", config.serverName);
|
||||||
deviceID = StrDuplicate(UserGetDeviceId(user));
|
deviceID = StrDuplicate(UserGetDeviceId(user));
|
||||||
|
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
|
@ -85,6 +88,6 @@ ROUTE_IMPL(RouteWhoami, path, argp)
|
||||||
Free(deviceID);
|
Free(deviceID);
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(&config);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -33,12 +34,14 @@
|
||||||
static HashMap *
|
static HashMap *
|
||||||
StateResolveV1(Array * states)
|
StateResolveV1(Array * states)
|
||||||
{
|
{
|
||||||
|
(void) states;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static HashMap *
|
static HashMap *
|
||||||
StateResolveV2(Array * states)
|
StateResolveV2(Array * states)
|
||||||
{
|
{
|
||||||
|
(void) states;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* 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, "%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, "");
|
||||||
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,
|
Log(LOG_INFO,
|
||||||
"Documentation/Support: https://telodendria.io");
|
"Documentation/Support: https://telodendria.io");
|
||||||
Log(LOG_INFO, "");
|
Log(LOG_INFO, "");
|
||||||
|
|
33
src/Uia.c
33
src/Uia.c
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* 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);
|
json = DbJson(ref);
|
||||||
HashMapSet(json, "completed", JsonValueArray(ArrayCreate()));
|
HashMapSet(json, "completed", JsonValueArray(ArrayCreate()));
|
||||||
HashMapSet(json, "last_access", JsonValueInteger(UtilServerTs()));
|
HashMapSet(json, "last_access", JsonValueInteger(UtilTsMillis()));
|
||||||
DbUnlock(db, ref);
|
DbUnlock(db, ref);
|
||||||
|
|
||||||
HashMapSet(*response, "completed", JsonValueArray(ArrayCreate()));
|
HashMapSet(*response, "completed", JsonValueArray(ArrayCreate()));
|
||||||
|
@ -205,7 +206,7 @@ UiaStageBuild(char *type, HashMap * params)
|
||||||
|
|
||||||
int
|
int
|
||||||
UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
HashMap * request, HashMap ** response, Config * config)
|
HashMap * request, HashMap ** response, Config config)
|
||||||
{
|
{
|
||||||
JsonValue *val;
|
JsonValue *val;
|
||||||
HashMap *auth;
|
HashMap *auth;
|
||||||
|
@ -222,6 +223,8 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
HashMap *dbJson;
|
HashMap *dbJson;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
char *msg;
|
||||||
|
|
||||||
if (!flows)
|
if (!flows)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -242,8 +245,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
|
|
||||||
if (JsonValueType(val) != JSON_OBJECT)
|
if (JsonValueType(val) != JSON_OBJECT)
|
||||||
{
|
{
|
||||||
|
msg = "'auth' is not an object.";
|
||||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||||
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,8 +256,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
|
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
|
msg = "'auth->session' is unset or not a string.";
|
||||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||||
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,8 +316,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
|
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
|
msg = "'auth->type' is unset or not a string.";
|
||||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||||
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||||
ret = 0;
|
ret = 0;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
@ -345,7 +351,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
char *password = JsonValueAsString(HashMapGet(auth, "password"));
|
char *password = JsonValueAsString(HashMapGet(auth, "password"));
|
||||||
HashMap *identifier = JsonValueAsObject(HashMapGet(auth, "identifier"));
|
HashMap *identifier = JsonValueAsObject(HashMapGet(auth, "identifier"));
|
||||||
char *type;
|
char *type;
|
||||||
UserId *userId;
|
CommonID *userId;
|
||||||
User *user;
|
User *user;
|
||||||
|
|
||||||
if (!password || !identifier)
|
if (!password || !identifier)
|
||||||
|
@ -357,10 +363,11 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
|
|
||||||
type = JsonValueAsString(HashMapGet(identifier, "type"));
|
type = JsonValueAsString(HashMapGet(identifier, "type"));
|
||||||
userId = UserIdParse(JsonValueAsString(HashMapGet(identifier, "user")),
|
userId = UserIdParse(JsonValueAsString(HashMapGet(identifier, "user")),
|
||||||
config->serverName);
|
config.serverName);
|
||||||
|
|
||||||
if (!type || !StrEquals(type, "m.id.user")
|
if (!type || !StrEquals(type, "m.id.user")
|
||||||
|| !userId || !StrEquals(userId->server, config->serverName))
|
|| !userId
|
||||||
|
|| !ParserServerNameEquals(userId->server, config.serverName))
|
||||||
{
|
{
|
||||||
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
||||||
ret = BuildResponse(flows, db, response, session, dbRef);
|
ret = BuildResponse(flows, db, response, session, dbRef);
|
||||||
|
@ -368,7 +375,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
user = UserLock(db, userId->localpart);
|
user = UserLock(db, userId->local);
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
||||||
|
@ -445,7 +452,7 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
ArrayFree(possibleNext);
|
ArrayFree(possibleNext);
|
||||||
JsonValueFree(HashMapSet(dbJson, "last_access", JsonValueInteger(UtilServerTs())));
|
JsonValueFree(HashMapSet(dbJson, "last_access", JsonValueInteger(UtilTsMillis())));
|
||||||
DbUnlock(db, dbRef);
|
DbUnlock(db, dbRef);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -491,7 +498,7 @@ UiaCleanup(MatrixHttpHandlerArgs * args)
|
||||||
char *session = ArrayGet(sessions, i);
|
char *session = ArrayGet(sessions, i);
|
||||||
DbRef *ref = DbLock(args->db, 2, "user_interactive", session);
|
DbRef *ref = DbLock(args->db, 2, "user_interactive", session);
|
||||||
|
|
||||||
UInt64 lastAccess;
|
uint64_t lastAccess;
|
||||||
|
|
||||||
if (!ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
|
@ -506,7 +513,7 @@ UiaCleanup(MatrixHttpHandlerArgs * args)
|
||||||
|
|
||||||
/* If last access was greater than 15 minutes ago, remove this
|
/* If last access was greater than 15 minutes ago, remove this
|
||||||
* session */
|
* session */
|
||||||
if (UInt64Gt(UInt64Sub(UtilServerTs(), lastAccess), UInt64Create(0, 1000 * 60 * 15)))
|
if ((UtilTsMillis() - lastAccess) > (1000 * 60 * 15))
|
||||||
{
|
{
|
||||||
DbDelete(args->db, 2, "user_interactive", session);
|
DbDelete(args->db, 2, "user_interactive", session);
|
||||||
Log(LOG_DEBUG, "Deleted session %s", session);
|
Log(LOG_DEBUG, "Deleted session %s", session);
|
||||||
|
|
185
src/User.c
185
src/User.c
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -27,8 +28,8 @@
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
#include <Cytoplasm/Sha.h>
|
#include <Cytoplasm/Sha.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Int64.h>
|
|
||||||
#include <Cytoplasm/UInt64.h>
|
#include <Parser.h>
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ struct User
|
||||||
char *deviceId;
|
char *deviceId;
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserValidate(char *localpart, char *domain)
|
UserValidate(char *localpart, char *domain)
|
||||||
{
|
{
|
||||||
size_t maxLen = 255 - strlen(domain) - 1;
|
size_t maxLen = 255 - strlen(domain) - 1;
|
||||||
|
@ -53,23 +54,23 @@ UserValidate(char *localpart, char *domain)
|
||||||
|
|
||||||
if (i > maxLen)
|
if (i > maxLen)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||||
(c == '.') || (c == '_') || (c == '=') || (c == '-') ||
|
(c == '.') || (c == '_') || (c == '=') || (c == '-') ||
|
||||||
(c == '/')))
|
(c == '/')))
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserHistoricalValidate(char *localpart, char *domain)
|
UserHistoricalValidate(char *localpart, char *domain)
|
||||||
{
|
{
|
||||||
size_t maxLen = 255 - strlen(domain) - 1;
|
size_t maxLen = 255 - strlen(domain) - 1;
|
||||||
|
@ -81,21 +82,21 @@ UserHistoricalValidate(char *localpart, char *domain)
|
||||||
|
|
||||||
if (i > maxLen)
|
if (i > maxLen)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!((c >= 0x21 && c <= 0x39) || (c >= 0x3B && c <= 0x7E)))
|
if (!((c >= 0x21 && c <= 0x39) || (c >= 0x3B && c <= 0x7E)))
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserExists(Db * db, char *name)
|
UserExists(Db * db, char *name)
|
||||||
{
|
{
|
||||||
return DbExists(db, 2, "users", name);
|
return DbExists(db, 2, "users", name);
|
||||||
|
@ -130,7 +131,7 @@ UserAuthenticate(Db * db, char *accessToken)
|
||||||
|
|
||||||
char *userName;
|
char *userName;
|
||||||
char *deviceId;
|
char *deviceId;
|
||||||
UInt64 expires;
|
uint64_t expires;
|
||||||
|
|
||||||
if (!db || !accessToken)
|
if (!db || !accessToken)
|
||||||
{
|
{
|
||||||
|
@ -154,8 +155,7 @@ UserAuthenticate(Db * db, char *accessToken)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UInt64Neq(expires, UInt64Create(0, 0)) &&
|
if (expires && UtilTsMillis() >= expires)
|
||||||
UInt64Geq(UtilServerTs(), expires))
|
|
||||||
{
|
{
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
DbUnlock(db, atRef);
|
DbUnlock(db, atRef);
|
||||||
|
@ -168,14 +168,14 @@ UserAuthenticate(Db * db, char *accessToken)
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserUnlock(User * user)
|
UserUnlock(User * user)
|
||||||
{
|
{
|
||||||
int ret;
|
bool ret;
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Free(user->name);
|
Free(user->name);
|
||||||
|
@ -193,7 +193,7 @@ UserCreate(Db * db, char *name, char *password)
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
HashMap *json = NULL;
|
HashMap *json = NULL;
|
||||||
|
|
||||||
UInt64 ts = UtilServerTs();
|
uint64_t ts = UtilTsMillis();
|
||||||
|
|
||||||
/* TODO: Put some sort of password policy(like for example at least
|
/* TODO: Put some sort of password policy(like for example at least
|
||||||
* 8 chars, or maybe check it's entropy)? */
|
* 8 chars, or maybe check it's entropy)? */
|
||||||
|
@ -230,7 +230,7 @@ UserCreate(Db * db, char *name, char *password)
|
||||||
|
|
||||||
json = DbJson(user->ref);
|
json = DbJson(user->ref);
|
||||||
HashMapSet(json, "createdOn", JsonValueInteger(ts));
|
HashMapSet(json, "createdOn", JsonValueInteger(ts));
|
||||||
HashMapSet(json, "deactivated", JsonValueBoolean(0));
|
HashMapSet(json, "deactivated", JsonValueBoolean(false));
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
@ -353,7 +353,7 @@ UserGetDeviceId(User * user)
|
||||||
return user ? user->deviceId : NULL;
|
return user ? user->deviceId : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserCheckPassword(User * user, char *password)
|
UserCheckPassword(User * user, char *password)
|
||||||
{
|
{
|
||||||
HashMap *json;
|
HashMap *json;
|
||||||
|
@ -365,11 +365,11 @@ UserCheckPassword(User * user, char *password)
|
||||||
char *hashedPwd;
|
char *hashedPwd;
|
||||||
char *tmp;
|
char *tmp;
|
||||||
|
|
||||||
int result;
|
bool result;
|
||||||
|
|
||||||
if (!user || !password)
|
if (!user || !password)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
json = DbJson(user->ref);
|
json = DbJson(user->ref);
|
||||||
|
@ -379,12 +379,12 @@ UserCheckPassword(User * user, char *password)
|
||||||
|
|
||||||
if (!storedHash || !salt)
|
if (!storedHash || !salt)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp = StrConcat(2, password, salt);
|
tmp = StrConcat(2, password, salt);
|
||||||
hashBytes = Sha256(tmp);
|
hashBytes = Sha256(tmp);
|
||||||
hashedPwd = ShaToHex(hashBytes);
|
hashedPwd = ShaToHex(hashBytes, HASH_SHA256);
|
||||||
Free(tmp);
|
Free(tmp);
|
||||||
Free(hashBytes);
|
Free(hashBytes);
|
||||||
|
|
||||||
|
@ -395,7 +395,7 @@ UserCheckPassword(User * user, char *password)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserSetPassword(User * user, char *password)
|
UserSetPassword(User * user, char *password)
|
||||||
{
|
{
|
||||||
HashMap *json;
|
HashMap *json;
|
||||||
|
@ -407,7 +407,7 @@ UserSetPassword(User * user, char *password)
|
||||||
|
|
||||||
if (!user || !password)
|
if (!user || !password)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
json = DbJson(user->ref);
|
json = DbJson(user->ref);
|
||||||
|
@ -415,7 +415,7 @@ UserSetPassword(User * user, char *password)
|
||||||
salt = StrRandom(16);
|
salt = StrRandom(16);
|
||||||
tmpstr = StrConcat(2, password, salt);
|
tmpstr = StrConcat(2, password, salt);
|
||||||
hashBytes = Sha256(tmpstr);
|
hashBytes = Sha256(tmpstr);
|
||||||
hash = ShaToHex(hashBytes);
|
hash = ShaToHex(hashBytes, HASH_SHA256);
|
||||||
|
|
||||||
JsonValueFree(HashMapSet(json, "salt", JsonValueString(salt)));
|
JsonValueFree(HashMapSet(json, "salt", JsonValueString(salt)));
|
||||||
JsonValueFree(HashMapSet(json, "password", JsonValueString(hash)));
|
JsonValueFree(HashMapSet(json, "password", JsonValueString(hash)));
|
||||||
|
@ -425,10 +425,10 @@ UserSetPassword(User * user, char *password)
|
||||||
Free(hashBytes);
|
Free(hashBytes);
|
||||||
Free(tmpstr);
|
Free(tmpstr);
|
||||||
|
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserDeactivate(User * user, char * from, char * reason)
|
UserDeactivate(User * user, char * from, char * reason)
|
||||||
{
|
{
|
||||||
HashMap *json;
|
HashMap *json;
|
||||||
|
@ -436,7 +436,7 @@ UserDeactivate(User * user, char * from, char * reason)
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* By default, it's the target's username */
|
/* By default, it's the target's username */
|
||||||
|
@ -447,7 +447,7 @@ UserDeactivate(User * user, char * from, char * reason)
|
||||||
|
|
||||||
json = DbJson(user->ref);
|
json = DbJson(user->ref);
|
||||||
|
|
||||||
JsonValueFree(HashMapSet(json, "deactivated", JsonValueBoolean(1)));
|
JsonValueFree(HashMapSet(json, "deactivated", JsonValueBoolean(true)));
|
||||||
|
|
||||||
val = JsonValueString(from);
|
val = JsonValueString(from);
|
||||||
JsonValueFree(JsonSet(json, val, 2, "deactivate", "by"));
|
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"));
|
JsonValueFree(JsonSet(json, val, 2, "deactivate", "reason"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserReactivate(User * user)
|
UserReactivate(User * user)
|
||||||
{
|
{
|
||||||
HashMap *json;
|
HashMap *json;
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
json = DbJson(user->ref);
|
json = DbJson(user->ref);
|
||||||
|
|
||||||
|
|
||||||
JsonValueFree(HashMapSet(json, "deactivated", JsonValueBoolean(0)));
|
JsonValueFree(HashMapSet(json, "deactivated", JsonValueBoolean(false)));
|
||||||
|
|
||||||
JsonValueFree(HashMapDelete(json, "deactivate"));
|
JsonValueFree(HashMapDelete(json, "deactivate"));
|
||||||
|
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserDeactivated(User * user)
|
UserDeactivated(User * user)
|
||||||
{
|
{
|
||||||
HashMap *json;
|
HashMap *json;
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
json = DbJson(user->ref);
|
json = DbJson(user->ref);
|
||||||
|
@ -534,17 +534,17 @@ UserAccessTokenGenerate(User * user, char *deviceId, int withRefresh)
|
||||||
|
|
||||||
if (withRefresh)
|
if (withRefresh)
|
||||||
{
|
{
|
||||||
token->lifetime = Int64Create(0, 1000 * 60 * 60 * 24 * 7); /* 1 Week */
|
token->lifetime = 1000 * 60 * 60 * 24 * 7; /* 1 Week */
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
token->lifetime = Int64Create(0, 0);
|
token->lifetime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserAccessTokenSave(Db * db, UserAccessToken * token)
|
UserAccessTokenSave(Db * db, UserAccessToken * token)
|
||||||
{
|
{
|
||||||
DbRef *ref;
|
DbRef *ref;
|
||||||
|
@ -552,14 +552,14 @@ UserAccessTokenSave(Db * db, UserAccessToken * token)
|
||||||
|
|
||||||
if (!token)
|
if (!token)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref = DbCreate(db, 3, "tokens", "access", token->string);
|
ref = DbCreate(db, 3, "tokens", "access", token->string);
|
||||||
|
|
||||||
if (!ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
json = DbJson(ref);
|
json = DbJson(ref);
|
||||||
|
@ -567,9 +567,9 @@ UserAccessTokenSave(Db * db, UserAccessToken * token)
|
||||||
HashMapSet(json, "user", JsonValueString(token->user));
|
HashMapSet(json, "user", JsonValueString(token->user));
|
||||||
HashMapSet(json, "device", JsonValueString(token->deviceId));
|
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);
|
return DbUnlock(db, ref);
|
||||||
|
@ -589,7 +589,7 @@ UserAccessTokenFree(UserAccessToken * token)
|
||||||
Free(token);
|
Free(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserDeleteToken(User * user, char *token)
|
UserDeleteToken(User * user, char *token)
|
||||||
{
|
{
|
||||||
char *username;
|
char *username;
|
||||||
|
@ -607,14 +607,14 @@ UserDeleteToken(User * user, char *token)
|
||||||
|
|
||||||
if (!user || !token)
|
if (!user || !token)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
db = user->db;
|
db = user->db;
|
||||||
/* First check if the token even exists */
|
/* First check if the token even exists */
|
||||||
if (!DbExists(db, 3, "tokens", "access", token))
|
if (!DbExists(db, 3, "tokens", "access", token))
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If it does, get it's username. */
|
/* If it does, get it's username. */
|
||||||
|
@ -622,7 +622,7 @@ UserDeleteToken(User * user, char *token)
|
||||||
|
|
||||||
if (!tokenRef)
|
if (!tokenRef)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
tokenJson = DbJson(tokenRef);
|
tokenJson = DbJson(tokenRef);
|
||||||
username = JsonValueAsString(HashMapGet(tokenJson, "user"));
|
username = JsonValueAsString(HashMapGet(tokenJson, "user"));
|
||||||
|
@ -632,7 +632,7 @@ UserDeleteToken(User * user, char *token)
|
||||||
{
|
{
|
||||||
/* Token does not match user, do not delete it */
|
/* Token does not match user, do not delete it */
|
||||||
DbUnlock(db, tokenRef);
|
DbUnlock(db, tokenRef);
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
userJson = DbJson(user->ref);
|
userJson = DbJson(user->ref);
|
||||||
|
@ -640,7 +640,7 @@ UserDeleteToken(User * user, char *token)
|
||||||
|
|
||||||
if (!deviceObj)
|
if (!deviceObj)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Delete refresh token, if present */
|
/* Delete refresh token, if present */
|
||||||
|
@ -654,17 +654,17 @@ UserDeleteToken(User * user, char *token)
|
||||||
deletedVal = HashMapDelete(deviceObj, deviceId);
|
deletedVal = HashMapDelete(deviceObj, deviceId);
|
||||||
if (!deletedVal)
|
if (!deletedVal)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
JsonValueFree(deletedVal);
|
JsonValueFree(deletedVal);
|
||||||
|
|
||||||
/* Delete the access token. */
|
/* Delete the access token. */
|
||||||
if (!DbUnlock(db, tokenRef) || !DbDelete(db, 3, "tokens", "access", token))
|
if (!DbUnlock(db, tokenRef) || !DbDelete(db, 3, "tokens", "access", token))
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
|
@ -696,7 +696,7 @@ UserSetProfile(User * user, char *name, char *val)
|
||||||
JsonValueFree(JsonSet(json, JsonValueString(val), 2, "profile", name));
|
JsonValueFree(JsonSet(json, JsonValueString(val), 2, "profile", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserDeleteTokens(User * user, char *exempt)
|
UserDeleteTokens(User * user, char *exempt)
|
||||||
{
|
{
|
||||||
HashMap *devices;
|
HashMap *devices;
|
||||||
|
@ -705,13 +705,13 @@ UserDeleteTokens(User * user, char *exempt)
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
devices = JsonValueAsObject(HashMapGet(DbJson(user->ref), "devices"));
|
devices = JsonValueAsObject(HashMapGet(DbJson(user->ref), "devices"));
|
||||||
if (!devices)
|
if (!devices)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (HashMapIterate(devices, &deviceId, (void **) &deviceObj))
|
while (HashMapIterate(devices, &deviceId, (void **) &deviceObj))
|
||||||
|
@ -738,7 +738,7 @@ UserDeleteTokens(User * user, char *exempt)
|
||||||
JsonValueFree(HashMapDelete(devices, deviceId));
|
JsonValueFree(HashMapDelete(devices, deviceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -749,54 +749,50 @@ UserGetPrivileges(User * user)
|
||||||
return USER_NONE;
|
return USER_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserDecodePrivileges(HashMapGet(DbJson(user->ref), "privileges"));
|
return UserDecodePrivileges(JsonValueAsArray(HashMapGet(DbJson(user->ref), "privileges")));
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
bool
|
||||||
UserSetPrivileges(User * user, int privileges)
|
UserSetPrivileges(User * user, int privileges)
|
||||||
{
|
{
|
||||||
JsonValue *val;
|
JsonValue *val;
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!privileges)
|
if (!privileges)
|
||||||
{
|
{
|
||||||
JsonValueFree(HashMapDelete(DbJson(user->ref), "privileges"));
|
JsonValueFree(HashMapDelete(DbJson(user->ref), "privileges"));
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = UserEncodePrivileges(privileges);
|
val = JsonValueArray(UserEncodePrivileges(privileges));
|
||||||
if (!val)
|
if (!val)
|
||||||
{
|
{
|
||||||
return 0;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonValueFree(HashMapSet(DbJson(user->ref), "privileges", val));
|
JsonValueFree(HashMapSet(DbJson(user->ref), "privileges", val));
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
UserDecodePrivileges(JsonValue * val)
|
UserDecodePrivileges(Array * arr)
|
||||||
{
|
{
|
||||||
int privileges = USER_NONE;
|
int privileges = USER_NONE;
|
||||||
|
|
||||||
size_t i;
|
size_t i;
|
||||||
Array *arr;
|
|
||||||
|
|
||||||
if (!val)
|
if (!arr)
|
||||||
{
|
{
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JsonValueType(val) == JSON_ARRAY)
|
|
||||||
{
|
|
||||||
arr = JsonValueAsArray(val);
|
|
||||||
for (i = 0; i < ArraySize(arr); i++)
|
for (i = 0; i < ArraySize(arr); i++)
|
||||||
{
|
{
|
||||||
val = ArrayGet(arr, i);
|
JsonValue *val = ArrayGet(arr, i);
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
@ -804,7 +800,6 @@ UserDecodePrivileges(JsonValue * val)
|
||||||
|
|
||||||
privileges |= UserDecodePrivilege(JsonValueAsString(val));
|
privileges |= UserDecodePrivilege(JsonValueAsString(val));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
return privileges;
|
return privileges;
|
||||||
|
@ -851,7 +846,7 @@ UserDecodePrivilege(const char *p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonValue *
|
Array *
|
||||||
UserEncodePrivileges(int privileges)
|
UserEncodePrivileges(int privileges)
|
||||||
{
|
{
|
||||||
Array *arr = ArrayCreate();
|
Array *arr = ArrayCreate();
|
||||||
|
@ -883,13 +878,14 @@ UserEncodePrivileges(int privileges)
|
||||||
#undef A
|
#undef A
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
return JsonValueArray(arr);
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserId *
|
CommonID *
|
||||||
UserIdParse(char *id, char *defaultServer)
|
UserIdParse(char *id, char *defaultServer)
|
||||||
{
|
{
|
||||||
UserId *userId;
|
CommonID *userId;
|
||||||
|
char *server;
|
||||||
|
|
||||||
if (!id)
|
if (!id)
|
||||||
{
|
{
|
||||||
|
@ -902,48 +898,38 @@ UserIdParse(char *id, char *defaultServer)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
userId = Malloc(sizeof(UserId));
|
userId = Malloc(sizeof(CommonID));
|
||||||
if (!userId)
|
if (!userId)
|
||||||
{
|
{
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
memset(userId, 0, sizeof(CommonID));
|
||||||
|
|
||||||
/* Fully-qualified user ID */
|
/* Fully-qualified user ID */
|
||||||
if (*id == '@')
|
if (*id == '@')
|
||||||
{
|
{
|
||||||
char *localStart = id + 1;
|
if (!ParseCommonID(id, userId) || !userId->server.hostname)
|
||||||
char *serverStart = localStart;
|
|
||||||
|
|
||||||
while (*serverStart != ':' && *serverStart != '\0')
|
|
||||||
{
|
{
|
||||||
serverStart++;
|
UserIdFree(userId);
|
||||||
}
|
|
||||||
|
|
||||||
if (*serverStart == '\0')
|
|
||||||
{
|
|
||||||
Free(userId);
|
|
||||||
userId = NULL;
|
userId = NULL;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
*serverStart = '\0';
|
|
||||||
serverStart++;
|
|
||||||
|
|
||||||
userId->localpart = StrDuplicate(localStart);
|
|
||||||
userId->server = StrDuplicate(serverStart);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Treat it as just a localpart */
|
/* Treat it as just a localpart */
|
||||||
userId->localpart = StrDuplicate(id);
|
userId->local = StrDuplicate(id);
|
||||||
userId->server = StrDuplicate(defaultServer);
|
ParseServerPart(defaultServer, &userId->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!UserHistoricalValidate(userId->localpart, userId->server))
|
server = ParserRecomposeServerPart(userId->server);
|
||||||
|
if (!UserHistoricalValidate(userId->local, server))
|
||||||
{
|
{
|
||||||
UserIdFree(userId);
|
UserIdFree(userId);
|
||||||
userId = NULL;
|
userId = NULL;
|
||||||
}
|
}
|
||||||
|
Free(server);
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
Free(id);
|
Free(id);
|
||||||
|
@ -951,12 +937,11 @@ finish:
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
UserIdFree(UserId * id)
|
UserIdFree(CommonID * id)
|
||||||
{
|
{
|
||||||
if (id)
|
if (id)
|
||||||
{
|
{
|
||||||
Free(id->localpart);
|
CommonIDFree(*id);
|
||||||
Free(id->server);
|
|
||||||
Free(id);
|
Free(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -48,68 +49,12 @@
|
||||||
* .Xr telodendria-config 7 .
|
* .Xr telodendria-config 7 .
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <Schema/Config.h>
|
||||||
|
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Array.h>
|
#include <Cytoplasm/Array.h>
|
||||||
#include <Cytoplasm/Db.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
|
* Parse a JSON object, extracting the necessary values, validating
|
||||||
* them, and adding them to the configuration structure for use by the
|
* 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,
|
* 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.
|
* and if there is an error, it should display the error to the user.
|
||||||
*/
|
*/
|
||||||
extern Config * ConfigParse(HashMap *);
|
extern void ConfigParse(HashMap *, Config *);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 *);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether or not the configuration exists in the database,
|
* 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
|
* The return value of this function is the same as
|
||||||
* .Fn ConfigParse .
|
* .Fn ConfigParse .
|
||||||
*/
|
*/
|
||||||
extern Config * ConfigLock(Db *);
|
extern void ConfigLock(Db *, Config *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock the specified configuration, returning it back to the
|
* Unlock the specified configuration, returning it back to the
|
||||||
|
@ -160,4 +98,9 @@ extern Config * ConfigLock(Db *);
|
||||||
*/
|
*/
|
||||||
extern int ConfigUnlock(Config *);
|
extern int ConfigUnlock(Config *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a ConfigLogLevel into a valid syslog level.
|
||||||
|
*/
|
||||||
|
extern int ConfigLogLevelToSyslog(ConfigLogLevel);
|
||||||
|
|
||||||
#endif /* TELODENDRIA_CONFIG_H */
|
#endif /* TELODENDRIA_CONFIG_H */
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
108
src/include/Parser.h
Normal file
108
src/include/Parser.h
Normal 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 */
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -40,56 +41,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Cytoplasm/Db.h>
|
#include <Cytoplasm/Db.h>
|
||||||
#include <Cytoplasm/Int64.h>
|
|
||||||
|
|
||||||
/**
|
#include <Schema/RegToken.h>
|
||||||
* This structure describes a registration token that is in the
|
|
||||||
* database.
|
|
||||||
*/
|
|
||||||
typedef struct RegTokenInfo
|
|
||||||
{
|
|
||||||
Db *db;
|
|
||||||
DbRef *ref;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The token itself.
|
|
||||||
*/
|
|
||||||
char *name;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Who created this token. Note that this can be NULL if the
|
|
||||||
* token was created by Telodendria itself.
|
|
||||||
*/
|
|
||||||
char *owner;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* How many times the token was used.
|
|
||||||
*/
|
|
||||||
Int64 used;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* How many uses are allowed.
|
|
||||||
*/
|
|
||||||
Int64 uses;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Timestamp when this token was created.
|
|
||||||
*/
|
|
||||||
UInt64 created;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Timestamp when this token expires, or 0 if it does not
|
|
||||||
* expire.
|
|
||||||
*/
|
|
||||||
UInt64 expires;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A bit field describing the privileges this token grants. See
|
|
||||||
* the User API documentation for the privileges supported.
|
|
||||||
*/
|
|
||||||
int grants;
|
|
||||||
|
|
||||||
} RegTokenInfo;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ``Use'' the specified registration token by increasing the used
|
* ``Use'' the specified registration token by increasing the used
|
||||||
|
@ -124,7 +77,7 @@ extern RegTokenInfo * RegTokenGetInfo(Db *, char *);
|
||||||
* structure will be returned. Otherwise, NULL will be returned.
|
* structure will be returned. Otherwise, NULL will be returned.
|
||||||
*/
|
*/
|
||||||
extern RegTokenInfo *
|
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
|
* Free the memory associated with the registration token. This should
|
||||||
|
@ -132,7 +85,6 @@ RegTokenCreate(Db *, char *, char *, UInt64, Int64, int);
|
||||||
* .Fn RegTokenClose .
|
* .Fn RegTokenClose .
|
||||||
*/
|
*/
|
||||||
extern void RegTokenFree(RegTokenInfo *);
|
extern void RegTokenFree(RegTokenInfo *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a boolean value indicating whether or not the specified token
|
* Return a boolean value indicating whether or not the specified token
|
||||||
* is valid. A registration token is only valid if it has not expired
|
* is valid. A registration token is only valid if it has not expired
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -85,6 +86,7 @@ ROUTE(RouteChangePwd);
|
||||||
ROUTE(RouteDeactivate);
|
ROUTE(RouteDeactivate);
|
||||||
ROUTE(RouteTokenValid);
|
ROUTE(RouteTokenValid);
|
||||||
ROUTE(RouteUserProfile);
|
ROUTE(RouteUserProfile);
|
||||||
|
ROUTE(RouteUserDirectory);
|
||||||
ROUTE(RouteRequestToken);
|
ROUTE(RouteRequestToken);
|
||||||
|
|
||||||
ROUTE(RouteUiaFallback);
|
ROUTE(RouteUiaFallback);
|
||||||
|
@ -105,6 +107,8 @@ ROUTE(RouteRoomAliases);
|
||||||
|
|
||||||
ROUTE(RouteAdminDeactivate);
|
ROUTE(RouteAdminDeactivate);
|
||||||
|
|
||||||
|
ROUTE(RouteAdminTokens);
|
||||||
|
|
||||||
#undef ROUTE
|
#undef ROUTE
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -132,7 +133,7 @@ extern void UiaCleanup(MatrixHttpHandlerArgs *);
|
||||||
* the caller proceed with its logic.
|
* the caller proceed with its logic.
|
||||||
*/
|
*/
|
||||||
extern int
|
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
|
* Free an array of flows, as described above. Even though the caller
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
@ -38,10 +39,13 @@
|
||||||
* users, among many other tasks.
|
* users, among many other tasks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Cytoplasm/Int64.h>
|
|
||||||
#include <Cytoplasm/Db.h>
|
#include <Cytoplasm/Db.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
|
|
||||||
|
#include <Parser.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Many functions here operate on an opaque user structure.
|
* Many functions here operate on an opaque user structure.
|
||||||
*/
|
*/
|
||||||
|
@ -74,7 +78,7 @@ typedef struct UserAccessToken
|
||||||
char *user;
|
char *user;
|
||||||
char *string;
|
char *string;
|
||||||
char *deviceId;
|
char *deviceId;
|
||||||
Int64 lifetime;
|
uint64_t lifetime;
|
||||||
} UserAccessToken;
|
} UserAccessToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,15 +91,6 @@ typedef struct UserLoginInfo
|
||||||
char *refreshToken;
|
char *refreshToken;
|
||||||
} UserLoginInfo;
|
} 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
|
* Take a localpart and domain as separate parameters and validate them
|
||||||
* against the rules of the Matrix specification. The reasion the
|
* 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
|
* the local part is allowed to be. This function is used to ensure
|
||||||
* that client-provided Matrix IDs are valid on this server.
|
* 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
|
* 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
|
* spec compliant but remain in use since before the new restrictions
|
||||||
* were put in place.
|
* were put in place.
|
||||||
*/
|
*/
|
||||||
extern int UserHistoricalValidate(char *, char *);
|
extern bool UserHistoricalValidate(char *, char *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user identified by the specified localpart
|
* Determine whether the user identified by the specified localpart
|
||||||
* exists in the database.
|
* 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
|
* Create a new user with the specified localpart and password, in
|
||||||
|
@ -152,7 +147,7 @@ extern User * UserAuthenticate(Db *, char *);
|
||||||
* .Fn DbUnlock
|
* .Fn DbUnlock
|
||||||
* under the hood.
|
* under the hood.
|
||||||
*/
|
*/
|
||||||
extern int UserUnlock(User *);
|
extern bool UserUnlock(User *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log in a user. This function takes the user's password, desired
|
* 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
|
* does not store passwords in plain text, so this function hashes the
|
||||||
* password and checks it against what is stored in the database.
|
* 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
|
* Reset the given user's password by hashing a plain text password and
|
||||||
* storing it in the database.
|
* 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
|
* 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
|
* responsible for deactivating the target user is NULL, then it is
|
||||||
* set to the target's own name.
|
* 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
|
* Reactivates the given user account if it has been deactvated with
|
||||||
* .Fn UserDeactivate ,
|
* .Fn UserDeactivate ,
|
||||||
* otherwise, it simply doesn't do anything.
|
* 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
|
* Return a boolean value indicating whether or not the user was
|
||||||
* deactivated using
|
* deactivated using
|
||||||
* .Fn UserDeactivate .
|
* .Fn UserDeactivate .
|
||||||
*/
|
*/
|
||||||
extern int UserDeactivated(User *);
|
extern bool UserDeactivated(User *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the devices that belong to the user, in JSON format,
|
* 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
|
* Write the specified access token to the database, returning a
|
||||||
* boolean value indicating success.
|
* boolean value indicating success.
|
||||||
*/
|
*/
|
||||||
extern int UserAccessTokenSave(Db *, UserAccessToken *);
|
extern bool UserAccessTokenSave(Db *, UserAccessToken *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free the memory associated with the given access token.
|
* Free the memory associated with the given access token.
|
||||||
|
@ -249,7 +244,7 @@ extern void UserAccessTokenFree(UserAccessToken *);
|
||||||
/**
|
/**
|
||||||
* Delete a specific access token by name.
|
* 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
|
* 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
|
* except for the one provided by name, unless NULL is provided for
|
||||||
* the name.
|
* 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
|
* Get the current privileges of the user as a packed bit field. Use
|
||||||
|
@ -280,19 +275,19 @@ extern int UserGetPrivileges(User *);
|
||||||
/**
|
/**
|
||||||
* Set the privileges of the 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
|
* Decode the JSON that represents the user privileges into a packed
|
||||||
* bit field for simple manipulation.
|
* bit field for simple manipulation.
|
||||||
*/
|
*/
|
||||||
extern int UserDecodePrivileges(JsonValue *);
|
extern int UserDecodePrivileges(Array *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode the packed bit field that represents user privileges as a
|
* Encode the packed bit field that represents user privileges as a
|
||||||
* JSON value.
|
* JSON value.
|
||||||
*/
|
*/
|
||||||
extern JsonValue *UserEncodePrivileges(int);
|
extern Array *UserEncodePrivileges(int);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a string privilege into its bit in the bit field. This is
|
* Convert a string privilege into its bit in the bit field. This is
|
||||||
|
@ -302,15 +297,15 @@ extern JsonValue *UserEncodePrivileges(int);
|
||||||
extern int UserDecodePrivilege(const char *);
|
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
|
* first argument is a localpart, then the second argument is used as
|
||||||
* the server name.
|
* 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 */
|
#endif /* TELODENDRIA_USER_H */
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* obtaining a copy of this software and associated documentation files
|
||||||
|
|
|
@ -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
|
* Permission is hereby granted, free of charge, to any person
|
||||||
* obtaining a copy of this software and associated documentation files
|
* 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 expectArr = 0;
|
||||||
int func = 0;
|
int func = 0;
|
||||||
|
|
||||||
expectArr = (sscanf(key, "%127[^[][%lu]", keyName, &arrInd) == 2);
|
expectArr = (sscanf(key, "%127[^[][%zu]", keyName, &arrInd) == 2);
|
||||||
|
|
||||||
if (keyName[0] == '@')
|
if (keyName[0] == '@')
|
||||||
{
|
{
|
||||||
if (StrEquals(keyName + 1, "length"))
|
if (StrEquals(keyName + 1, "length"))
|
||||||
{
|
{
|
||||||
UInt64 len;
|
uint64_t len;
|
||||||
|
|
||||||
switch (JsonValueType(val))
|
switch (JsonValueType(val))
|
||||||
{
|
{
|
||||||
case JSON_ARRAY:
|
case JSON_ARRAY:
|
||||||
if (sizeof(size_t) == sizeof(UInt64))
|
len = ArraySize(JsonValueAsArray(val));
|
||||||
{
|
|
||||||
size_t slen = ArraySize(JsonValueAsArray(val));
|
|
||||||
UInt32 high = slen >> 32;
|
|
||||||
UInt32 low = slen;
|
|
||||||
|
|
||||||
len = UInt64Create(high, low);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
len = UInt64Create(0, ArraySize(JsonValueAsArray(val)));
|
|
||||||
}
|
|
||||||
|
|
||||||
val = JsonValueInteger(len);
|
val = JsonValueInteger(len);
|
||||||
ArrayAdd(cleanUp, val);
|
ArrayAdd(cleanUp, val);
|
||||||
break;
|
break;
|
||||||
case JSON_STRING:
|
case JSON_STRING:
|
||||||
if (sizeof(size_t) == sizeof(UInt64))
|
len = strlen(JsonValueAsString(val));
|
||||||
{
|
|
||||||
size_t slen = strlen(JsonValueAsString(val));
|
|
||||||
UInt32 high = slen >> 32;
|
|
||||||
UInt32 low = slen;
|
|
||||||
|
|
||||||
len = UInt64Create(high, low);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
len = UInt64Create(0, strlen(JsonValueAsString(val)));
|
|
||||||
}
|
|
||||||
|
|
||||||
val = JsonValueInteger(len);
|
val = JsonValueInteger(len);
|
||||||
ArrayAdd(cleanUp, val);
|
ArrayAdd(cleanUp, val);
|
||||||
break;
|
break;
|
||||||
|
@ -153,7 +130,7 @@ query(char *select, HashMap * json, int canonical)
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
if (sscanf(keyName + 1, "%lu", &i) == 1)
|
if (sscanf(keyName + 1, "%zu", &i) == 1)
|
||||||
{
|
{
|
||||||
JsonValueFree(ArrayDelete(JsonValueAsArray(val), i));
|
JsonValueFree(ArrayDelete(JsonValueAsArray(val), i));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue