Compare commits
337 Commits
Telodendri
...
master
Author | SHA1 | Date |
---|---|---|
lda | ff85b72899 | |
lda | bccbb3bcac | |
Jordan Bancino | dede82ad33 | |
Jordan Bancino | fde2b26857 | |
Jordan Bancino | 6305f5d76e | |
Jordan Bancino | 95a5f6f087 | |
Jordan Bancino | 129802fe94 | |
Jordan Bancino | c7d44866c3 | |
Jordan Bancino | 85672985eb | |
Jordan Bancino | 19443a1c24 | |
Jordan Bancino | 15fb6d8c2a | |
Jordan Bancino | cd22aea772 | |
Jordan Bancino | ae0724f01c | |
Jordan Bancino | e62389aa14 | |
Jordan Bancino | f2a4a64b27 | |
Jordan Bancino | 258f509413 | |
Jordan Bancino | 2e92daeb00 | |
Jordan Bancino | ac7ea4dec1 | |
Jordan Bancino | 54420f0036 | |
lda | 243de4f1a0 | |
Jordan Bancino | 35b9ef51f9 | |
Jordan Bancino | 83eb69f2cd | |
Jordan Bancino | 22d0e88dde | |
Jordan Bancino | f83be63d53 | |
lda | af4fd5dceb | |
lda | 2c6d5194d2 | |
Jordan Bancino | 0a91a0c40b | |
Jordan Bancino | d3dcf334f0 | |
lda | 3dae19e82d | |
Jordan Bancino | 2d30719be4 | |
Jordan Bancino | 503bfb6104 | |
Jordan Bancino | 77800e4117 | |
Jordan Bancino | db4ae0408c | |
Jordan Bancino | edee1288d8 | |
Jordan Bancino | 4b90800a2b | |
Jordan Bancino | 6e7f170768 | |
Jordan Bancino | 1f02f3c2a2 | |
Jordan Bancino | 582c79b608 | |
Jordan Bancino | 42a901b7f5 | |
Jordan Bancino | 1fee47a628 | |
Jordan Bancino | 6377689a83 | |
Jordan Bancino | 23237f97b5 | |
Jordan Bancino | 82ae2d4e41 | |
Jordan Bancino | 7d3d84d969 | |
Jordan Bancino | bf1ce839d0 | |
Jordan Bancino | f8d3e54fec | |
lda | cb41716bf3 | |
Jordan Bancino | 6247085df1 | |
lda | 0172fa083b | |
Jordan Bancino | 0b820b80f7 | |
Jordan Bancino | 6dcfa7dc02 | |
Jordan Bancino | 024482d4e8 | |
Jordan Bancino | 7a091c5b93 | |
Jordan Bancino | 9e8523d92e | |
Jordan Bancino | 419017bcc9 | |
Jordan Bancino | 1a009e87c3 | |
Jordan Bancino | 08594e0fb1 | |
Jordan Bancino | 56508afe4c | |
Jordan Bancino | ac3582ddeb | |
Jordan Bancino | 61f7ab1040 | |
Jordan Bancino | a672c05112 | |
Jordan Bancino | 09023089f5 | |
Jordan Bancino | 6d6fd1645c | |
Jordan Bancino | 043c2e9e33 | |
Jordan Bancino | 9e2f047e82 | |
Jordan Bancino | c78d075a93 | |
Jordan Bancino | e4a217550f | |
Jordan Bancino | d50372a91a | |
Jordan Bancino | fa3b5e95bd | |
Jordan Bancino | 7033a1f0b1 | |
Jordan Bancino | f6c54cbc7f | |
Jordan Bancino | 3cb04417ff | |
Jordan Bancino | 93e6582db5 | |
Jordan Bancino | ee62d31c68 | |
Jordan Bancino | 4bd527aa9a | |
Jordan Bancino | 79ce36c860 | |
Jordan Bancino | be3ee54bd8 | |
array-in-a-matrix | 5367ffca96 | |
Jordan Bancino | c5b2fcf586 | |
Jordan Bancino | 2b43a93524 | |
Jordan Bancino | fd28f97449 | |
Jordan Bancino | 745a208f14 | |
Jordan Bancino | 8843b34ba6 | |
Jordan Bancino | b059f966f1 | |
Jordan Bancino | 8bf8afd91d | |
lda | 6db3f3c612 | |
Jordan Bancino | fd1172ff56 | |
Jordan Bancino | 468656eee6 | |
Jordan Bancino | b625655439 | |
Jordan Bancino | dab666c969 | |
Jordan Bancino | 36413b4dca | |
Jordan Bancino | 1c32e18c74 | |
Jordan Bancino | 2382638005 | |
Jordan Bancino | 38a303da91 | |
Jordan Bancino | 2f76d5b9ae | |
Jordan Bancino | 93c4b6bfc4 | |
Jordan Bancino | d565640455 | |
Jordan Bancino | bc71a7ec01 | |
Jordan Bancino | 69d28f39d1 | |
Jordan Bancino | e0af88145e | |
Jordan Bancino | 6ef965d1e0 | |
Jordan Bancino | 28d9e1cb3b | |
Jordan Bancino | 78302d6320 | |
Jordan Bancino | 6ec87b8d76 | |
Jordan Bancino | 5f3220372e | |
Jordan Bancino | 77d71989df | |
Jordan Bancino | e9600a21e9 | |
Jordan Bancino | 942d2aad18 | |
Jordan Bancino | aeaa8487c3 | |
Jordan Bancino | ed37afe564 | |
Jordan Bancino | 2e193d4bcf | |
Jordan Bancino | d22baf440f | |
Jordan Bancino | c467d0913d | |
Jordan Bancino | 68b644a4f2 | |
Jordan Bancino | b65394ab50 | |
Jordan Bancino | a958c4a529 | |
Jordan Bancino | c96ac30f28 | |
Jordan Bancino | a4330123b9 | |
Jordan Bancino | b1c23ee53a | |
Jordan Bancino | bc8283f844 | |
Jordan Bancino | 5f34b846ee | |
Jordan Bancino | 3054a80906 | |
Jordan Bancino | 539fde773f | |
Jordan Bancino | 1fa07d2d3c | |
Jordan Bancino | 80da024e4e | |
Jordan Bancino | e3badbd55c | |
Jordan Bancino | 861d4146c0 | |
Jordan Bancino | f9e1250d47 | |
Jordan Bancino | 4e184102cb | |
Jordan Bancino | 8bda70b1fb | |
Jordan Bancino | 8f0d197480 | |
Jordan Bancino | b9641e89d6 | |
Jordan Bancino | 1381a31cbd | |
Jordan Bancino | c3287c1674 | |
Jordan Bancino | 071a86114c | |
Jordan Bancino | 1f14169284 | |
Jordan Bancino | 2f946848cb | |
Jordan Bancino | 2b3d0aaeaf | |
Jordan Bancino | af15234799 | |
Jordan Bancino | 971f099bb6 | |
Jordan Bancino | 96a1d3c3c4 | |
Jordan Bancino | 095e05e927 | |
Jordan Bancino | c511ca9f0f | |
Jordan Bancino | faaa12c51f | |
Jordan Bancino | 88f73a6131 | |
Jordan Bancino | c206ec495d | |
Jordan Bancino | 27e40135ad | |
Jordan Bancino | 5df684b609 | |
Jordan Bancino | b5b1a021d8 | |
Jordan Bancino | 54924b9444 | |
Jordan Bancino | 42526c95bb | |
Jordan Bancino | 34f33a1c1b | |
Jordan Bancino | d24c1161f6 | |
Jordan Bancino | d81600d944 | |
Jordan Bancino | efb27c9db8 | |
Jordan Bancino | 3b659ce09e | |
Jordan Bancino | 34eb9ff670 | |
Jordan Bancino | e87a0647e0 | |
Jordan Bancino | 836229fd1a | |
Jordan Bancino | 2693b89598 | |
Jordan Bancino | e22cf38eac | |
Jordan Bancino | b1049a9a70 | |
Jordan Bancino | 441599b088 | |
Jordan Bancino | 42191ec03f | |
Jordan Bancino | a4f369a0a9 | |
Jordan Bancino | 4cc876eb10 | |
Jordan Bancino | 51b9e2eaed | |
Jordan Bancino | e71ffec164 | |
Jordan Bancino | d38ec7cb38 | |
Jordan Bancino | 896f561213 | |
Jordan Bancino | f2f972bb9d | |
Jordan Bancino | ff0a9f33b8 | |
Jordan Bancino | 4043285413 | |
Jordan Bancino | 8021cff122 | |
Jordan Bancino | 5c8a42117c | |
Jordan Bancino | a1e3bd7d8e | |
Jordan Bancino | 4d9c907b58 | |
Jordan Bancino | c1c57fd4cf | |
Jordan Bancino | 609890654e | |
Jordan Bancino | 31866a14b4 | |
Jordan Bancino | e6dd20e2b2 | |
Jordan Bancino | ebc3da9b23 | |
Jordan Bancino | a2eec3946c | |
Jordan Bancino | 0b11b97022 | |
Jordan Bancino | 0e69a12784 | |
Jordan Bancino | 2a87583d2f | |
Jordan Bancino | 95ceba0645 | |
Jordan Bancino | bbea55be6c | |
Jordan Bancino | 3d9a7664b1 | |
Jordan Bancino | cb7ee91908 | |
Jordan Bancino | f1f66c6331 | |
Jordan Bancino | abbbfe4d7f | |
Jordan Bancino | 872fa1aa66 | |
Jordan Bancino | a08018870e | |
Jordan Bancino | e0c94d7bd2 | |
Jordan Bancino | f819093b7d | |
Jordan Bancino | 0b1b4a8b29 | |
Jordan Bancino | a3cc06ff2a | |
Jordan Bancino | 2d8d5244c4 | |
Jordan Bancino | b70c3f0bed | |
Jordan Bancino | 71fa96d10d | |
Jordan Bancino | a00ded6d06 | |
Jordan Bancino | 9292f1d9da | |
Jordan Bancino | 9880aac674 | |
Jordan Bancino | 24a03ba126 | |
Jordan Bancino | 95cb14213f | |
Jordan Bancino | 6e976a2b8d | |
Jordan Bancino | 72467f6503 | |
Jordan Bancino | 3b06ab120b | |
Jordan Bancino | 2447bb63cc | |
Jordan Bancino | fdcf7ec065 | |
Jordan Bancino | 459b2e856f | |
Jordan Bancino | 96ca9a725d | |
Jordan Bancino | e1367d5bff | |
Jordan Bancino | d83db35df0 | |
Jordan Bancino | d933d12e1b | |
Jordan Bancino | 098eed44a0 | |
Jordan Bancino | 2ac08ad74d | |
Jordan Bancino | 3e4698bf72 | |
Jordan Bancino | 38565d4aa6 | |
Jordan Bancino | 05cf076ebc | |
Jordan Bancino | a525830b64 | |
Jordan Bancino | 279f261aed | |
Jordan Bancino | 313249ca88 | |
Jordan Bancino | 737e060243 | |
Jordan Bancino | 5431c2cd90 | |
Jordan Bancino | 2bbe13aaf0 | |
Jordan Bancino | 866343071a | |
Jordan Bancino | 6a5d89e14b | |
Jordan Bancino | fb24f93aaa | |
Jordan Bancino | 0b7282c36a | |
Jordan Bancino | ec09882dbe | |
Jordan Bancino | 3b28af2031 | |
Jordan Bancino | 19b0dcac5a | |
Jordan Bancino | 4a5c7480aa | |
Jordan Bancino | 687b89a83a | |
Jordan Bancino | 25b7c0d059 | |
Jordan Bancino | ffeb45375e | |
Jordan Bancino | ae38791df2 | |
Jordan Bancino | 0cca38115a | |
Jordan Bancino | ff4d265dcc | |
Jordan Bancino | 582df63a31 | |
Jordan Bancino | 4ee66ae3c7 | |
Jordan Bancino | 768ecda41a | |
Jordan Bancino | 6ca1265076 | |
Jordan Bancino | e882693c78 | |
Jordan Bancino | b21d018daa | |
Jordan Bancino | 83971dfaff | |
Jordan Bancino | a90f7c4b9e | |
Jordan Bancino | 3192063340 | |
Jordan Bancino | 1f8df737da | |
Jordan Bancino | 7c865d06fd | |
Jordan Bancino | e0c8530b12 | |
Jordan Bancino | e592840c99 | |
Jordan Bancino | 7b3d537175 | |
Jordan Bancino | f341fd2b6e | |
Jordan Bancino | eef615fc9a | |
Jordan Bancino | 9b21e2460a | |
Jordan Bancino | c6f4a4a546 | |
Jordan Bancino | a9da9fbca7 | |
Jordan Bancino | c37d3801b2 | |
Jordan Bancino | a24c27bf4f | |
Jordan Bancino | af776c64a7 | |
Jordan Bancino | a25573063f | |
Jordan Bancino | aeb49f80e5 | |
Jordan Bancino | 007e639b0c | |
Jordan Bancino | fe32c652cd | |
Jordan Bancino | 20d41d794b | |
Jordan Bancino | b4e4263cea | |
Jordan Bancino | e13442c122 | |
Jordan Bancino | 2441f07848 | |
Jordan Bancino | 2fab7b55fe | |
Jordan Bancino | 089d8d4d94 | |
Jordan Bancino | 9ec330f40a | |
Jordan Bancino | fccd15b239 | |
Jordan Bancino | 35f65a667d | |
Jordan Bancino | 8faf6f2126 | |
Jordan Bancino | fc975e6a93 | |
Jordan Bancino | 413c7ad803 | |
Jordan Bancino | e30fa3ee33 | |
Jordan Bancino | e6f3dfad18 | |
Jordan Bancino | b8d00bc8bf | |
Jordan Bancino | f3c4c0ac65 | |
Jordan Bancino | 8782aa046d | |
Jordan Bancino | bdaea9872e | |
Jordan Bancino | b58ca7d22e | |
Jordan Bancino | 6561b5bae1 | |
Jordan Bancino | 996356832e | |
Jordan Bancino | d1b4ecff48 | |
Jordan Bancino | 63d07365db | |
Jordan Bancino | 2a92d0de7e | |
Jordan Bancino | 4a27f50538 | |
Jordan Bancino | 6c9e939b9f | |
Jordan Bancino | 5289c16e2b | |
Jordan Bancino | a97a593f21 | |
Jordan Bancino | 27b3b6cdc6 | |
Jordan Bancino | 8539a03d5b | |
Jordan Bancino | e0a3760a37 | |
Jordan Bancino | 6ee1857f5f | |
Jordan Bancino | 7d9770fc12 | |
Jordan Bancino | 65f4c90df3 | |
Jordan Bancino | ff52cc78dc | |
Jordan Bancino | ab4755240a | |
Jordan Bancino | 92da3542a6 | |
Jordan Bancino | 5dbaf3c223 | |
Jordan Bancino | afc7667737 | |
Jordan Bancino | ae97d8116c | |
Jordan Bancino | 76bfa120ee | |
Jordan Bancino | 62cd1cdc98 | |
Jordan Bancino | 7fa982a16f | |
Jordan Bancino | aba1ef9251 | |
Jordan Bancino | b4a394c44b | |
Jordan Bancino | 64add9c9ab | |
Jordan Bancino | 3037f12907 | |
Jordan Bancino | 2d9b706f38 | |
Jordan Bancino | fd12dee62e | |
Jordan Bancino | 8ead9cc93a | |
Jordan Bancino | 3af2d3d12b | |
Jordan Bancino | 7344d4fa46 | |
Jordan Bancino | ca053a12b1 | |
Jordan Bancino | 9a1300ff2e | |
Jordan Bancino | c32c3abfd6 | |
Jordan Bancino | 012c334ee5 | |
Jordan Bancino | 20ebeb9c32 | |
Jordan Bancino | 76413f834e | |
Jordan Bancino | efdf168085 | |
Jordan Bancino | 50e599f1cd | |
Jordan Bancino | 7b22fb02a2 | |
Jordan Bancino | cb8c4fceb5 | |
Jordan Bancino | 19e89110cd | |
Jordan Bancino | 1a169d1a2e | |
Jordan Bancino | 8d75d8a023 | |
Jordan Bancino | 7e144ae488 | |
Jordan Bancino | 3e42da279c | |
Jordan Bancino | 313f0e2e73 | |
Jordan Bancino | fc8fbc9a70 | |
Jordan Bancino | 7e41251f07 |
|
@ -1,6 +0,0 @@
|
|||
build
|
||||
data
|
||||
.env
|
||||
*.patch
|
||||
*.log
|
||||
vgcore.*
|
|
@ -0,0 +1,49 @@
|
|||
name: Bug Report
|
||||
about: File a bug report regarding Telodendria, its website, or its documentation.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
You are submitting a bug report. Please be sure to fill out the
|
||||
title with a brief description of the bug.
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
description: Select the type of issue.
|
||||
options:
|
||||
- Memory Leak
|
||||
- Crash
|
||||
- Unexpected Error Message
|
||||
- Documentation
|
||||
- Website
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please give a thorough and detailed description of the bug you
|
||||
are reporting. Provide all the information you have, and do
|
||||
some investigating to ensure you are providing a legitimate
|
||||
issue report that is well thought out. **Include details on
|
||||
how to reproduce the issue, or explicitly state that you were
|
||||
unable to reproduce it.**
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Log Output
|
||||
description: |
|
||||
Please copy and paste the relevant sections of the log output,
|
||||
or the entire log if it is not unreasonably large. The logs
|
||||
will be automatically formatted, no code block is necessary.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please attach any additional files that may aid in our
|
||||
investigation of this issue, including screenshots, debugging
|
||||
session stack traces and dumps, etc.
|
|
@ -0,0 +1,7 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: General Matrix Room
|
||||
url: "https://matrix.to/#/#telodendria-general:bancino.net"
|
||||
about: |
|
||||
General discussion on Telodendria happens in this Matrix room. You
|
||||
may get quicker feedback from there.
|
|
@ -0,0 +1,18 @@
|
|||
name: Feature Request
|
||||
about: Request a new feature or enhancement be added to Telodendria.
|
||||
title: "[Feature] "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
You are submitting a feature request. Please be sure to fill
|
||||
out the title with a brief description of the feature you are
|
||||
requesting.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please give a thorough and detailed description of the feature
|
||||
you are requesting.
|
||||
validations:
|
||||
required: true
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
---
|
||||
|
||||
Please review the developer certificate of origin:
|
||||
|
||||
1. The contribution was created in whole or in part by me, and I have
|
||||
the right to submit it under the open source licenses of the
|
||||
Telodendria project; or
|
||||
1. The contribution is based upon a previous work that, to the best of
|
||||
my knowledge, is covered under an appropriate open source license and
|
||||
I have the right under that license to submit that work with
|
||||
modifications, whether created in whole or in part by me, under the
|
||||
Telodendria project license; or
|
||||
1. The contribution was provided directly to me by some other person
|
||||
who certified (1), (2), or (3), and I have not modified it.
|
||||
1. I understand and agree that this project and the contribution are
|
||||
made public and that a record of the contribution—including all
|
||||
personal information I submit with it—is maintained indefinitely
|
||||
and may be redistributed consistent with this project or the open
|
||||
source licenses involved.
|
||||
|
||||
- [ ] I have read the Telodendria Project development certificate of
|
||||
origin, and I certify that I have permission to submit this patch
|
||||
under the conditions specified in it.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
name: Compile Telodendria
|
||||
run-name: Compile Telodendria on ${{ gitea.actor }}
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
"Compile Telodendria":
|
||||
strategy:
|
||||
matrix:
|
||||
os: [debian-v12.4, alpine-v3.19, openbsd-v7.4, freebsd-v14.0, netbsd-v9.3]
|
||||
arch: [x86, x86_64]
|
||||
exclude:
|
||||
# 32-bit OpenBSD does not behave well in QEMU. Even when using
|
||||
# QEMU to emulate i386, it utilizes 100% of its CPU core and is
|
||||
# still extremely sluggish. Thus, we don't have a working 32-bit
|
||||
# OpenBSD runner, so exclude it from the matrix configuration.
|
||||
- os: openbsd-v7.4
|
||||
arch: x86
|
||||
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Configure Telodendria
|
||||
run: ./configure
|
||||
- name: Configure & Build Cytoplasm
|
||||
run: make cytoplasm
|
||||
- name: Build Telodendria
|
||||
run: make
|
|
@ -0,0 +1,18 @@
|
|||
# Telodendria .gitignore
|
||||
|
||||
build
|
||||
out
|
||||
data
|
||||
Makefile
|
||||
|
||||
*-leaked.txt
|
||||
.env
|
||||
*.patch
|
||||
*.orig
|
||||
*.log
|
||||
vgcore.*
|
||||
*.core
|
||||
contrib/.vagrant
|
||||
src/Schema
|
||||
src/include/Schema
|
||||
man/mandoc.db
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "Cytoplasm"]
|
||||
path = Cytoplasm
|
||||
url = https://git.telodendria.io/Telodendria/Cytoplasm.git
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 5d87da31cda74e6808eebca72e9475aabde86532
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
|
136
README.md
136
README.md
|
@ -1,7 +1,135 @@
|
|||
# [Telodendria](https://telodendria.io)
|
||||
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
|
||||
|
||||
**Telodendria** is an open source Matrix homeserver implementation written from scratch in ANSI C and designed to be lightweight and simple, yet functional.
|
||||
**Telodendria** is an extremely powerful, yet lightweight and portable
|
||||
chat server designed to be easy to install and configure. Powered by
|
||||
the [Matrix](https://matrix.org) protocol, Telodendria empowers
|
||||
everyone to run their own chat server on ordinary hardware, including
|
||||
old and embedded devices. Whether you want a simple chat server just
|
||||
for you and your friends and family, or want to talk to users on other
|
||||
Matrix homeservers but don't want to go through all the hastle of
|
||||
hosting a complicated, high-maintenance homeserver or joining an
|
||||
existing homeserver for privacy or other reasons, then Telodendria
|
||||
might be for you.
|
||||
|
||||
**Important:** This project is not developed on GitHub, or even with Git. As such, GitHub Pull Requests are not accepted. But that doesn't mean we don't want your contribution! You're more than welcome to clone this repo and use Git to make changes to the project if you'd prefer it to CVS, but when it comes time to actually submit your changes to this project, use [git format-patch](https://git-scm.com/docs/git-format-patch) to generate patch files, then submit them to the official Matrix room: [#telodendria-patches:bancino.net](https://matrix.to/#/#telodendria-patches:bancino.net).
|
||||
!!!! **Note:** Telodendria still in development. See **Status** below.
|
||||
|
||||
Please see the `README.txt` file for the actual project `README`, which simply details the repository structure a little bit. All of **Telodendria**'s user and developer documentation is available as `man` pages, or online.
|
||||
## What is Matrix?
|
||||
|
||||
Matrix is an **open standard** for *interoperable*, *decentralized*,
|
||||
*secure*, and *real-time* communication over the internet.
|
||||
|
||||
Matrix can be thought of as the successor to email, but it works
|
||||
very similar to iMessage, Discord, or direct messaging on most
|
||||
social media networks. The primary difference between Matrix and these
|
||||
other services, however, is that Matrix doesn't depend on one central
|
||||
authority, and is designed in such a way to respect your privacy.
|
||||
Matrix has proven itself over the last few
|
||||
years to be a reliable communication tool, and has only gotten more
|
||||
user-friendly over the course of its development. Matrix is capable
|
||||
enough that it can—and should—totally replace any other
|
||||
means of digital communication, and it offers a much higher degree
|
||||
of security, simplicity, and functionality.
|
||||
|
||||
Strictly speaking, Matrix itself is just the *protocol* by which
|
||||
clients and servers communicate. In order to use Matrix, we need
|
||||
implementations of both clients and servers. Telodendria is a server
|
||||
implementation of the Matrix protocol.
|
||||
|
||||
## Why Telodendria?
|
||||
|
||||
- **Lightweight:** Written in the C programming language, Telodendria
|
||||
is automatically lighter and faster than other self-hosted chat servers.
|
||||
It has very few external dependencies and is as self-contained as
|
||||
possible.
|
||||
- **Fully-Featured:** Most lightweight chat solutions compromise on
|
||||
features. Telodendria is built on the fully-featured Matrix protocol,
|
||||
which provides a chat experience that most normal users are familiar
|
||||
with.
|
||||
- **Portable:** You can run Telodendria on just about everything,
|
||||
including more traditional options like a personal home server or VPS,
|
||||
but also more obscure platforms like Raspberry Pis or retro computers.
|
||||
Telodendria can run on a broad number of operating systems, which means
|
||||
that no matter which platform and OS you prefer, there is a good chance
|
||||
you can add Telodendria without much difficulty. It is also extremely
|
||||
easy to migrate a Telodendria instance between platforms; just copy the
|
||||
data directory to a new device.
|
||||
- **Simple:** Telodendria is designed to be a simple, no-frills
|
||||
chat server. It is easy to install, easy to configure, and easy to
|
||||
maintain.
|
||||
- **Stable:** Other Matrix homeservers develop at the pace of the
|
||||
Matrix specification itself, which is to say quite rapidly. Changes are
|
||||
always being made, and a version shipped 6 months ago is already
|
||||
incredibly outdated. Telodendria, on the other hand, aims to be stable.
|
||||
It should *just work* for long periods of time between upgrades, and
|
||||
you should never feel like Telodendria is going to change significantly
|
||||
between upgrades.
|
||||
- **Well-Documented:** Telodendria places as much emphasis on documentation as on code, which means you can be sure that the documentation will always remain up-to-date, accurate, and most importantly, reasonably exhaustive.
|
||||
|
||||
[Read Technical Rationale →](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/dev/rationale.md)
|
||||
|
||||
## Get Started
|
||||
|
||||
Check out the [Documentation](https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md) to get started with
|
||||
Telodendria.
|
||||
|
||||
## Status
|
||||
|
||||
Telodendria is in the very early stages of development. As such, it may
|
||||
not yet deliver on all of its promises. Currently, Telodendria is not
|
||||
ready for end-users yet. While it features very basic user
|
||||
authentication, it does not actually work as a chat server yet.
|
||||
|
||||
We are hoping to ship Telodendria `v1.7.0-alpha4` by January of 2025. This
|
||||
release should be usable for communication between **local users**
|
||||
only. Additional features, including federation with other Matrix
|
||||
homeservers will be added in future releases.
|
||||
|
||||
You can help speed up development by **sponsoring**
|
||||
Telodendria or [getting involved](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/CONTRIBUTING.md).
|
||||
|
||||
## Sponsorship
|
||||
|
||||
Telodendria is maintained by a loosely-knit band of volunteers. The
|
||||
project currently has no sponsors and thus no source of income to
|
||||
pay for infrastructure costs and developer time. To ensure
|
||||
Telodendria's long-term success, please consider sponsoring the
|
||||
project.
|
||||
|
||||
You can make a recurring donation to Telodendria using
|
||||
[LiberaPay](https://liberapay.com/Telodendria/donate). You can also make
|
||||
one-time donations using
|
||||
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
|
||||
like to make a recurring donation larger than that allowed by
|
||||
LiberaPay, please contact Jordan Bancino over Matrix at
|
||||
`@jordan:bancino.net` or email at `jordan@bancino.net`.
|
||||
|
||||
### Benefits
|
||||
|
||||
While there are no set sponsorship tiers at this time, sponsoring
|
||||
Telodendria is a mutually beneficial relationship. Depending on the
|
||||
amount you donate, you can get your name, logo, and website links
|
||||
on the [Sponsors](../sponsors) page, the project `README`, or the
|
||||
main website.
|
||||
|
||||
## License
|
||||
|
||||
All of the code and documentation for Telodendria is licensed under a
|
||||
modified MIT license. The MIT license is an extremely permissive
|
||||
license that has very few restrictions. Please consult the
|
||||
[`LICENSE.txt`](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/LICENSE.txt) file for the actual license text. It is
|
||||
important to note that the Telodendria license text differs from the
|
||||
original MIT license in the following ways:
|
||||
|
||||
- Where the MIT license states that the copyright notice and permission
|
||||
notice shall be included in all copies or *substantial* portions of the
|
||||
software, the Telodendria requires the copyright notice and
|
||||
permission notice be included with *all* portions, regardless of the
|
||||
size, by omitting the word *substantial*.
|
||||
|
||||
The Telodendria logo in all its forms, including the ASCII
|
||||
representation, belongs solely to the Telodendria project. It must be
|
||||
used only to represent the official Telodendria project. You are free
|
||||
to use the logo in any way as long as it represents or links to the
|
||||
official project. If Telodendria is forked, the logo must be removed
|
||||
completely from the project, and optionally replaced by a different
|
||||
one.
|
||||
|
|
63
README.txt
63
README.txt
|
@ -1,63 +0,0 @@
|
|||
.= -=-
|
||||
:.:+ .=:.
|
||||
.=+-==. :.
|
||||
.+- =.
|
||||
.+ :+.
|
||||
==. -+:
|
||||
=++==--:: =+.
|
||||
.:::--=+=: :+=
|
||||
:==. -=:
|
||||
===----=-. ... :+.
|
||||
:==+=======: .-+-::-+-=+=
|
||||
.==*%#======= :+- ..
|
||||
.:--=-===+=========-. :+:
|
||||
.=++=::..:============-+=-=-
|
||||
:+=: :=+-: .-=========-. .
|
||||
=+++: .:=+-: .:--. .--:==:
|
||||
::---:.. :=+: ==
|
||||
++. .+-
|
||||
=+ .+- ...:
|
||||
+- -+-:-+=::+:
|
||||
:=-....:-=: .--: =-
|
||||
-++=:.:::..
|
||||
|
||||
=======================================================
|
||||
|_ _|__| | ___ __| | ___ _ __ __| |_ __(_) __ _
|
||||
| |/ _ \ |/ _ \ / _` |/ _ \ '_ \ / _` | '__| |/ _` |
|
||||
| | __/ | (_) | (_| | __/ | | | (_| | | | | (_| |
|
||||
|_|\___|_|\___/ \__,_|\___|_| |_|\__,_|_| |_|\__,_|
|
||||
=======================================================
|
||||
Copyright (C) 2023 Jordan Bancino <@jordan:bancino.net>
|
||||
|
||||
This is the source code for Telodendria, a Matrix homeserver written
|
||||
in C. All of the documentation is available as man pages in the
|
||||
man/ directory, or online at https://telodendria.io
|
||||
|
||||
If information is missing from the documentation, please feel free
|
||||
to reach out to #telodendria-general:bancino.net on Matrix.
|
||||
|
||||
This file documents the directory structure of the source code
|
||||
repository.
|
||||
|
||||
Telodendria/
|
||||
contrib/ - Supplemental files, such as example configs.
|
||||
man/ - The official documentation as man pages.
|
||||
proposals/ - Proposals for new features or fixes, as man pages.
|
||||
site/ - The official website.
|
||||
src/ - The C source code for Telodendria.
|
||||
include/ - Header files for the source code.
|
||||
Routes/ - Where Matrix API endpoints are implemented.
|
||||
Static/ - Endpoints that just generate static HTML pages.
|
||||
tests/ - Unit and integration tests will eventually go here.
|
||||
tools/ - Development environment and tools.
|
||||
|
||||
To cut a new release for Telodendria, perform the following
|
||||
steps. This is just a reference for me so I don't mess it up.
|
||||
|
||||
- Update tools/bin/td to declare the next version number.
|
||||
- Make sure man/man7/telodendria-changelog.7 is up to date.
|
||||
with the latest information.
|
||||
- Commit all changes.
|
||||
- Run the release recipe: td release
|
||||
- Deploy the site: td site
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_CLIENTEVENT_H",
|
||||
"header": "Schema/ClientEvent.h",
|
||||
"types": {
|
||||
"ClientEventUnsignedData": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"prev_content": {
|
||||
"type": "object"
|
||||
},
|
||||
"redacted_because": {
|
||||
"type": "object"
|
||||
},
|
||||
"transaction_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ClientEvent": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"content": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"event_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"origin_server_ts": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"room_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"sender": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"_unsigned": {
|
||||
"type": "ClientEventUnsignedData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_FILTER_H",
|
||||
"header": "Schema\/Filter.h",
|
||||
"types": {
|
||||
"FilterRoom": {
|
||||
"fields": {
|
||||
"not_rooms": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"state": {
|
||||
"type": "FilterRoomEvent"
|
||||
},
|
||||
"include_leave": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"timeline": {
|
||||
"type": "FilterRoomEvent"
|
||||
},
|
||||
"account_data": {
|
||||
"type": "FilterRoomEvent"
|
||||
},
|
||||
"rooms": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"ephemeral": {
|
||||
"type": "FilterRoomEvent"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"FilterEventFormat": {
|
||||
"fields": {
|
||||
"federation": {
|
||||
"name": "FILTER_FORMAT_FEDERATION"
|
||||
},
|
||||
"client": {
|
||||
"name": "FILTER_FORMANT_CLIENT"
|
||||
}
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"FilterEvent": {
|
||||
"fields": {
|
||||
"not_senders": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"senders": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"types": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"not_types": {
|
||||
"type": "[string]"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"Filter": {
|
||||
"fields": {
|
||||
"event_format": {
|
||||
"type": "FilterEventFormat"
|
||||
},
|
||||
"presence": {
|
||||
"type": "FilterEvent"
|
||||
},
|
||||
"account_data": {
|
||||
"type": "FilterEvent"
|
||||
},
|
||||
"room": {
|
||||
"type": "FilterRoom"
|
||||
},
|
||||
"event_fields": {
|
||||
"type": "[string]"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"FilterRoomEvent": {
|
||||
"fields": {
|
||||
"not_rooms": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"not_senders": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"senders": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"include_redundant_members": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"types": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"rooms": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"lazy_load_members": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"not_types": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"contains_url": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"unread_thread_notifications": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_PDUV1_H",
|
||||
"header": "Schema/PduV1.h",
|
||||
"types": {
|
||||
"PduV1EventHash": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"sha256": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"PduV1UnsignedData": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PduV1": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"auth_events": {
|
||||
"type": "array",
|
||||
"required": true
|
||||
},
|
||||
"content": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"depth": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"event_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"hashes": {
|
||||
"type": "PduV1EventHash",
|
||||
"required": true
|
||||
},
|
||||
"origin_server_ts": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"prev_events": {
|
||||
"type": "array",
|
||||
"required": true
|
||||
},
|
||||
"redacts": {
|
||||
"type": "string"
|
||||
},
|
||||
"room_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"sender": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"signatures": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"_unsigned": {
|
||||
"type": "PduV1UnsignedData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_PDUV3_H",
|
||||
"header": "Schema/PduV3.h",
|
||||
"types": {
|
||||
"PduV3EventHash": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"sha256": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"PduV3UnsignedData": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PduV3": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"auth_events": {
|
||||
"type": "[string]",
|
||||
"required": true
|
||||
},
|
||||
"content": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"depth": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"hashes": {
|
||||
"type": "PduV3EventHash",
|
||||
"required": true
|
||||
},
|
||||
"origin_server_ts": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"prev_events": {
|
||||
"type": "[string]",
|
||||
"required": true
|
||||
},
|
||||
"redacts": {
|
||||
"type": "string"
|
||||
},
|
||||
"room_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"sender": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"signatures": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"_unsigned": {
|
||||
"type": "PduV3UnsignedData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_ROOMCREATE_H",
|
||||
"header": "Schema/RoomCreateRequest.h",
|
||||
"types": {
|
||||
"RoomVisibility": {
|
||||
"fields": {
|
||||
"public": {
|
||||
"name": "ROOM_PUBLIC"
|
||||
},
|
||||
"private": {
|
||||
"name": "ROOM_PRIVATE"
|
||||
}
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"RoomCreateRequest": {
|
||||
"fields": {
|
||||
"invite": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"room_version": {
|
||||
"type": "string"
|
||||
},
|
||||
"invite_3pid": {
|
||||
"type": "[RoomInvite3Pid]"
|
||||
},
|
||||
"topic": {
|
||||
"type": "string"
|
||||
},
|
||||
"visibility": {
|
||||
"type": "RoomVisibility"
|
||||
},
|
||||
"creation_content": {
|
||||
"type": "object"
|
||||
},
|
||||
"is_direct": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"initial_state": {
|
||||
"type": "[RoomStateEvent]"
|
||||
},
|
||||
"power_level_content_override": {
|
||||
"type": "object"
|
||||
},
|
||||
"room_alias_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"preset": {
|
||||
"type": "RoomCreatePreset"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"RoomInvite3Pid": {
|
||||
"fields": {
|
||||
"id_access_token": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"address": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"medium": {
|
||||
"required": true,
|
||||
"type": "Room3PidMedium"
|
||||
},
|
||||
"id_server": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"Room3PidMedium": {
|
||||
"fields": {
|
||||
"msisdn": {
|
||||
"name": "ROOM_3PID_MSISDN"
|
||||
},
|
||||
"email": {
|
||||
"name": "ROOM_3PID_EMAIL"
|
||||
}
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"RoomCreatePreset": {
|
||||
"fields": {
|
||||
"public_chat": {
|
||||
"name": "ROOM_CREATE_PUBLIC"
|
||||
},
|
||||
"trusted_private_chat": {
|
||||
"name": "ROOM_CREATE_TRUSTED"
|
||||
},
|
||||
"private_chat": {
|
||||
"name": "ROOM_CREATE_PRIVATE"
|
||||
}
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"RoomStateEvent": {
|
||||
"fields": {
|
||||
"content": {
|
||||
"required": true,
|
||||
"type": "object"
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
}
|
||||
}
|
91
TODO.txt
91
TODO.txt
|
@ -1,91 +0,0 @@
|
|||
Telodendria To-Do List
|
||||
======================
|
||||
|
||||
Key:
|
||||
|
||||
[ ] Not Started
|
||||
[x] Done
|
||||
[~] In Progress
|
||||
[!] Won't Fix
|
||||
|
||||
Milestone: v0.3.0
|
||||
-----------------
|
||||
|
||||
[ ] Stream API
|
||||
[ ] TLS
|
||||
[ ] SOCKS
|
||||
[ ] Multi-output
|
||||
[ ] HTTP Client API
|
||||
[ ] Option to pretty-print Json
|
||||
|
||||
[ ] Simple command line tool to make matrix requests
|
||||
- Built on HTTP client API
|
||||
[ ] Simple command line tool for working with JSON
|
||||
- Like a simpler version of jq
|
||||
- Should pretty-print Json
|
||||
- Should be able to query fields for use in shell scripts.
|
||||
|
||||
[ ] Move configuration to database
|
||||
[ ] Initial configuration
|
||||
[ ] If no config, create one-time use registration token that
|
||||
grants user admin privileges.
|
||||
[ ] /_telodendria/admin/config endpoint
|
||||
[ ] Refactor TelodendriaConfig to just Config (ConfigLock() and ConfigUnlock())
|
||||
|
||||
[ ] Client-Server API
|
||||
[ ] 4: Token-based user registration
|
||||
[ ] Implement user-interactive auth flow
|
||||
[ ] Token validity endpoint
|
||||
[ ] Add m.login.registration_token to registration endpoint
|
||||
flow
|
||||
- Ensure that registration tokens can be used even if
|
||||
registration is disabled.
|
||||
[ ] 4: Account management
|
||||
[ ] Deactivate
|
||||
[ ] Make sure UserLogin() fails if user is deactivated.
|
||||
[ ] Change password
|
||||
[ ] Whoami
|
||||
[ ] 9: User Data
|
||||
[ ] 5: Capabilities negotiation
|
||||
[ ] 10: Security (Rate Limiting)
|
||||
|
||||
Milestone: v0.4.0
|
||||
-----------------
|
||||
|
||||
[ ] Client-Server API
|
||||
[ ] 6: Filtering
|
||||
[ ] 7: Events
|
||||
[ ] 8: Rooms
|
||||
|
||||
Milestone: v0.5.0
|
||||
-----------------
|
||||
|
||||
[~] Client-Server API
|
||||
[ ] Modules
|
||||
[ ] Content Repository
|
||||
|
||||
Milestone: v1.0.0
|
||||
-----------------
|
||||
|
||||
[~] Client-Server API
|
||||
[ ] Modules
|
||||
[ ] Instant Messaging
|
||||
[ ] Voice over IP
|
||||
[ ] Receipts
|
||||
[ ] Fully Read Markers
|
||||
[ ] Send-To-Device Messaging
|
||||
[ ] Server-Server API
|
||||
[ ] Application Service API
|
||||
[ ] Identity Service API
|
||||
[ ] Push Gateway API
|
||||
[ ] Room Versions
|
||||
|
||||
Milestone v1.1.0
|
||||
----------------
|
||||
|
||||
[ ] Database upgrades/migration path
|
||||
[ ] Create a command line tool to manage Telodendria
|
||||
[ ] User management
|
||||
[ ] Room management
|
||||
[ ] Migrate from Synapse or Dendrite, whichever is more mainstream by
|
||||
the time we get here.
|
|
@ -0,0 +1,316 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Argument Parsing
|
||||
#
|
||||
|
||||
echo "Build Configuration"
|
||||
echo "-------------------"
|
||||
|
||||
BUILD="build"
|
||||
OUT="out"
|
||||
SRC="src"
|
||||
INCLUDE="src/include"
|
||||
TOOLS="tools/src"
|
||||
SCHEMA="Schema"
|
||||
CYTOPLASM="Cytoplasm"
|
||||
|
||||
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
|
||||
LIBS="-lm -pthread -lCytoplasm"
|
||||
|
||||
|
||||
# Set default args for all platforms
|
||||
SCRIPT_ARGS="--prefix=/usr/local --bin-name=telodendria --version=1.7.0-alpha4"
|
||||
|
||||
if [ -f "${CYTOPLASM}/configure" ]; then
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cytoplasm=${CYTOPLASM}"
|
||||
else
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cytoplasm=" # No cytoplasm path.
|
||||
fi
|
||||
|
||||
# Set compiler depending on the platform.
|
||||
case "$(uname)" in
|
||||
Linux|NetBSD)
|
||||
# These systems typically use GCC.
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=gcc"
|
||||
;;
|
||||
OpenBSD|FreeBSD)
|
||||
# These systems typically use Clang.
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=clang"
|
||||
;;
|
||||
*)
|
||||
# Use default compiler which is required to be present on
|
||||
# all POSIX-compliant systems.
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=c99"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Append any additional args specified by user
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} $@"
|
||||
|
||||
echo "Processing options..."
|
||||
echo "Ran with arguments: $SCRIPT_ARGS"
|
||||
|
||||
# Process all arguments
|
||||
for arg in $SCRIPT_ARGS; do
|
||||
case "$arg" in
|
||||
--cc=*)
|
||||
CC=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
case "${CC}" in
|
||||
gcc*|clang*)
|
||||
# "Fancy" compilers that support a plethora of additional flags we
|
||||
# want to enable if present.
|
||||
CFLAGS="-Wall -Wextra -Werror -pedantic -std=c99 -O3 ${CFLAGS}"
|
||||
LDFLAGS="${LDFLAGS} -flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--prefix=*)
|
||||
PREFIX=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
;;
|
||||
--bin-name=*)
|
||||
BIN_NAME=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
;;
|
||||
--version=*)
|
||||
VERSION=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
;;
|
||||
--enable-debug)
|
||||
DEBUG="-O0 -g"
|
||||
;;
|
||||
--disable-debug)
|
||||
DEBUG=""
|
||||
;;
|
||||
--cytoplasm=*)
|
||||
CYTOPLASM=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
if [ -n "${CYTOPLASM}" ]; then
|
||||
if [ ! -f "${CYTOPLASM}/configure" ]; then
|
||||
echo "Path for Cytoplasm does not appear to actually contain Cytoplasm source:"
|
||||
echo "${CYTOPLASM}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CFLAGS="${CFLAGS} -I${CYTOPLASM}/include"
|
||||
LDFLAGS="-L${CYTOPLASM}/out/lib ${LDFLAGS}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument: $arg"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
CFLAGS="${CFLAGS} '-DTELODENDRIA_VERSION=\"${VERSION}\"' ${DEBUG}"
|
||||
LDFLAGS="${LDFLAGS} ${LIBS}"
|
||||
|
||||
#
|
||||
# Makefile generation
|
||||
#
|
||||
|
||||
collect() {
|
||||
from="$1"
|
||||
orig_ext="$2"
|
||||
new_ext="$3"
|
||||
prefix="$4"
|
||||
exec="$5"
|
||||
|
||||
find "${from}" -name "*${orig_ext}" -type f | while IFS= read -r src; do
|
||||
src=$(echo "$src" | sed -e "s|^${from}||g")
|
||||
obj=$(echo "$src" | sed -e "s|${orig_ext}\$|${new_ext}|g")
|
||||
|
||||
obj="${prefix}${obj}"
|
||||
src="${from}${src}"
|
||||
|
||||
"${exec}" "${src}" "${obj}"
|
||||
done
|
||||
}
|
||||
|
||||
prefix() {
|
||||
prefix="$1"
|
||||
shift
|
||||
|
||||
for thing in $@; do
|
||||
printf "${prefix}${thing} "
|
||||
done
|
||||
}
|
||||
|
||||
cytoplasm_tool() {
|
||||
tool="$1"
|
||||
|
||||
if [ -n "${CYTOPLASM}" ]; then
|
||||
echo "LD_LIBRARY_PATH=${CYTOPLASM}/out/lib ${CYTOPLASM}/out/bin/$tool"
|
||||
else
|
||||
echo "$tool"
|
||||
fi
|
||||
}
|
||||
|
||||
print_src() {
|
||||
printf '%s ' "$1"
|
||||
}
|
||||
|
||||
print_obj() {
|
||||
printf '%s ' "$2"
|
||||
}
|
||||
|
||||
get_deps() {
|
||||
src="$1"
|
||||
|
||||
${CC} -I${INCLUDE} -I${BUILD} $(if [ -n "${CYTOPLASM}" ]; then echo "-I${CYTOPLASM}/include"; fi) -E "$src" \
|
||||
| grep '^#' \
|
||||
| awk '{print $3}' \
|
||||
| cut -d '"' -f 2 \
|
||||
| sort \
|
||||
| uniq \
|
||||
| grep -v '^[/<]' \
|
||||
| grep -e "^${SRC}/" -e "^${BUILD}/" \
|
||||
| while IFS= read -r dep; do
|
||||
printf "%s " "$dep"
|
||||
done
|
||||
}
|
||||
|
||||
compile_obj() {
|
||||
src="$1"
|
||||
obj="$2"
|
||||
|
||||
pref="${obj}: $(get_deps ${src})"
|
||||
echo "$pref $(collect ${SCHEMA}/ .json .h ${BUILD}/Schema/ print_obj)"
|
||||
echo "${TAB}@mkdir -p $(dirname ${obj})"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
|
||||
}
|
||||
|
||||
compile_bin() {
|
||||
src="$1"
|
||||
out="$2"
|
||||
|
||||
depObjs=$(prefix ${BUILD}/ CanonicalJson.o Telodendria.o)
|
||||
|
||||
echo "${out}: ${src}"
|
||||
echo "${TAB}@mkdir -p ${OUT}/bin"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -o \"${out}\" \"${src}\" $depObjs \$(LDFLAGS)"
|
||||
}
|
||||
|
||||
compile_doc() {
|
||||
src="$1"
|
||||
out="$2"
|
||||
|
||||
if echo "${src}" | grep "Schema" > /dev/null; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "${out}: ${src}"
|
||||
echo "${TAB}@mkdir -p ${OUT}/man/man3"
|
||||
echo "${TAB}$(cytoplasm_tool hdoc) -D \"Os=${BIN_NAME}\" -i \"${src}\" -o \"${out}\""
|
||||
}
|
||||
|
||||
print_doc() {
|
||||
if echo "${src}" | grep "Schema" > /dev/null; then
|
||||
return
|
||||
fi
|
||||
printf '%s ' "$2"
|
||||
}
|
||||
|
||||
compile_schema() {
|
||||
src="$1"
|
||||
out="$2"
|
||||
|
||||
obj="${BUILD}/Schema/${out}.o"
|
||||
|
||||
echo "${BUILD}/Schema/${out}.h:"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}$(cytoplasm_tool j2s) -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
|
||||
|
||||
echo "${BUILD}/Schema/${out}.c:"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}$(cytoplasm_tool j2s) -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
|
||||
|
||||
echo "${obj}: ${src} ${BUILD}/Schema/${out}.c"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${BUILD}/Schema/${out}.c\""
|
||||
}
|
||||
|
||||
install_out() {
|
||||
src="$1"
|
||||
out="$2"
|
||||
|
||||
echo "${TAB}install -D \"$src\" \"$out\""
|
||||
}
|
||||
|
||||
install_man() {
|
||||
src="${OUT}/man/man3/${BIN_NAME}-$(basename $1 .h).3"
|
||||
out="$2"
|
||||
|
||||
echo "${TAB}install -D \"$src\" \"$out\""
|
||||
}
|
||||
|
||||
uninstall_out() {
|
||||
src="$1"
|
||||
out="$2"
|
||||
|
||||
echo "${TAB}rm \"$out\""
|
||||
}
|
||||
|
||||
echo "Generating Makefile..."
|
||||
|
||||
OBJS="$(collect ${SRC}/ .c .o ${BUILD}/ print_obj) $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
|
||||
TAB=$(printf '\t')
|
||||
|
||||
cat << EOF > Makefile
|
||||
.POSIX:
|
||||
|
||||
# Generated by '$0' on $(date).
|
||||
# This file should generally not be manually edited.
|
||||
|
||||
CC = ${CC}
|
||||
PREFIX = ${PREFIX}
|
||||
CFLAGS = ${CFLAGS}
|
||||
LDFLAGS = ${LDFLAGS}
|
||||
|
||||
all: ${BIN_NAME} docs tools
|
||||
docs: $(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${BIN_NAME}- print_doc)
|
||||
tools: $(collect ${TOOLS}/ .c '' ${OUT}/bin/ print_obj)
|
||||
|
||||
format:
|
||||
${TAB}find . -name '*.c' | while IFS= read -r src; do \\
|
||||
${TAB} if indent "\$\$src"; then \\
|
||||
${TAB} rm \$\$(basename "\$\$src").BAK; \\
|
||||
${TAB} fi \\
|
||||
${TAB}done
|
||||
|
||||
license:
|
||||
${TAB}find . -name '*.[ch]' | while IFS= read -r src; do \\
|
||||
${TAB} srcHeader=\$\$(grep -n -m 1 '^ \*/' "\$\$src" | cut -d ':' -f 1); \\
|
||||
${TAB} head -n\$\$srcHeader \$\$src | \\
|
||||
${TAB} diff -u -p - "LICENSE.txt" | \\
|
||||
${TAB} patch "\$\$src" | grep -v "^Hmm"; \\
|
||||
${TAB}done
|
||||
|
||||
${BIN_NAME}: ${OUT}/bin/${BIN_NAME}
|
||||
|
||||
install: ${BIN_NAME}
|
||||
${TAB}install -D ${OUT}/bin/${BIN_NAME} \$(PREFIX)/bin/${BIN_NAME}
|
||||
|
||||
uninstall:
|
||||
${TAB}rm \$(PREFIX)/bin/${BIN_NAME}
|
||||
|
||||
clean:
|
||||
${TAB}rm -r "${BUILD}" "${OUT}"
|
||||
|
||||
${OUT}/bin/${BIN_NAME}: ${OBJS}
|
||||
${TAB}@mkdir -p "${OUT}/bin"
|
||||
${TAB}\$(CC) -o "${OUT}/bin/${BIN_NAME}" ${OBJS} \$(CFLAGS) \$(LDFLAGS)
|
||||
|
||||
$(collect ${SCHEMA}/ .json '' '' compile_schema)
|
||||
$(collect ${SRC}/ .c .o ${BUILD}/ compile_obj)
|
||||
$(collect ${TOOLS}/ .c '' ${OUT}/bin/ compile_bin)
|
||||
$(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${BIN_NAME}- compile_doc)
|
||||
|
||||
$(
|
||||
if [ -n "${CYTOPLASM}" ]; then
|
||||
echo "cytoplasm:"
|
||||
echo "${TAB}cd ${CYTOPLASM} && ./configure && \$(MAKE)"
|
||||
fi
|
||||
)
|
||||
|
||||
EOF
|
||||
|
||||
echo "Done. Run 'make' to build ${BIN_NAME}."
|
|
@ -1 +0,0 @@
|
|||
.vagrant
|
|
@ -3,6 +3,4 @@ all:
|
|||
|
||||
install:
|
||||
install build/telodendria $(PREFIX)/bin/telodendria
|
||||
mkdir -p $(PREFIX)/share/examples/telodendria
|
||||
install contrib/production.conf $(PREFIX)/share/examples/telodendria/telodendria.conf
|
||||
find man -name 'telodendria*\.[1-8]' -exec install {} $(PREFIX)/{} \;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
@ -21,45 +22,44 @@
|
|||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#ifndef TELODENDRIA_DB_H
|
||||
#define TELODENDRIA_DB_H
|
||||
#include <Tls.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#if TLS_IMPL == TLS_TEMPLATE /* Set your TLS_* implementation
|
||||
* flag here */
|
||||
|
||||
#include <HashMap.h>
|
||||
#include <Array.h>
|
||||
/*
|
||||
* #include statements and any implementation structures
|
||||
* needed should go here.
|
||||
*/
|
||||
|
||||
typedef struct Db Db;
|
||||
typedef struct DbRef DbRef;
|
||||
void *
|
||||
TlsInitClient(int fd, const char *serverName)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern Db *
|
||||
DbOpen(char *, size_t);
|
||||
void *
|
||||
TlsInitServer(int fd, const char *crt, const char *key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern void
|
||||
DbClose(Db *);
|
||||
ssize_t
|
||||
TlsRead(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern DbRef *
|
||||
DbCreate(Db *, size_t,...);
|
||||
ssize_t
|
||||
TlsWrite(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern int
|
||||
DbDelete(Db *, size_t,...);
|
||||
|
||||
extern DbRef *
|
||||
DbLock(Db *, size_t,...);
|
||||
|
||||
extern int
|
||||
DbUnlock(Db *, DbRef *);
|
||||
|
||||
extern int
|
||||
DbExists(Db *, size_t,...);
|
||||
|
||||
extern Array *
|
||||
DbList(Db *, size_t,...);
|
||||
|
||||
extern void
|
||||
DbListFree(Array *);
|
||||
|
||||
extern HashMap *
|
||||
DbJson(DbRef *);
|
||||
int
|
||||
TlsClose(void *cookie)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,14 +1,18 @@
|
|||
{
|
||||
"serverName": "localhost",
|
||||
"baseUrl": "http://localhost:8008",
|
||||
"dataDir": "./data",
|
||||
"federation": true,
|
||||
"registration": true,
|
||||
"threads": 2,
|
||||
"log": {
|
||||
"output": "stdout",
|
||||
"//level": "debug",
|
||||
"timestampFormat": "none",
|
||||
"color": true
|
||||
}
|
||||
"log": {
|
||||
"output": "stdout",
|
||||
"color": true,
|
||||
"timestampFormat": "none",
|
||||
"level": "debug"
|
||||
},
|
||||
"listen": [
|
||||
{
|
||||
"port": 8008,
|
||||
"tls": false
|
||||
}
|
||||
],
|
||||
"registration": true,
|
||||
"serverName": "localhost",
|
||||
"baseUrl": "http:\/\/localhost:8008",
|
||||
"federation": true
|
||||
}
|
||||
|
|
|
@ -1,13 +1,24 @@
|
|||
{
|
||||
"serverName": "example.com",
|
||||
"baseUrl": "https://matrix.example.com",
|
||||
"identityServer": "https://identity.example.com",
|
||||
"dataDir": "/var/telodendria",
|
||||
"federation": true,
|
||||
"registration": false,
|
||||
"threads": 4,
|
||||
"maxCache": 512000000,
|
||||
"log": {
|
||||
"output": "file"
|
||||
"log": {
|
||||
"output": "file"
|
||||
},
|
||||
"listen": [
|
||||
{
|
||||
"port": 8008,
|
||||
"tls": false
|
||||
},
|
||||
{
|
||||
"port": 8448,
|
||||
"tls": {
|
||||
"cert": "telodendria.crt",
|
||||
"key": "telodendria.key"
|
||||
}
|
||||
}
|
||||
],
|
||||
"serverName": "example.com",
|
||||
"identityServer": "https://identity.example.com",
|
||||
"baseUrl": "https://matrix.example.com",
|
||||
"registration": false,
|
||||
"federation": true,
|
||||
"maxCache": 512000000
|
||||
}
|
||||
|
|
|
@ -0,0 +1,291 @@
|
|||
# Telodendria Change Log
|
||||
|
||||
This document contains the complete change log for every official release of Telodendria.
|
||||
It is intended to be updated with every commit that makes a user-facing change worth
|
||||
reporting in the change log. As such, it changes frequently between releases. Final
|
||||
change log entries are published as [Releases](releases).
|
||||
|
||||
## v1.7.0-alpha4
|
||||
|
||||
**Not Released Yet.**
|
||||
|
||||
This release brings filters, rooms, and events! The core of the Matrix
|
||||
protocol architecture is now in place.
|
||||
|
||||
Note that the versioning scheme has changed from `v0.X.0` to
|
||||
`v1.7.0-alphaX`. This is so that Telodendria releases correspond to the
|
||||
Matrix specification that they implement, in accordance with
|
||||
[this blog post](https://telodendria.io/blog/on-matrixs-release-cadence-and-state-resolution-v1).
|
||||
This versioning scheme change does not indicate a drastic leap forward
|
||||
in Telodendria's development—the `-alpha4` suffix indicates that
|
||||
this is the 4th pre-release, with the target being a stable `v1.7.0`.
|
||||
Note also that we still have a *long* way to go before we reach that
|
||||
stable release.
|
||||
|
||||
### Matrix Specification
|
||||
|
||||
The following endpoints were added:
|
||||
|
||||
- **POST** `/_matrix/client/v3/user/{userId}/filter`
|
||||
- **GET** `/_matrix/client/v3/user/{userId}/filter/{filterId}`
|
||||
|
||||
### Bug Fixes & General Improvements
|
||||
|
||||
- Use `j2s` for parsing the configuration
|
||||
- Fixed a double-free in `RouteUserProfile()` that would cause errors
|
||||
with certain Matrix clients. (#35)
|
||||
- Improved compatibility with NetBSD on various platforms.
|
||||
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository. It
|
||||
will now be maintained separately and have its own releases as well.
|
||||
- Use a `configure` script and `make` to build Telodendria instead of
|
||||
custom scripts.
|
||||
- Greatly simplified some endpoint code by using Cytoplasm's `j2s` for
|
||||
parsing request bodies.
|
||||
- Create a parser API for grammars found in Matrix, and refactor the
|
||||
User API to use it.
|
||||
|
||||
### New Features
|
||||
|
||||
- Implemented a `"pid"` option in the configuration, allowing Telodendria
|
||||
to write its process ID to a specified file.
|
||||
- Moved all administrator API endpoints to `/_telodendria/admin/v1`,
|
||||
because later revisions of the administrator API may break clients, so
|
||||
we want a way to give those breaking revisions new endpoints.
|
||||
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins
|
||||
to be able to deactivate users.
|
||||
- Added a **PUT** option to `/_telodendria/admin/v1/config` that gives
|
||||
the ability to change only a subset of the configuration.
|
||||
- Implemented the following APIs for managing registration tokens:
|
||||
- **GET** `/_telodendria/admin/tokens`
|
||||
- **GET** `/_telodendria/admin/tokens/[token]`
|
||||
- **POST** `/_telodendria/admin/tokens`
|
||||
- **DELETE** `/_telodendria/admin/tokens/[token]`
|
||||
- **GET** `/_matrix/client/v3/directory/room/[alias]`
|
||||
- **PUT** `/_matrix/client/v3/directory/room/[alias]`
|
||||
- **DELETE** `/_matrix/client/v3/directory/room/[alias]`
|
||||
- **GET** `/_matrix/client/v3/rooms/[id]/aliases`
|
||||
|
||||
## v0.3.0
|
||||
|
||||
**Saturday, June 10, 2023**
|
||||
|
||||
Introducing a new configuration API and Cytoplasm, a general-purpose C library that
|
||||
supports source/sink-agnostic I/O, TLS, an HTTP client, and more! The third major
|
||||
release of Telodendria packs a lot of architectural improvements on top of supporting
|
||||
more of the Matrix specification.
|
||||
|
||||
### Matrix Specification
|
||||
|
||||
Added support for the following endpoints:
|
||||
|
||||
- `/_matrix/client/v3/account/whoami`
|
||||
- `/_matrix/client/v3/account/password`
|
||||
- `/_matrix/client/v3/account/deactivate`
|
||||
- `/_matrix/client/v3/profile/*`
|
||||
- `/_matrix/client/v3/capabilities`
|
||||
- `/_matrix/client/v3/auth/*/fallback/web`
|
||||
|
||||
There is also support for token-based user registration. Note that there is as of
|
||||
yet no admin-facing way to create these registration tokens, but the APIs are in
|
||||
place.
|
||||
|
||||
### New Features
|
||||
|
||||
- Added a new `HttpClient` API for making HTTP requests. This will eventually be
|
||||
used for federating with other Matrix homeservers.
|
||||
- Added support for pretty-printing JSON in `Json`. Telodendria itself does not
|
||||
pretty-print JSON, but this is useful for debugging and building useful tools.
|
||||
- Added a handful of new development tools built on the Telodendria APIs. New
|
||||
tools include `http`, a command line tool for making HTTP requests, similar to
|
||||
`curl`, `json`, a command line tool for working with JSON, similar to `jq`, and
|
||||
`http-debug-server`, a simple HTTP server that just prints requests out to standard
|
||||
output and returns an empty JSON object. `http` and `json` are replacements for
|
||||
`curl` and `jq` that build on the `HttpClient` and `Json` APIs. They exist mainly
|
||||
to test those APIs, but also to reduce the number of dependencies that Telodendria
|
||||
has. `http-debug-server` exists to test the `HttpServer` and `HttpClient` APIs.
|
||||
- Replaced all usage of `jq` with the new `json` tool. `jq` is no longer a development
|
||||
dependency.
|
||||
- Replaced all usage of `curl` with the new `http` tool. `curl` is no longer a
|
||||
required development dependency.
|
||||
- Added a new `tt` script for easily making Matrix requests against Telodendria
|
||||
in development.
|
||||
- Added TLS support to both the HTTP client and server. Currently, Telodendria
|
||||
supports LibreSSL and OpenSSL, but other TLS libraries should be extremely easy
|
||||
to add support for.
|
||||
- Added support for spinning up multiple HTTP servers. This is useful for having
|
||||
a TLS port and a non-TLS port, for example.
|
||||
- Moved all program configuration to the data directory and added an administrator
|
||||
API endpoint to manage it. It is now no longer recommended to manually update the
|
||||
configuration file. Consult the [Administrator API](user/admin/README.md) documentation
|
||||
and the [Configuration](user/config.md) documentation.
|
||||
- Added an administrator API endpoint for process control. Telodendria can now be
|
||||
restarted or shutdown via API endpoint.
|
||||
- Added an administrator API endpoint for getting statistics about the running
|
||||
Telodendria process.
|
||||
- Added support for user privileges, a way to have fine-grained control over what
|
||||
users are allowed to do with the administrator API. Administrator APIs for setting
|
||||
and getting privileges is now supported, and registration tokens have privileges
|
||||
associated with them so that users created with a token will automatically be given
|
||||
the specified privileges.
|
||||
|
||||
### Fixes & General Improvements
|
||||
|
||||
- Fixed a few warnings that were generated on some obscure compilers.
|
||||
- Moved the `main()` function to its own file to make it easier to link other
|
||||
programs with the Telodendria APIs.
|
||||
- Fixed the development tools environment script. Apparently using a hyphen as a bullet
|
||||
point is not very portable, because some shell implementations of `printf` interpret it
|
||||
as a flag. Switched to an asterisk.
|
||||
- Fixed some intermittent I/O errors that would occur as a result of race conditions in
|
||||
`JsonConsomeWhitespace()`. This function, and a few others, expect I/O to be blocking,
|
||||
but the `HttpServer` sets up I/O to be non-blocking, leading to occasional failures in
|
||||
JSON parsing.
|
||||
- Abstracted all I/O into the new `Io` and `Stream` APIs, which provide an input- and
|
||||
output- agnostic stream processing interface. This allows for a simple implementation of
|
||||
proxies, TLS, and other stream filters without having to change any of the existing
|
||||
code.
|
||||
- Remove all non-POSIX function calls, including the call to `chroot()` and, on
|
||||
OpenBSD, `pledge()` and `unveil()`. This may seem like a downgrade in security, but
|
||||
these are platform-specific system calls that should be patched in by package maintainers
|
||||
if they are desired. They also caused problems when implementing other features, because
|
||||
some library calls need to be able to access files on the filesystem.
|
||||
- Fixed the build script to supply `LDFLAGS` after the object files when linking.
|
||||
Apparently the order in which libraries are passed matters to some compilers.
|
||||
- Added the response status of a request to the log output. This means that requests are
|
||||
logged after they have completed, not before they are started.
|
||||
- Memory allocations, reallocations, and frees are no longer loged when the log level
|
||||
is set to debug in the configuration file. To enable the logging of memory operations,
|
||||
pass the `-v` flag.
|
||||
- Implemented a proper HTTP request router with POSIX regular expression support.
|
||||
Previously, a series of nested `if`-statements were used to route requests, but this
|
||||
approach quickly becamse very messy. While the HTTP request router incurs a small memory
|
||||
and runtime speed penalty, the code is now much more maintainable and easier to follow.
|
||||
- Fixed some memory bugs in `Db` that were related to caching data. Caching should
|
||||
now work as expected.
|
||||
- Fixed a major design flaw in `Db` that would cause deadlock when multiple threads
|
||||
request access to the same object. Database locking is now in a per-thread basis,
|
||||
instead of a per-reference basis.
|
||||
- Telodendria now shuts down cleanly in response to `SIGTERM`.
|
||||
- Did some general refactoring to make the source code more readable and easier
|
||||
to maintain.
|
||||
Fixed a number of memory-related issues, including switching out some unsafe
|
||||
functions for safer versions, per the recommendations of the OpenBSD linker.
|
||||
- Moved all code documentation into the C header files to make it more likely
|
||||
that it will get updated. A simple header file parser and documentation generator
|
||||
have been added to the code base. See the `hdoc` man pages for documentation.
|
||||
- Updated the build script to provide static and shared libraries containing
|
||||
the code for Telodendria to make it easier to statically and dynamically link to
|
||||
other programs. The idea is that these libraries should be shipped with Telodendria,
|
||||
or as a separate package, and can be used to provide a high-level programming
|
||||
environment.
|
||||
- Updated the `Json` API to calculate the length of a JSON object. This is
|
||||
used to set the `Content-Length` header in HTTP requests and reponses.
|
||||
- Added some string functions, including `StrEquals()`, which replaced almost all
|
||||
uses of `strcmp()`, since `strcmp()` is used almost exclusively for equality
|
||||
checking. `StrEquals()` provides a standard way to do so, because previously,
|
||||
multiple different conventions could be found throughout the code base (for example:
|
||||
`!strcmp(str1, str2)` vs `strcmp(str1, str2) == 0`).
|
||||
|
||||
... And many more!
|
||||
|
||||
## v0.2.1
|
||||
|
||||
**Monday, March 6, 2023**
|
||||
|
||||
This is a patch release that fixes a few typos and other minor issues.
|
||||
|
||||
## v0.2.0
|
||||
|
||||
**Monday, March 6, 2023**
|
||||
|
||||
This release is focused on providing a decent amount of the client authentication
|
||||
API. You can now create accounts on a Telodendria homeserver, and log in to
|
||||
get access tokens.
|
||||
|
||||
### New
|
||||
|
||||
- Added the basic form of the user registration API. If registration is enabled
|
||||
in the configuration file, clients can now register for Matrix accounts.
|
||||
- Added the basic form of the user login API. Clients can now log in to
|
||||
their accounts and generate access tokens to be used to authenticate requests.
|
||||
- Added the basic form of the user interactive authentication API, which can be used
|
||||
by endpoints that the spec says requires it. Currently, it only implements the
|
||||
dummy and password stages, but more stages, such as the registration token stage,
|
||||
will be added in future releases.
|
||||
- Added a simple landing page that allows those setting up Telodendria to
|
||||
quickly verify that it is accessible where it needs to be.
|
||||
- Added the static login page for clients that don't support regular login.
|
||||
|
||||
### Changes
|
||||
|
||||
- Improved HTTP request logging by removing unnecessary log entries and making
|
||||
errors more specific.
|
||||
- Leaked memory is now hexdump-ed out to the log if the log level is set to debug.
|
||||
This greatly simplifies debugging, because developers can now see exactly what the
|
||||
contents of the leaked memory are. Note that in some circumstances, this memory
|
||||
may contain sensitive data, such as access tokens, usernames, or passwords. However,
|
||||
Telodendria should not be leaking memory at all, so if you encounter any leaks,
|
||||
please report them.
|
||||
- Refactored a lot of the code and accompanying documentation to be more readable and
|
||||
maintainable.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed a memory leak that would occur when parsing an invalid JSON object.
|
||||
- Fixed an edge case where HTTP response headers were being sent before they were
|
||||
properly set, causing the server to report a status of 200 even when that wasn't the
|
||||
desired status.
|
||||
- Fixed a few memory leaks in the HTTP parameter decoder that would occur in some
|
||||
edge cases.
|
||||
- Fixed an "off-by-one" error in the HTTP server request parser that would prevent
|
||||
`GET` parameters from being parsed.
|
||||
- Fixed the database file name descriptor to prevent directory traversal attacks
|
||||
by replacing special characters with safer ones.
|
||||
- Fixed a memory leak that would occur when closing a database that contains
|
||||
cached objects.
|
||||
- Fixed a memory leak that would occur when deleting database objects.
|
||||
- Fixed a few non-fatal memory warnings that would show up as a result of passing a
|
||||
constant string into certain functions.
|
||||
|
||||
### Misc.
|
||||
|
||||
- Fixed a bug in `td` that caused `cvs` to be invoked in the wrong directory when
|
||||
tagging a new release.
|
||||
- Added support for environment variable substitution in all site files. This
|
||||
makes it easier to release Telodendria versions.
|
||||
- Fix whitespace issues in various shell scripts.
|
||||
- Fixed the debug log output so that it only shows the file name, not the
|
||||
entire file path in the repository.
|
||||
- Updated the copyright year in the source code and compiled output.
|
||||
- Switched the `-std=c89` flag to `-ansi`, as `-ansi` might be more supported.
|
||||
- Fixed the `-v` flag. It now sets the log level to debug as soon as possible to
|
||||
allow debugging configuration file parsing if necessary.
|
||||
|
||||
... And many more bug fixes and feature additions! Too much has changed to make a
|
||||
comprehensive change log. A lot of things have been done under the hood to make
|
||||
Telodendria easier to develop in the future. Please test the current functionality,
|
||||
and report bugs.
|
||||
|
||||
The following platforms have been known to compile and run Telodendria:
|
||||
|
||||
- OpenBSD
|
||||
- Linux (GNU and non-GNU)
|
||||
- Windows (via Cygwin)
|
||||
- FreeBSD
|
||||
- NetBSD
|
||||
- DragonFlyBSD
|
||||
- Haiku OS
|
||||
- Android (via Termux)
|
||||
|
||||
Telodendria is about being portable; if you compile it on an obscure operating system,
|
||||
do let us know about it!
|
||||
|
||||
## v0.1.0
|
||||
|
||||
**Tuesday, December 13, 2022**
|
||||
|
||||
This is the first public release of Telodendria so there are no changes to report.
|
||||
Future releases will have a complete change log entry here.
|
||||
|
||||
This is a symbolic release targeted at developers, so there's nothing useful to
|
||||
ordinary users yet. Stay tuned for future releases though!
|
|
@ -0,0 +1,256 @@
|
|||
# Contributing
|
||||
|
||||
Telodendria is a fully open source project. As such, it welcomes
|
||||
contributions. There are many ways you can contribute, and any way you
|
||||
can is greatly appreciated. This document details the ways you can
|
||||
contribute, and how to go about contributing.
|
||||
|
||||
## Sponsoring Telodendria
|
||||
|
||||
If you would like to sponsor Telodendria, see the
|
||||
[Sponsorship](../README.md#sponsorship) section on the main project
|
||||
page. Donations of any size are greatly appreciated.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
An important way to get involved is to just report issues you find with
|
||||
Telodendria during experimentation or normal use. To report an issue,
|
||||
go to [Issues](/Telodendria/telodendria/issues) →
|
||||
[New Issue](/Telodendria/telodendria/issues/new/choose) and follow the
|
||||
instructions.
|
||||
|
||||
> **Note:** GitHub issues are not accepted. Issues may only be
|
||||
> submitted to the official [Gitea](https://git.telodendria.io)
|
||||
> instance.
|
||||
|
||||
### Feature Requests
|
||||
|
||||
Feature requests are allowed, but note that they are low-priority in
|
||||
comparison to existing issues and features. That being said, don't
|
||||
hesitate to submit feature requests. Just select the "Feature Request"
|
||||
option when submitting an issue.
|
||||
|
||||
## Developing
|
||||
|
||||
If you want to write code for Telodendria, either to fix an issue or
|
||||
add a new feature, you're in the right place. Please follow all the
|
||||
guidelines in this document to ensure the contribution workflow goes
|
||||
as smoothly as possible.
|
||||
|
||||
### Who can develop Telodendria?
|
||||
|
||||
Everyone is welcome to contribute code to Telodendria, provided that
|
||||
they are willing to license their contributions under the same license
|
||||
as the project itself.
|
||||
|
||||
The primary language used to write Telodendria code is ANSI C. Other
|
||||
languages you'll find in the Telodendria repository include shell
|
||||
scripts, `mdoc`, a little bit of HTML and CSS, and `Makefiles`.
|
||||
Experience with any of these is preferred, but if you want to use
|
||||
Telodendria to learn, that's okay too! Telodendria's code base should
|
||||
hopefully be a good learning tool, and if you are serious about
|
||||
submitting quality work, we'll guide you through the process and
|
||||
offer suggestions.
|
||||
|
||||
### What do I need?
|
||||
|
||||
You'll need a couple of things to develop Telodendria:
|
||||
|
||||
- A Unix-like operating system that provides standard POSIX behavior,
|
||||
or the Windows Subsystem for Linux (WSL), Cygwin, or Msys2 if you are
|
||||
running Windows.
|
||||
- A C compiler capable of compiling ANSI C89 code (pretty much all of
|
||||
them do—pick your favorite, and if you find it doesn't work,
|
||||
open an issue!).
|
||||
- `make` for building the project.
|
||||
- `git` for managing your changes.
|
||||
- [Cytoplasm](/Telodendria/Cytoplasm), a simple C library written by
|
||||
the Telodendria developers for the purpose of supporting Telodendria
|
||||
in a modular way.
|
||||
|
||||
Optionally, you may also find these tools helpful:
|
||||
|
||||
- `indent` for formatting code.
|
||||
- `valgrind` for debugging particularly nasty issues.
|
||||
|
||||
### Getting The Code
|
||||
|
||||
Telodendria is developed using Git. The easiest way to contribute
|
||||
changes is to fork the main repository, and then creating a pull
|
||||
request to ask us to pull your changes into our repo.
|
||||
|
||||
1. If you don't have an account on the
|
||||
[Gitea instance](https://git.telodendria.io), create one and sign in.
|
||||
1. Fork this repository.
|
||||
1. In your development environment, clone your fork:
|
||||
```shell
|
||||
git clone https://git.telodendria.io/[YOUR_USERNAME]/Telodendria.git
|
||||
cd Telodendria
|
||||
```
|
||||
|
||||
Please base your changes on the `master` branch. If you need help
|
||||
getting started with Git, that is beyond the scope of this
|
||||
document, but you can find many good tutorials on the web.
|
||||
|
||||
### Building & Running
|
||||
|
||||
Telodendria uses the `make` build system. Because it aims at maximum
|
||||
portability, it targets POSIX `make` and should thus run on any POSIX
|
||||
system that provides a `make`, be it GNU, BSD, or something different
|
||||
entirely. To facilitate this, Telodendria provides a `configure` script
|
||||
which generates the `Makefile`, because the `Makefile` would be far too
|
||||
verbose and tedious to maintain in a POSIX-compatible way otherwise.
|
||||
This is similar to how other C programs and libraries are built, although
|
||||
note that Telodendria's `configure` script is not nearly as advanced as
|
||||
an `autoconf` script, for example.
|
||||
|
||||
Please follow the build and installation directions for
|
||||
[Cytoplasm](/Telodendria/Cytoplasm) first before attempting to build
|
||||
Telodendria, because Telodendria depends on Cytoplasm and assumes it is
|
||||
installed in the standard location for your system. For the best results,
|
||||
it is recommended to take the time to enable TLS, unless you plan on
|
||||
running Telodendria behind a reverse proxy.
|
||||
|
||||
To build Telodendria, simply run `configure`, then `make`:
|
||||
|
||||
```
|
||||
$ ./configure
|
||||
$ make
|
||||
```
|
||||
|
||||
You may find some of the following options for `configure` helpful:
|
||||
|
||||
- `--prefix=<path>`: Set the install prefix to set by default in the `Makefile`. This defaults to `/usr/local`, which should be appropriate for most Unix-like systems.
|
||||
- `--(enable|disable)-ld-extra`: Control whether or not to enable additional linking flags that create a more optimized binary. For large compilers such as GCC and Clang, these flags should be enabled. However, if you are using a small or more obscure compiler, then these flags may not be supported, so you can disable them with this option.
|
||||
- `--(enable|disable)-debug`: Control whether or not to enable debug mode. This sets the optimization level to 0 and builds with debug symbols. Useful for running with a debugger.
|
||||
- `--static` and `--no-static`: Controls whether static binaries are built by default. On BSD systems, `--static` is perfectly acceptable, but on GNU systems, `--no-static` is often desirable to silence warnings about static binaries emitted by the GNU linker.
|
||||
|
||||
Telodendria can be customized with the following options:
|
||||
|
||||
- `--bin-name=<name>`: The output name of the server binary. This defaults to `telodendria`. Common alternatives are `matrix-telodendria` or `telodendria-server`.
|
||||
- `--version=<version>`: The version string to embed in the binary. This can be used to indicate build customizations or non-release versions of Telodendria.
|
||||
|
||||
The following recipes are available in the generated `Makefile`:
|
||||
|
||||
- `all`: This is the default target. It builds everything.
|
||||
- `telodendria`: Build the `telodendria` binary. If you specified an alternative `--bin-name`, then this target will be named after that.
|
||||
- `docs`: Generate the header documentation as `man` pages.
|
||||
- `tools`: Build the supplemental tools which may be useful for development.
|
||||
- `clean`: Remove the build and output directories. Telodendria builds are out-of-tree, which greatly simplifies this recipe compared to in-tree builds.
|
||||
|
||||
If you're developing Telodendria, these recipes may also be helpful:
|
||||
|
||||
- `format`: Format the source code using `indent`. This may require a BSD `indent` because last time I tried GNU `indent`, it didn't like the flags in `indent.pro`. Your mileage may vary.
|
||||
- `license`: Update the license headers in all source code files with the contents of the `LICENSE.txt`.
|
||||
|
||||
To install Telodendria to your system, the following recipes are available:
|
||||
|
||||
- `install`: This installs Telodendria under the prefix set with `./configure --prefix=<dir>` or with `make PREFIX=<dir>`. By default, the `make` `PREFIX` is set to whatever was set with `configure --prefix`.
|
||||
- `uninstall`: Uninstall Telodendria from the same prefix as specified above.
|
||||
|
||||
After a build, you can find the object files in `build/` and the output binary in `out/bin/`.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
> **Note:** Telodendria does not accept GitHub pull requests at this
|
||||
> time. Please submit your pull requests via Gitea.
|
||||
|
||||
Telodendria follows the standard pull request procedures. Once you have
|
||||
made your changes, committed them, and pushed to your fork, you should
|
||||
be able to open a pull request on the main repository. When you do, you
|
||||
will be prompted to write a description. Be sure to include the
|
||||
related issue that you are closing in your description.
|
||||
|
||||
### Code Style
|
||||
|
||||
In general, these are the conventions used by the code base. This
|
||||
guide may be slightly outdated or subject to change, but it should be
|
||||
a good start. The source code itself is always the absolute source of
|
||||
truth, so as long as you make your code look like the code surrounding
|
||||
it, you should be fine.
|
||||
|
||||
- All function, enumeration, structure, and header names are
|
||||
`CamelCase`. This is preferred to `snake_case` because it is more
|
||||
compact.
|
||||
- All variable names are `lowerCamelCase`. This is preferred to
|
||||
`snake_case` because it is more compact. One exception to this rule is
|
||||
if a variable name, such as a member of a struct, directly represents
|
||||
a JSON key in an object specified by the Matrix specification, which
|
||||
may be in `snake_case`.
|
||||
- Enumerations and structures are always `typedef`-ed to their same
|
||||
name. The `typedef` should occur in the public API header, and the
|
||||
actual declaration should live in the implementation file, unless
|
||||
the enumeration or structure is intended to be made fully public.
|
||||
- A feature of the code base lives in a single C source file that has a
|
||||
matching header. The header file should only export public symbols;
|
||||
everything else in the C source should be static.
|
||||
- Except where absolutely necessary, global variables are forbidden
|
||||
to prevent problems with threads and whatnot. Every variable a
|
||||
function needs should be passed to it either through a structure, or
|
||||
as a separate argument.
|
||||
- Anywhere that C allows curly braces to be optional, there still must
|
||||
be curly braces. This makes it easier to read the code by making it
|
||||
less ambiguous, and it makes it easier to add on to the code later.
|
||||
|
||||
As far as actually formatting the code goes, such as where to put
|
||||
brackets, and whether or not to use tabs or spaces, use `indent` to
|
||||
take care of that. The repository contains a `.indent.pro` that should
|
||||
automatically be loaded by `indent` to set the correct rules. If you
|
||||
don't have a working `indent`, then just indicate in your pull
|
||||
request that I should run my `indent` on the code.
|
||||
|
||||
### Documentation
|
||||
|
||||
This project places a strong emphasis on documentation. Well-documented
|
||||
code is fundamental to a successful project, so when you are writing
|
||||
code, please also make sure that it is documented appropriately.
|
||||
|
||||
- If you are adding a header, make sure you add the necessary comments
|
||||
detailing the header and the functions in it.
|
||||
- If you are adding a function, make sure you add the necessary
|
||||
comments to the appropriate header.
|
||||
|
||||
If your pull request does not also include proper documentation, it
|
||||
will likely be rejected.
|
||||
|
||||
### Be Recognized!
|
||||
|
||||
If your pull request gets approved, you should be recognized for your
|
||||
contributions to the project!
|
||||
|
||||
To have your work recognized, add your information to the `CONTRIBUTORS.txt`
|
||||
file in the root of the Telodendria repository if it isn't there already.
|
||||
You should do this as a part of your pull request so that when it is merged,
|
||||
your information will be automatically added to the repository.
|
||||
|
||||
The `CONTRIBUTORS.txt` file loosely follows the Linux kernel's
|
||||
[CREDITS](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/CREDITS)
|
||||
file format. It is designed to be human-readable, but also parsable by
|
||||
scripts.
|
||||
|
||||
The following fields are available:
|
||||
|
||||
```
|
||||
(N) Name
|
||||
(E) Email
|
||||
(M) Matrix ID
|
||||
(W) Website
|
||||
(D) Description of contribution
|
||||
(L) Physical location
|
||||
```
|
||||
|
||||
Here are the rules:
|
||||
|
||||
* All fields are optional. If you don't want to include a field, that's
|
||||
okay, simply omit it.
|
||||
* All fields identify you however you wish. The goal is to recognize you for
|
||||
your contribution, but if you wish to remain anonymous, you don't have to
|
||||
use your real information.
|
||||
* All fields can be specified multiple times. For example, if you have
|
||||
multiple email addresses, websites, or Matrix IDs and you want to include
|
||||
all of them, you absolutely may. Likewise, if you have made multiple
|
||||
contributions, you can add multiple description entries.
|
||||
* You can make up your own fields if you want. Just add their description
|
||||
above.
|
||||
* Leave exactly one blank like between entries in this file.
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Telodendria Documentation
|
||||
|
||||
Here you will find all of the documentation for Telodendria. If you
|
||||
find that some documentation is missing or incorrect, please open an
|
||||
issue, and, even better, a pull request to fix the issue.
|
||||
|
||||
You are also welcome to join the
|
||||
[`#telodendria-general:bancino.net`](https://matrix.to/#/#telodendria-general:bancino.net)
|
||||
matrix room, where you can discuss Telodnedria with others and ask
|
||||
questions.
|
||||
|
||||
## User Documentation
|
||||
|
||||
- [System Requirements](user/requirements.md)
|
||||
- [Install](user/install.md)
|
||||
- [Usage & Running](user/usage.md)
|
||||
- [Initial Set Up](user/setup.md)
|
||||
- [Configuration Options](user/config.md)
|
||||
- [Administrator API](user/admin/README.md)
|
||||
|
||||
## Developer Documentation
|
||||
|
||||
- [Repository Structure](dev/repo.md)
|
||||
- [Contributing Guidelines](CONTRIBUTING.md)
|
||||
- [Porting Guidelines](dev/ports.md)
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
- [Matrix Specification](https://spec.matrix.org) ([Mirror](https://telodendria.io/spec.matrix.org))
|
||||
- [Change Log](CHANGELOG.md)
|
||||
- [Project Road Map](ROADMAP.md)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# Telodendria Matrix Specification Roadmap
|
||||
|
||||
This document provides a high-level overview of Telodendria's roadmap as it pertains to implementing the Matrix specification. Essentially, the Matrix specification is divided up into manageable portions amongst Telodendria releases, so that each release up until the first stable release implements a small portion of it.
|
||||
|
||||
**Note:** The first stable release of Telodendria will implement Matrix v1.7, no newer version. The Matrix specification changes too frequently, so I had to just pick a version in order to make this project manageable. Once v1.7 is complete, then we can move on to later specs.
|
||||
|
||||
This document will be updated to include more implementation details as they come up. It contains the big picture for far-out releases, and more relevant implementation details for near releases.
|
||||
|
||||
## Milestone v0.4.0
|
||||
|
||||
- [ ] Client-Server API
|
||||
- [ ] **7:** Events
|
||||
- [x] Compute size of JSON object in `CanonicalJson`
|
||||
- [x] Rename `Sha2.h` to just `Sha.h`; add `Sha1()` function
|
||||
- [x] Make `Sha256()` just return raw bytes; add function to convert to hex string.
|
||||
- [ ] **8:** Rooms
|
||||
- [ ] **9:** User Data
|
||||
- [x] Profiles
|
||||
- [ ] Directory
|
||||
|
||||
## Milestone v0.5.0
|
||||
|
||||
- [ ] Client-Server API
|
||||
- [ ] Modules
|
||||
- [ ] Content Repository
|
||||
|
||||
## Milestone v0.6.0
|
||||
|
||||
- [ ] Client-Server API
|
||||
- [ ] Modules
|
||||
- [ ] Instant Messaging
|
||||
- [ ] Voice over IP
|
||||
- [ ] Receipts
|
||||
- [ ] Fully Read Markers
|
||||
- [ ] Send-To-Device Messaging
|
||||
- [ ] Security (Rate Limiting)
|
||||
|
||||
## Milestone v0.7.0
|
||||
|
||||
- [ ] Server-Server API
|
||||
|
||||
## Milestone v0.8.0
|
||||
|
||||
- [ ] Application Service API
|
||||
- [ ] YAML parser?
|
||||
|
||||
## Milestone v0.9.0
|
||||
|
||||
- [ ] Identity Service API
|
||||
|
||||
## Milestone v1.7.0 (Stable Release!)
|
||||
|
||||
- [ ] Push Gateway API
|
||||
- [ ] Room Versions
|
|
@ -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.
|
|
@ -0,0 +1,92 @@
|
|||
## Ports
|
||||
|
||||
Telodendria is distributed primarily as source code, and the project
|
||||
itself does not offer a convenient install process such as in the form
|
||||
of a shell script. This is intentional; the Telodendria project is
|
||||
primarily concerned with developing Telodendria itself, not packaging
|
||||
it for the hundreds of different operating systems and linux
|
||||
distributions that exist. It is my firm belief that distributing an
|
||||
open source project is not the job of the open source developer; that
|
||||
is the reason software distributions exist: to collect and
|
||||
*distribute* software.
|
||||
|
||||
It would be impossible to single-handedly package Telodendria for
|
||||
every platform, because each platform has very different expectations
|
||||
and conventions for software. Even different Linux distributions have
|
||||
different conventions for where manual pages, binaries, and
|
||||
configuration files go.
|
||||
|
||||
That being said, this document aims to assist those who want to
|
||||
package Telodendria for their operating system or software
|
||||
distribution.
|
||||
|
||||
---
|
||||
|
||||
Before attempting to package Telodendria, make sure that you can build
|
||||
it and that it builds cleanly on your target platform. See
|
||||
[Install → From Source](../user/install.md#from-source)
|
||||
for general build instructions.
|
||||
|
||||
To package Telodendria, you should collect the following files, and
|
||||
figure out where they should be installed on your system:
|
||||
|
||||
- The `telodendria` server binary itself.
|
||||
- An init script. People that wish to install Telodendria on their
|
||||
system using your package are going to expect it to be integrated
|
||||
enough that Telodendria can easily be started at boot and otherwise
|
||||
managed by the system's daemon tools, be it `systemd` or another
|
||||
init system. Consult your system's documentation for writing an init
|
||||
script. **Note:** Telodendria *does not* fork itself to the background;
|
||||
the init script should do that.
|
||||
- You may also wish to ship the `docs/` directory
|
||||
so that the user can read the documentation offline, and ensure that
|
||||
they are reading the correct documentation for the installed version.
|
||||
|
||||
You may wish to optionally create a dedicated user under which
|
||||
Telodendria should run. Telodendria can be directly started as that
|
||||
user, or start as root and be configured to automatically drop to that
|
||||
user. Additionally, it might be helpful to provide a default
|
||||
configuration, which can be placed in the samples directory on your
|
||||
platform, or in a default location that Telodendria will load from.
|
||||
A good default directory that you may wish to provide for configuration,
|
||||
data, and logs could perhaps be `/var/telodendria` or `/var/db/telodendria` on Unix-like systems.
|
||||
|
||||
Once you have collected the necessary files and directories that need
|
||||
to be installed, make sure your package performs the following tasks
|
||||
on install:
|
||||
|
||||
- If necessary and depending on the configuration used, create a new
|
||||
system user for the Telodnedria daemon to run as.
|
||||
- If conventional for your system, enable the Telodendria init script
|
||||
so that Telodendria is started on system boot.
|
||||
- Instruct the user to carefully read the [Setup](../user/setup.md)
|
||||
(`docs/user/setup.md`) instructions and the
|
||||
[Configuration](../user/config.md) (`docs/user/config.md`) instructions
|
||||
before starting Telodendria.
|
||||
|
||||
The goal of a package should be to get everything as ready-to-run as
|
||||
possible. The user should be able to start Telodendria right away and
|
||||
begin configuring it.
|
||||
|
||||
Remember to publicly document the setup of Telodendria on your platform
|
||||
if there are additional steps required that are not mentioned in the
|
||||
official Telodendria documentation. This ensures that users can get
|
||||
up and running quickly and easily. If you're packaging Telodendria
|
||||
for a container system such as Docker, you can omit the things that
|
||||
containers typically do not have, such as the init scripts and
|
||||
documentation.
|
||||
|
||||
Also remember that your port should feel like it belongs on your target
|
||||
system. Follow all of your system's conventions when placing files
|
||||
on the filesystem, so your users know what to expect. The goal is not
|
||||
necessarily to have a unified experience across all operating systems,
|
||||
rather, you should cater to the opinions of your operating system.
|
||||
Telodendria is architected in such a way that it does not impose the
|
||||
developer's opinions of where things should go, and since the
|
||||
configuration lives in the database, it is fairly self contained.
|
||||
|
||||
If there are any changes necessary to the upstream code or build
|
||||
system that would make your job in porting Telodendria easier, do not
|
||||
hesitate to get involved by opening an issue and/or submitting a pull
|
||||
request.
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# Rationale
|
||||
|
||||
This document seeks to answer the question of "why Telodendria?" from
|
||||
a technical perspective by comparing it to existing Matrix homservers.
|
||||
Telodendria is written entirely from scratch in ANSI C. It is designed
|
||||
to be lightweight, simple, and functional. Telodendria differentiates
|
||||
itself from other homeserver implementations because it:
|
||||
|
||||
- Is written C, a stable, low-level programming language with a long
|
||||
history, low build and runtime overhead, and wide compatibility.
|
||||
- Is written with minimalism as a primary design goal. Whenever possible
|
||||
and practical, no third-party libraries are pulled into the code.
|
||||
Everything Telodnedria needs is custom written. As a result, Telodendria
|
||||
depends only on a standard C compiler and a POSIX C library to be
|
||||
built, both of which should come with any good Unix-style operating
|
||||
system already, which means you shouldn't have to install anything
|
||||
additional to use Telodendria.
|
||||
- Uses a flat-file directory structure to store data instead of a
|
||||
real database. This has a number of advantages:
|
||||
- It make setup and mainenance much easier.
|
||||
- It allows Telodendria to run on systems with fewer resources.
|
||||
- Is packaged as a single small, statically-linked and highly-optimized
|
||||
binary that can be run just about anywhere. It is designed to be
|
||||
extremely easy to set up and consume as few resources as possible.
|
||||
- Is permissively licensed. Telodendria is licensed under a modified
|
||||
MIT license, which imposes very few restrictions on what you can do
|
||||
with it.
|
||||
|
||||
## What about [Conduit](https://conduit.rs)?
|
||||
|
||||
At this point, you may be wondering why one would prefer Telodendria
|
||||
over Conduit, a Matrix homeserver that could also say pretty much
|
||||
everything this document has said so far. After all, Conduit is older
|
||||
and thus better established, and written in Rust, a Memory Safe™
|
||||
programming language.
|
||||
|
||||
In this section, we will discuss some additional advantages of
|
||||
Telodendria that Conduit lacks.
|
||||
|
||||
### Small Dependency Chain
|
||||
|
||||
Conduit's dependency chain is quite large. What this means is that
|
||||
Conduit depends on a lot of code that it does not control, making it
|
||||
vulnerable to supply chain attacks. A problem with Rust Crates
|
||||
is that they are developer-published, so they don't go through any sort
|
||||
of auditing process like a Debian package would, for example.
|
||||
If any one of the dependencies is
|
||||
hijacked or otherwise compromised, then Conduit itself is compromised
|
||||
and it is likely that this would go unnoticed for quite a while. While
|
||||
one could argue that this is extremely unlikely to happen, sometimes you
|
||||
just don't want to take that risk, especially not if you're deploying a
|
||||
Matrix homeserver, likely for the purpose of secure, private chat.
|
||||
|
||||
Telodendria doesn't pull in any packages from developer repositories, so
|
||||
the risk of supply chain attacks is much lower. It
|
||||
only uses its own code and code provided by the operating system it is running
|
||||
on, which has been vetted by a large number of developers and can be trusted
|
||||
due to the sheer scope of an operating system. A supply chain attack against
|
||||
Telodendria would be a supply chain attack against the entire operating system;
|
||||
at that point, end users have much bigger problems.
|
||||
|
||||
Minimal dependencies doesn't only mitigate supply chain attacks. It also makes
|
||||
maintenance much easier. Telodendria can spend more time writing code than
|
||||
Conduit because Conduit developers have to ensure dependencies stay up to date and
|
||||
when they inevitably break things, Conduit must pause development to fix those.
|
||||
Telodendria doesn't suffer from this problem: because most of the code is developed
|
||||
along side of Telodendria, it can remain as stable or become as volatile as the
|
||||
developers choose. Additionally, because Telodendria is so low-level, the code on
|
||||
which it depends is extremely unlikely to be changed in any significant way,
|
||||
since so many other programs depend on that code.
|
||||
|
||||
### Standardized
|
||||
|
||||
Conduit is written in Rust, which has no formal standard. This makes it less than
|
||||
ideal for long-lived software projects, because it changes frequently and often
|
||||
breaks existing code. Telodendria is written in C, a stable, mature, and standardized
|
||||
language that will always compile the same code the same way, making it more
|
||||
portable and sustainable for the future because we don't ever have to worry about
|
||||
upgrading our toolchain—using standard tools built into most operating systems
|
||||
will suffice.
|
||||
|
||||
Because the language in which Telodendria is written never changes, Telodendria can
|
||||
continually optimize and improve the code, instead of having to fix breaking changes.
|
||||
This ensures that Telodendria's code will last. Rust code becomes obsolete with in a
|
||||
few years at best—programs written in Rust last year probably won't compile or run
|
||||
properly on the latest Rust toolchain. Telodendria, on the other hand, is written in C89,
|
||||
which compiled and ran the same way in 1989 as it does today and will continue to for the
|
||||
foreseeable future.
|
||||
|
||||
### Fast Compile Times
|
||||
|
||||
Rust is well-known for taking an extremely long time to compile moderately-sized
|
||||
programs. Since a Matrix homeserver is such a large project, the compile times would
|
||||
be prohibitively large for rapid development. By writing Telodendria in C, we can take
|
||||
advantage of decades worth of compiler optimizations and speed improvements, resulting
|
||||
in extremely fast builds.
|
||||
|
||||
### Portable
|
||||
|
||||
One does not typically think of C as more portable than something like Rust, but
|
||||
Telodendria is written in such a way that it is. Rust relies on LLVM, which doesn't
|
||||
support some strange or exotic architectures in the same way that a specialized C
|
||||
compiler for those architectures will. This allows users to run Telodendria on the
|
||||
hardware of their choice, even if that hardware is so strange that the modern world
|
||||
has totally left it behind.
|
||||
|
||||
Telodendria doesn't just aim at being lightweight and portable, it aims to empower
|
||||
people to use common hardware that they already have, even if it is typically thought
|
||||
of as underpowered.
|
|
@ -0,0 +1,17 @@
|
|||
# Repository Structure
|
||||
|
||||
This document describes the filesystem layout of the Telodendria source
|
||||
code repository.
|
||||
|
||||
- `Telodendria/`
|
||||
- `Cytoplasm/`: The source code for Cytoplasm, Telodendria's
|
||||
general-purpose support library that provides core functionality.
|
||||
- `contrib/`: Supplemental files, such as example configurations.
|
||||
- `docs/`: All user and developer documentation as Markdown.
|
||||
- `site/`: The official website source code as HTML.
|
||||
- `src/': The C source code and headers for Telodendria.
|
||||
- `Routes/`: Where the Matrix API endpoints are implemented.
|
||||
- `Static/`: Endpoints that just generate static HTML pages.
|
||||
- `include/`: Header files.
|
||||
- `tools/`: Development environment and tools.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Administrator API
|
||||
|
||||
Telodendria provides an administrator API as an extension to the
|
||||
Matrix specification that allows for administrator control over the
|
||||
homeserver. This includes profiling and examining the state of
|
||||
running instances, as well as managing users and media.
|
||||
|
||||
Like Synapse, Telodendria supports designating specific local users as
|
||||
administrators. However, unlike Synapse, Telodendria uses a more
|
||||
fine-grained privilege model that allows a server administrator to
|
||||
delegate specific administration tasks to other users while not
|
||||
compromising and granting them full administrative access to the server.
|
||||
|
||||
To authenticate with the administrator API, simply use your login
|
||||
access token just like you would authenticate any other Matrix client
|
||||
request.
|
||||
|
||||
- [Privileges](privileges.md)
|
||||
- [Configuration](config.md)
|
||||
- [Server Statistics](stats.md)
|
||||
- [Process Control](proc.md)
|
||||
- [Registration Tokens](tokens.md)
|
||||
|
||||
## API Conventions
|
||||
|
||||
Unless otherwise indicated, HTTP response codes that are not `200 Ok`
|
||||
will be accompanied by a standard Matrix API error. Consult the Matrix
|
||||
specification for the format of these errors. The following error
|
||||
conditions are assumed to be possible for all API endpoints listed
|
||||
in the Administrator API documentation:
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 400 | The user is not authenticated, did not provide a valid JSON object, or provided a JSON object with invalid or missing parameters.|
|
||||
| 403 | The user does not have the privileges necessary to carry out the requested action.|
|
||||
| 500 | A fatal server error occurred. Check the logs for more information.|
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# Administrator API: Configuration
|
||||
|
||||
As mentioned in [Setup](../setup.md), Telodendria's configuration is
|
||||
intended to be managed via the configuration API. Consult the
|
||||
[Configuration](../config.md) document for a complete list of supported
|
||||
configuration options. This document simply describes the API used to
|
||||
update the configuration described in that document.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/config`
|
||||
|
||||
Retrieve the current configuration.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The current configuration was successfully retrieved.|
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/config`
|
||||
|
||||
Installs a new configuration. This endpoint validates the request body,
|
||||
ensuring it is a proper configuration, then it replaces the existing
|
||||
configuration with the new one.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The new configuration was successfully installed.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
||||
|
||||
### **PUT** `/_telodendria/admin/v1/config`
|
||||
|
||||
Update the currently installed configuration instead of completely replacing it. This endpoint
|
||||
validates the request body, merges it on top of the current configuration, validates the resulting
|
||||
configuration, then updates it in the database. This is useful when only one or two properties
|
||||
in the configuration needs to be changed.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The new configuration was successfully installed.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
|
@ -0,0 +1,141 @@
|
|||
# Administrator API: Privileges
|
||||
|
||||
This document describes the privilege model and the API endpoints that
|
||||
allow administrators to modify privileges for users.
|
||||
|
||||
## List Of Privileges
|
||||
|
||||
A local user can have any of the following privileges. Unless otherwise
|
||||
indicated, these privileges only grant access to certain parts of the
|
||||
administrator API; the regular Matrix API is unaffected.
|
||||
|
||||
- **DEACTIVATE:** Allows a user to deactivate any other local users.
|
||||
- **ISSUE_TOKENS:** Allows a user to create, modify, and delete
|
||||
registration tokens.
|
||||
- **CONFIG:** Allows a user to modify the Telodendria server daemon's
|
||||
configuration.
|
||||
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own
|
||||
privileges or the privileges of other local users.
|
||||
- **ALIAS:** Allows a user to modify and see room aliases created by
|
||||
other users. By default, users can only manage their own room aliases,
|
||||
but an administrator may wish to take over an alias or remove an
|
||||
offensive alias.
|
||||
- **PROC_CONTROL:** Allows a user to get statistics on the running
|
||||
process, as well as shutdown and resetart the Telodendria daemon
|
||||
itself. Typically this will pair well with **CONFIG**, because there
|
||||
are certain configuration options that require the process to be
|
||||
restarted to take full effect.
|
||||
|
||||
There is also a special "pseudo-privilege":
|
||||
|
||||
- **ALL:** Grants a user all of the aforementioned privileges, as well
|
||||
as privileges that do not yet exist. That is, if an update to
|
||||
Telodendria adds more privileges, users with this privilege will
|
||||
automatically gain those new privileges in addition to having all the
|
||||
existing privileges. This privilege should only be used with
|
||||
fully-trusted users. It is typical for a server administrator to not
|
||||
fully trust anyone else, and be the only one that holds an account with
|
||||
this privilege level.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The following API endpoints are implemented for managing privileges.
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Retrieve the permissions for a user. If the localpart is omitted, then
|
||||
retrieve the privileges for the user that owns the access token being
|
||||
used. Note that the owner of the access token must have the
|
||||
**GRANT_PRIVILEGES** privilege to use this endpoint.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The privileges were successfully retrieved.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by replacing the privileges array
|
||||
with the one specified in the request. Like the **GET** version of this
|
||||
endpoint, the localpart can be omitted to operate on the user that
|
||||
owns the access token.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The privileges were successfully replaced.|
|
||||
|
||||
#### Request Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **PUT** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by adding the privileges
|
||||
specified in the request to the users existing privileges.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The privileges were successfully added.|
|
||||
|
||||
#### Request Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **DELETE** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by removing the privileges
|
||||
specified in the request from the user's existing privileges.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The privileges were successfully removed.|
|
||||
|
||||
#### Request Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# Administrator API: Process Control
|
||||
|
||||
This document describes the administrator APIs that allow a server
|
||||
administrator to manage the Telodendria process itself.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/restart`
|
||||
|
||||
Restart the Telodendria daemon cleanly. This endpoint will respond
|
||||
immediately after signaling to the daemon that it should be restarted
|
||||
as soon as possible. Note that the restart wmay not happen
|
||||
instantaneously, as Telodendria will finish processing all current
|
||||
requests before restarting. Also note that this is not a true restart;
|
||||
the process does not exit and restart, rather, Telodendria simply tears
|
||||
down all its state and then jumps back to the beginning of its code and
|
||||
starts over.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The restart request was successfully sent.|
|
||||
|
||||
On success, this endpoint simply returns an empty JSON object.
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/shutdown`
|
||||
|
||||
Shut down the Telodendria process cleanly. This endpoint will respond
|
||||
immediately after signalling to the daemon that it should be shut
|
||||
down as soon as possible. Note that the shutdown may not happen
|
||||
instantaneously, as Telodendria will finish processing all current
|
||||
requests before shutting down. Also note that once shut down, Telodendria
|
||||
may be automatically restarted by the system's service manager.
|
||||
Otherwise, it will have to be manually restarted. This is a true
|
||||
shutdown; the Telodendria process exits as soon as possible.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The shutdown request was successfully sent.|
|
||||
|
||||
On success, this endpoint simply returns an empty JSON object.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Administrator API: Server Statistics
|
||||
|
||||
The administrator API allows users with the proper privileges to get
|
||||
information about how the server process is performing.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/stats`
|
||||
|
||||
Retrieve basic statistics about the currently running Telodendria
|
||||
process.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The server statistics were successfully retrieved.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `memory_allocated` | `Integer` | The total amount of memory allocated, measured in bytes.|
|
||||
| `version` | `String` | The current version of Telodendria.|
|
|
@ -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.
|
|
@ -0,0 +1,248 @@
|
|||
# Configuration
|
||||
|
||||
Telodendria is designed to be configurable. It is configured using
|
||||
JSON, which is intended to be submitted to the [Administrator
|
||||
API](admin/README.md). This document details Telodendria's configuration
|
||||
JSON format, which is used in both the administrator API and on-disk
|
||||
in the database. The configuration file on the disk in the databsae
|
||||
is `config.json`, though that file should not be edited by hand.
|
||||
Use the API described in
|
||||
[Administrator API → Configuration](admin/config.md).
|
||||
|
||||
## JSON Format
|
||||
|
||||
Telodendria's configuration is just a JSON object in the standard
|
||||
key-value form:
|
||||
|
||||
```json
|
||||
{
|
||||
"serverName": "telodendria.io",
|
||||
"listen": [
|
||||
{
|
||||
"port": 8008
|
||||
}
|
||||
]
|
||||
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
Some keys, called *directives* in this document, have values that are
|
||||
objects themselves.
|
||||
|
||||
## Directives
|
||||
|
||||
Here are the top-level directives:
|
||||
|
||||
- **listen:** `Array`
|
||||
|
||||
An array of listener description objects. Telodendria supports
|
||||
listening on multiple ports, and each port is configured
|
||||
independently of the others. A listener object looks like this:
|
||||
|
||||
- **port:** `integer`
|
||||
|
||||
The port to listen on. Telodendria will bind to all interfaces,
|
||||
so it is recommended to configure your firewall to only allow
|
||||
access on the desired interfaces. Note that Telodendria offers all
|
||||
APIs over each port, including the administrator APIs; there is no
|
||||
way to control which APIs are made available over which ports. If
|
||||
this is a concern, a reverse-proxy such as `relayd` can be placed
|
||||
in front of Telodendria to block access to undesired APIs.
|
||||
|
||||
- **tls:** `Object`
|
||||
|
||||
Telodendria can be compiled with TLS support. If it is, then a
|
||||
particular listener can be set to use TLS for connections. If
|
||||
**tls** is not `null` or `false`, then it can be an object with
|
||||
the following directives:
|
||||
|
||||
- **cert:** `String`
|
||||
|
||||
The full path—or path relative to the data
|
||||
directory—of the certificate file to load. The certificate
|
||||
file should be in the format expected by the platform's TLS
|
||||
library.
|
||||
|
||||
- **key:** `String`
|
||||
|
||||
Same as **cert**, but this should be the private key that matches
|
||||
the certificate being used.
|
||||
|
||||
- **threads:** `Integer`
|
||||
|
||||
How many worker threads to spin up to handle requests for this
|
||||
listener. This should generally be less than the total CPU core
|
||||
count, to prevent overloading the system. The most efficient number
|
||||
of threads ultimately depends on the configuration of the machine
|
||||
running Telodendria, so you may just have to play around with
|
||||
different values here to see which gives the best performance.
|
||||
|
||||
Note that this can be set as low as 0; in that case, the listener
|
||||
will never respond to requests. Each listener needs to have at
|
||||
least one thread to be useful. Also note that Telodendria may spin
|
||||
up additional threads for background work, so the actual total
|
||||
thread count at any given time may exceed the sum of threads
|
||||
specified in the configuration.
|
||||
|
||||
This directive is optional. The default value is `4` in the upstream
|
||||
code, but your software distribution may have patched this to be
|
||||
different.
|
||||
|
||||
- **maxConnections:** `Integer`
|
||||
|
||||
The maximum number of simultanious connections to allow to the
|
||||
daemon. This option prevents the daemon from allocating large
|
||||
amounts of memory in the event that it undergoes a denial of
|
||||
service attack. It is optional, defaults to `32`, and typically
|
||||
does not need to be adjusted.
|
||||
|
||||
- **serverName:** `String`
|
||||
|
||||
Configure the domain name of your homeserver. Note that Matrix
|
||||
servers cannot be migrated to other domains, so once this is set,
|
||||
it should never change unless you want unexpected things to happen
|
||||
or you want to start over. **serverName** should be a DNS name that
|
||||
can be publicly resolved. This directive is required.
|
||||
|
||||
- **pid:** `String`
|
||||
Configure the file Telodendria writes its PID to.
|
||||
|
||||
- **baseUrl:** `String`
|
||||
|
||||
Set the server's base URL. **baseUrl** should be a valid URL,
|
||||
complete with the protocol. It does not need to be the same as the
|
||||
server name; in fact, it is common for a subdomain of the server name
|
||||
to be the base URL for the Matrix homeserver.
|
||||
|
||||
This URL is the URL at which Matrix clients will connect to the
|
||||
server, and is thus served as a part of the `.well-known`
|
||||
manifest.
|
||||
|
||||
This directive is optional. If unspecified, it is automatically
|
||||
deduced from the server name.
|
||||
|
||||
- **identityServer:** `String`
|
||||
|
||||
The identity server that clients should use to perform identity
|
||||
lookups. **identityServer** folows the same rules as **baseUrl**.
|
||||
It also is optional, and is set to be the same as the **baseUrl**
|
||||
if left unspecified.
|
||||
|
||||
- **runAs:** `Object`
|
||||
|
||||
The effective Unix user and group to drop to after binding to the
|
||||
socket and completing any setup that may potentially require
|
||||
elevated privileges. This directive only takes effect if
|
||||
Telodendria is started as the root user, and is used as a security
|
||||
mechanism. If this option is set and Telodendria is started as a
|
||||
non-privileged user, then a warning is printed to the log if that
|
||||
user and group do not match what's specified here. This directive
|
||||
is optional, but should be used as a sanity check even if not
|
||||
running as `root`, just to make sure you have your permissions
|
||||
working properly.
|
||||
|
||||
This directive takes an object with the following directives:
|
||||
|
||||
- **uid:** `String`
|
||||
|
||||
The Unix username to switch to. If **runAs** is specified, this
|
||||
directive is required.
|
||||
|
||||
- **gid:** `String`
|
||||
|
||||
The Unix group to switch to. This directive is optional; if left
|
||||
unspecified, then the value of **uid** is copied.
|
||||
|
||||
- **federation:** `Boolean`
|
||||
|
||||
Whether or not to enable federation with other Matrix homeservers.
|
||||
Matrix by its very nature is a federated protocol, but if you just
|
||||
want to rn your own internal chat server with no contact with the
|
||||
outside, then you can use this option to disable federation. It is
|
||||
highly recommended to set this to `true`, however, if you wish to
|
||||
be able to communicate with users on other Matrix servers. This
|
||||
directive is required.
|
||||
|
||||
- **registration:** `Boolean`
|
||||
|
||||
Whether or not to enable new user registration or not. For security
|
||||
and anti-spam reasons, you can set this to `false`. If you do, you
|
||||
can still allow only certain users to be registered using
|
||||
registration tokens, which can be managed via the administrator API.
|
||||
This directive is required.
|
||||
|
||||
In an ideal world, everyone would run their own Matrix homeserver,
|
||||
so no public registration would ever be required. Unfortunately,
|
||||
not everyone has the means to run their own homeserver, especially
|
||||
because of the fact that IPv4 addresses are becoming increasingly
|
||||
hard to come by. If you would like to provide a service to those
|
||||
that are unable to run their homeserver, then set this to `true`,
|
||||
thereby allowing anyone to create an account.
|
||||
|
||||
Telodendria *should* be capable of handling a large amount of users
|
||||
without difficulty, but it is targetted at smaller deployments.
|
||||
|
||||
- **log:** `Object`
|
||||
|
||||
The logging configuration. Telodendria uses its own logging
|
||||
facility, which can output logs to standard output, a file, or the
|
||||
syslog. This directive is required, and it takes an object with the
|
||||
following directives:
|
||||
|
||||
- **output:** `Enum`
|
||||
|
||||
The log output destination. This can either be `stdout`, `file`,
|
||||
or `syslog`. If set to `file`, Telodendria will log to
|
||||
`telodendria.log` inside the data directory.
|
||||
|
||||
- **level:** `Enum`
|
||||
|
||||
The level of messages to log. Each level shows all the levels above
|
||||
it. The levels are as follows:
|
||||
|
||||
- `error`
|
||||
- `warning`
|
||||
- `notice`
|
||||
- `message`
|
||||
- `debug`
|
||||
|
||||
For example, setting the level to `error` will show only errors,
|
||||
while setting the level to `warning` will show both warnings
|
||||
*and* errors. The `debug` level shows all messages.
|
||||
|
||||
- **timestampFormat:** `Enum`
|
||||
|
||||
If you want to customize the timestamp format shown in the log,
|
||||
or disable it altogether, you can do so via this option. Acceptable
|
||||
values are `none`, `default`, or a formatter string as described
|
||||
by your system's `strftime()` documentation. This option only
|
||||
applies if **log** is `stdout` or `file`.
|
||||
|
||||
- **color:** `Boolean`
|
||||
|
||||
Whether or not to enable colored output on TTYs. Note that ANSI
|
||||
color sequences will not be written to a log file, only a real
|
||||
terminal, so this option only applies if the log is being written
|
||||
to a standard output which is connected to a terminal.
|
||||
|
||||
- **maxCache:** `Integer`
|
||||
|
||||
The maximum size of the cache. Telodendria relies heavily on caching
|
||||
for performance reasons. The cache grows as data is loaded from the
|
||||
data directory. All cache is stored in memory. This option limits the
|
||||
size of the memory cache. If you have a system with a lot of memory
|
||||
to spare, you'll get better performance if this option is set higher.
|
||||
Otherwise, this value should be lowered on systems that have a
|
||||
minimal amount of memory available.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
A number of example configuration files are shipped with Telodendria's
|
||||
source code. They can be found in the `contrib/` directory if you are
|
||||
viewing the source code directly. Otherwise, if you installed
|
||||
Telodendria from a package, it is possible that the example
|
||||
configurations were placed in the default locations for such files on
|
||||
your operating system.
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# Installation
|
||||
|
||||
There are multiple methods of installing Telodendria. Choose the one
|
||||
best suited to your use case.
|
||||
|
||||
## Package Manager Or System Ports
|
||||
|
||||
This is the recommended way to install Telodendria. If your operating
|
||||
system has an official package or port of Telodendria, you should
|
||||
prefer to use that instead of the other methods documented here,
|
||||
because your operating system or software distribution will have
|
||||
already figured out how to best integrate Telodendria with your system.
|
||||
|
||||
Consult your operating system or software distribution's system
|
||||
manual for instructions on how to install packages. Also consult the
|
||||
official repository of your distribution to see if a package is
|
||||
available. If a package exists but it is too out of date for your
|
||||
tastes, please contact the package's maintainer to notify them, or
|
||||
offer to update the package yourself using the
|
||||
[porting instructions](../dev/ports.md).
|
||||
|
||||
If you are maintaining a port or package for an operating system or
|
||||
software distribution, open a pull request to include your
|
||||
platform-specific instructions as a subheader of this section.
|
||||
Eventually, this section should contain basic instructions for the
|
||||
operating systems that have packages or ports.
|
||||
See [Ports](../dev/ports.md) for the project's distribution
|
||||
philosophy.
|
||||
|
||||
## Container
|
||||
|
||||
At this time, Telodendria does not have any officially recommended
|
||||
procedure for running in a container such as Docker or Vagrant. You
|
||||
may find helpful files in the [`contrib/`](../../contrib) directory,
|
||||
however.
|
||||
|
||||
If you are publishing container images, please open a pull request to
|
||||
add your source files to `contrib/`, as well as to add documentation
|
||||
under this section explaining how to get set started.
|
||||
|
||||
## Release Binary
|
||||
|
||||
At this time, Telodendria does not publish any official binaries that
|
||||
can be downloaded. The tentative plan is to eventually provide binaries
|
||||
with each release for a number of supported platforms. When that
|
||||
happens, instructions will be provided here for dealing with the
|
||||
binaries.
|
||||
|
||||
## From Source
|
||||
|
||||
If you would like to build Telodendria from source, you can download
|
||||
the latest release code from the
|
||||
[Releases](/Telodendria/telodendria/releases) page. After extracting
|
||||
the tarball, read
|
||||
[Contributing → Developing → Building & Running](../CONTRIBUTING.md#building-amp-running)
|
||||
for details on how to build Telodendria.
|
|
@ -0,0 +1,60 @@
|
|||
# Initial Set Up
|
||||
|
||||
While Telodendria strives to be extremely simple to deploy and run,
|
||||
in most circumstances a few basic setup steps will be necessary.
|
||||
Telodendria does not have a traditional configuration file like most
|
||||
daemons. Instead, its configuration lives in its database; as such,
|
||||
all configuration happens through the administrator API. This design
|
||||
decision makes Telodendria extremely flexible, because it is possible
|
||||
to re-configure Telodendria without having to manually edit files on
|
||||
the filesystem, thus allowing administrators to secure their server
|
||||
better.
|
||||
|
||||
Please follow the instructions followed here carefully in the order
|
||||
they are presented for the best results.
|
||||
|
||||
This document assumes that you have installed Telodendria using any
|
||||
of the instructions found in [Install](install.md). After installation,
|
||||
follow these steps:
|
||||
|
||||
1. Start Telodendria. If you installed it via a package or container,
|
||||
consult your operating system or container system's documentation. If
|
||||
you are running Telodendria from a release binary or have built it from
|
||||
source, execute the binary directly. If needed, consult the
|
||||
[Usage](usage.md) page for details on how to run Telodendria.
|
||||
1. Assuming that Telodendria started properly, it will spin up and
|
||||
initialize its database directly with a simple—and, importantly,
|
||||
safe—default configuration, as well as a randomly generated,
|
||||
single-use registration token that grants a user all privileges
|
||||
documented in the [Administrator API](admin/README.md) documentation.
|
||||
Consult the log file for this administrator registration token. By
|
||||
default, the log file is located in the data directory, and is named
|
||||
`telodendria.log`.
|
||||
1. Use the registration token to register for an account on the
|
||||
server. This account will be the administrator account. You can do this
|
||||
using the client of your choice, or using tools such as `curl` or
|
||||
`http`, following the Matrix specification for registering accounts.
|
||||
The administrator account behaves just like a normal local account
|
||||
that an ordinary user would have registered on the server, except that
|
||||
it also has all privileges granted to it, so it can make full use of
|
||||
the Administrator API.
|
||||
1. Using the access token granted for the administrator account via
|
||||
the login process, configure Telodendria as descibed in
|
||||
[Configuration](config.md). See the [Administrator API](admin/README.md)
|
||||
documentation for the configuration endpoint details.
|
||||
|
||||
This is the recommended way to set up Telodendria. However, if you
|
||||
wish to bypass the account creation step and want to configure
|
||||
Telodendria by directly writing a configuration file instead of using
|
||||
the administrator API, you can manually create the configuration file
|
||||
in the database before starting Telodendria. Simply create `config.json`
|
||||
following the description in [Configuration](config.md), then start
|
||||
Telodendria.
|
||||
|
||||
While this alternative method may seem simpler and more convenient
|
||||
to some administrators, it is only so in the short-term. Note that this
|
||||
method is not supported, because it gives no access to the
|
||||
administrator API whatsoever, unless you manually modify the database
|
||||
further to give a user admin privileges, which is error-prone and
|
||||
bypasses some of Telodendria's safety mechanisms.
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# Usage
|
||||
|
||||
This document provides general documentation on how to use the
|
||||
`telodendria` server binary, as well as details on how it behaves.
|
||||
The details here will be useful for setting up init systems, running
|
||||
Telodendria in a container, or manually executing the binary for
|
||||
testing or debugging purposes.
|
||||
|
||||
## Command Line Options
|
||||
|
||||
Typically, Telodendria is controlled via the
|
||||
[Administrator API](admin/README.md), but the Telodendria binary does include
|
||||
a few command line options, which can be used in init scripts or for
|
||||
debugging purposes.
|
||||
|
||||
The command line arguments are as follows:
|
||||
|
||||
- **`-d <dir>`** Specify the data directory to use. All persistent
|
||||
storage that Telodendria requires is saved to and loaded from here.
|
||||
- **`-V`** Only print the version information header and then quit
|
||||
with a success exit code.
|
||||
- **`-v`** Verbose mode. This overrides the configuration and sets the
|
||||
log level to `debug`. It also enables additional logging of memory
|
||||
operations, which can be useful for debugging.
|
||||
|
||||
Before proposing additional command line arguments, consider whether or
|
||||
not the functionality requested can be provided via a (potentially new
|
||||
and as of yet uncreated) administrator API endpoint.
|
||||
|
||||
## Environment
|
||||
|
||||
Telodendria does not read any environment variables. All configuration
|
||||
should be done via the [Configuration API](config.md).
|
||||
|
||||
## Signals
|
||||
|
||||
Telodendria recognizes and responds to a number of signals:
|
||||
|
||||
- **`PIPE`:** This signal is ignored, because all I/O errors should
|
||||
already be handled properly.
|
||||
- **`USR1`:** Perform a soft restart by shutting down the HTTP servers
|
||||
and resetting the program state. Note that the daemon process does
|
||||
not exit.
|
||||
- **`TERM`:** Perform a clean shutdown after all existing connections
|
||||
are closed.
|
||||
- **`INT`:** Same as `TERM`.
|
||||
|
||||
Any other signals are not explicitly handled, so they have the
|
||||
default behavior as defind by the operating system.
|
||||
|
||||
## Exit Status
|
||||
|
||||
Telodendria exits with a non-0 exit code if the configuration file is
|
||||
invalid, or one or more of required paths or files is inaccessible.
|
||||
Telodendria will print an error to the log and then terminate
|
||||
abnormally.
|
||||
|
||||
Telodendria exits with a code of 0 if the configuration file is valid,
|
||||
all paths and files required are accessible, and the HTTP listener
|
||||
starts as intended. If Telodendria is sent a signal that it catches
|
||||
after it begins servicing requests, it will still exit normally after
|
||||
it safely shuts down, because the bootstrap process completed
|
||||
successfully, and by all accounts, it ran normally and exitted
|
||||
normally.
|
|
@ -1 +0,0 @@
|
|||
mandoc.db
|
|
@ -0,0 +1,18 @@
|
|||
.Dd $Mdocdate: May 6 2023 $
|
||||
.Dt HTTP-DEBUG-SERVER 1
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm http-debug-server
|
||||
.Nd A simple HTTP server that logs requests to the standard output.
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
spins up an HTTP server, listening on port 8008, in the exact same
|
||||
manner as Telodendria itself. Any request it receives is written to
|
||||
the standard output, and an empty JSON object is returned to the
|
||||
client.
|
||||
.Pp
|
||||
This command exists just to test the HTTP server API during
|
||||
development. It probably serves no other practical purpose.
|
||||
.Sh SEE ALSO
|
||||
.Xr HttpServer 3
|
|
@ -0,0 +1,111 @@
|
|||
.Dd $Mdocdate: March 12 2023 $
|
||||
.Dt JSON 1
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm json
|
||||
.Nd A simple command line utility for parsing and generating JSON.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl s Ar query
|
||||
.Op Fl e Ar str
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a simple command line utility for dealing with JSON. It is
|
||||
somewhat inspired by
|
||||
.Xr jq 1 ,
|
||||
but is not compatible in any way with it.
|
||||
.Nm
|
||||
is designed to be much simpler than
|
||||
.Xr jq 1 ,
|
||||
and is built on Telodendria's own
|
||||
.Xr Json 3
|
||||
API. It primarily exists to ease development of Telodendria, and
|
||||
to make development possible without having to install any external
|
||||
tools.
|
||||
.Pp
|
||||
The options are as follows. Unless stated otherwise, these options
|
||||
are mutually exclusive, and the last one specified takes precedence.
|
||||
All positional parameters are ignored.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl s Ar query
|
||||
Use
|
||||
.Va query
|
||||
to query a field from a JSON object given on the standard input.
|
||||
The query syntax very vaguely resembles C code, but it is much
|
||||
more primitive. Multiple queries are separated by an arrow
|
||||
(``->''). This makes it trivial to drill down into nested
|
||||
objects and arrays.
|
||||
.Pp
|
||||
To select a value from an object, just specify the key. To select
|
||||
an element from an array specify the key whose value is the array,
|
||||
and then use the C square bracket syntax to select an element.
|
||||
.Pp
|
||||
A number of ``functions'' exist to make
|
||||
.Nm
|
||||
more versatile. Functions are called by prefacing the key with
|
||||
a ``@'' symbol. Functions can appear anywhere in the query, provided
|
||||
they make sense within the context of the JSON object being processed.
|
||||
The available functions are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It keys
|
||||
When applied to an object, outputs an array of keys.
|
||||
.It length
|
||||
When applied to an array, outputs the number of elements in the
|
||||
array. When applied to a string, returns the number of bytes
|
||||
needed to store the decoded version of the string.
|
||||
.It decode
|
||||
When applied to a string, outputs the decoded version of the
|
||||
string.
|
||||
.El
|
||||
.Pp
|
||||
When a key is prefaced with the ``^'' symbol, then instead of getting
|
||||
the value at that key, it is removed from the object, and the new
|
||||
object is passed along.
|
||||
.It Fl e Ar str
|
||||
Encode
|
||||
.Va str
|
||||
as a JSON string and print it to standard output. This is useful for
|
||||
generating JSON with shell scripts.
|
||||
.El
|
||||
.Pp
|
||||
If no options are specified, then the default behavior of
|
||||
.Nm
|
||||
is to read a JSON object given on the standard input and pretty-print
|
||||
it to the standard output, or print an error to standard error if
|
||||
the given input is invalid.
|
||||
.Sh EXAMPLES
|
||||
.Pp
|
||||
Get the error string of an error returned by a Matrix API endpoint:
|
||||
.Bd -literal -offset indent
|
||||
json -s 'error->@decode'
|
||||
.Ed
|
||||
.Pp
|
||||
Get the number of stages in the first flow listed in a list
|
||||
of user-interactive authentication flows:
|
||||
.Bd -literal -offset indent
|
||||
json -s 'flows[0]->stages->@length'
|
||||
.Ed
|
||||
.Pp
|
||||
Get the first stage of the first flow listed in a list
|
||||
of user-interactive authentication flows:
|
||||
.Bd -literal -offset indent
|
||||
json -s 'flows[0]->stages[0]->@decode'
|
||||
.Ed
|
||||
.Pp
|
||||
List the keys in a JSON object:
|
||||
.Bd -literal -offset indent
|
||||
json -s '@keys'
|
||||
.Ed
|
||||
.Pp
|
||||
Get the number of keys in a JSON object:
|
||||
.Bd -literal -offset indent
|
||||
json -s '@keys->@length'
|
||||
.Ed
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
exits with
|
||||
.Va EXIT_SUCCESS
|
||||
if all command line options were valid and the given JSON object
|
||||
parses successfully. It exits with
|
||||
.Va EXIT_FAILURE
|
||||
in all other scenarios.
|
|
@ -0,0 +1,34 @@
|
|||
.Dd $Mdocdate: April 29 2023 $
|
||||
.Dt TT 1
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm tt
|
||||
.Nd Make authenticated Matrix requests.
|
||||
.Sh SYNPOSIS
|
||||
.Nm
|
||||
.Op request-path
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is an extremely simple wrapper over
|
||||
.Xr http 1
|
||||
and
|
||||
.Xr json 1
|
||||
that automatically registers a user and continues to log in as that
|
||||
user. It obtains an access token that it uses to authenticate the
|
||||
given request, and then logs out when the request has completed. It
|
||||
also doesn't require the whole URL to be typed; only the path name
|
||||
is needed.
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag
|
||||
.It Ev METH
|
||||
The request method to use for the user-specified request.
|
||||
.It Ev DATA
|
||||
The data to pass to
|
||||
.Xr http 1 .
|
||||
This can either be a string, or a file. Consult the
|
||||
.Xr http 1
|
||||
page for details.
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr http 1 ,
|
||||
.Xr json 1
|
132
man/man3/Array.3
132
man/man3/Array.3
|
@ -1,132 +0,0 @@
|
|||
.Dd $Mdocdate: November 24 2022 $
|
||||
.Dt ARRAY 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Array
|
||||
.Nd A simple dynamic array data structure.
|
||||
.Sh SYNOPSIS
|
||||
.In Array.h
|
||||
.Ft Array *
|
||||
.Fn ArrayCreate "void"
|
||||
.Ft void
|
||||
.Fn ArrayFree "Array *"
|
||||
.Ft int
|
||||
.Fn ArrayTrim "Array *"
|
||||
.Ft size_t
|
||||
.Fn ArraySize "Array *"
|
||||
.Ft void *
|
||||
.Fn ArrayGet "Array *" "size_t"
|
||||
.Ft int
|
||||
.Fn ArrayInsert "Array *" "void *" "size_t"
|
||||
.Ft int
|
||||
.Fn ArrayAdd "Array *" "void *"
|
||||
.Ft void *
|
||||
.Fn ArrayDelete "Array *" "size_t"
|
||||
.Ft void
|
||||
.Fn ArraySort "Array *" "int (*) (void *, void *)"
|
||||
.Ft Array *
|
||||
.Fn ArrayFromVarArgs "size_t" "va_list"
|
||||
.Ft Array *
|
||||
.Fn ArrayDuplicate "Array *"
|
||||
.Sh DESCRIPTION
|
||||
These functions implement a simple array data structure that
|
||||
is automatically resized as necessary when new values are added.
|
||||
This implementation does not actually store the values of the
|
||||
items in it; it only stores pointers to the data. As such, you will
|
||||
still have to manually maintain all your data. The advantage of this
|
||||
is that these functions don't have to copy data, and thus don't care
|
||||
how big the data is. Furthermore, arbitrary data can be stored in the
|
||||
array.
|
||||
.Pp
|
||||
This array implementation is optimized for storage space and appending.
|
||||
Deletions are expensive in that all the items of the list above a deletion
|
||||
are moved down to fill the hole where the deletion occurred. Insertions are
|
||||
also expensive in that all the elements above the given index must be shifted
|
||||
up to make room for the new element.
|
||||
.Pp
|
||||
Due to these design choices, this array implementation is best suited to
|
||||
linear writing, and then linear or random reading.
|
||||
.Pp
|
||||
These functions operate on an array structure which is opaque to the
|
||||
caller.
|
||||
.Pp
|
||||
.Fn ArrayCreate
|
||||
and
|
||||
.Fn ArrayFree
|
||||
allocate and deallocate an array, respectively.
|
||||
Note that
|
||||
.Fn ArrayFree
|
||||
does not free any of the values stored in the array; it is the caller's
|
||||
job to manage the memory for each item. Typically, the caller would
|
||||
iterate over all the items in the array and free them before freeing
|
||||
the array.
|
||||
.Fn ArrayTrim
|
||||
reduces the amount of unused memory by calling
|
||||
.Xr realloc 3
|
||||
on the internal structure to perfectly fit the elements in the array. It
|
||||
is intended to be used by functions that return relatively read-only arrays
|
||||
that will be long-lived.
|
||||
.Pp
|
||||
.Fn ArrayInsert
|
||||
and
|
||||
.Fn ArrayDelete
|
||||
are the main functions used to modify the array.
|
||||
.Fn ArrayAdd
|
||||
is a convenience method that simply appends a value to the end of the
|
||||
array. It uses
|
||||
.Fn ArrayInsert .
|
||||
The array can also be sorted by using
|
||||
.Fn ArraySort ,
|
||||
which takes a pointer to a function that compares elements. The function
|
||||
should take two
|
||||
.Dv void
|
||||
pointers as parameters, and return an integer. The return value indicates
|
||||
to the algorithm how the elements relate to each other. A return value of
|
||||
0 indicates the elements are identical. A return value greater than 0
|
||||
indicates that the first item is "bigger" than the second item and should
|
||||
thus appear after it in the array, and a value less than zero indicates
|
||||
the opposite: the second element should appear after the first in the array.
|
||||
.Pp
|
||||
.Fn ArrayGet
|
||||
is used to get the element at the specified index.
|
||||
.Pp
|
||||
.Fn ArrayFromVarArgs
|
||||
is used to convert a variadic arguments list into an Array. In many
|
||||
cases, the Array API is much easier to work with than
|
||||
.Fn va_arg
|
||||
and friends.
|
||||
.Pp
|
||||
.Fn ArrayDuplicate
|
||||
duplicates an existing array. Note that Arrays only hold
|
||||
pointers to data, not the data itself, so the duplicated array will
|
||||
point to the same places in memory as the original array.
|
||||
.Sh RETURN VALUES
|
||||
.Fn ArrayCreate ,
|
||||
.Fn ArrayFromVarArgs ,
|
||||
and
|
||||
.Fn ArrayDuplicate
|
||||
return a pointer on the heap to a newly allocated array structure, or
|
||||
.Dv NULL
|
||||
if the allocation fails.
|
||||
.Pp
|
||||
.Fn ArrayGet
|
||||
and
|
||||
.Fn ArrayDelete
|
||||
return pointers to values that were put into the array, or
|
||||
.Dv NULL
|
||||
if the provided array is
|
||||
.Dv NULL
|
||||
or the provided index was out of bounds.
|
||||
.Fn ArrayDelete
|
||||
returns the element at the specified index after removing it so that
|
||||
it can be properly handled by the caller.
|
||||
.Pp
|
||||
.Fn ArrayTrim ,
|
||||
.Fn ArrayInsert ,
|
||||
and
|
||||
.Fn ArrayAdd
|
||||
return a boolean value indicating their status. They return a value of zero
|
||||
on failure, and a non-zero value on success.
|
||||
.Sh SEE ALSO
|
||||
.Xr HashMap 3 ,
|
||||
.Xr Queue 3
|
|
@ -1,81 +0,0 @@
|
|||
.Dd $Mdocdate: September 30 2022 $
|
||||
.Dt BASE64 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Base64
|
||||
.Nd A simple base64 encoder/decoder with "unpadded base64" support.
|
||||
.Sh SYNOPSIS
|
||||
.In Base64.h
|
||||
.Ft size_t
|
||||
.Fn Base64EncodedSize "size_t"
|
||||
.Ft size_t
|
||||
.Fn Base64DecodedSize "const char *" "size_t"
|
||||
.Ft char *
|
||||
.Fn Base64Encode "const char *" "size_t"
|
||||
.Ft char *
|
||||
.Fn Base64Decode "const char *" "size_t"
|
||||
.Ft void
|
||||
.Fn Base64Unpad "char *" "size_t"
|
||||
.Ft int
|
||||
.Fn Base64Pad "char **" "size_t"
|
||||
.Sh DESCRIPTION
|
||||
This is an efficient yet simple base64 encoding and decoding
|
||||
library that supports regular base64, as well as the Matrix
|
||||
specification's extension to base64, called "unpadded base64."
|
||||
.Nm provides the ability to convert between the two, instead of
|
||||
just implementing "unpadded base64."
|
||||
.Pp
|
||||
.Fn Base64EncodedSize
|
||||
and
|
||||
.Fn Base64DecodedSize
|
||||
compute the amount of characters needed to store an encoded or
|
||||
decoded message, respectively. Both functions take the size of
|
||||
the message being encoded or decoded, but
|
||||
.Fn Base64DecodedSize
|
||||
also takes a pointer to an encoded string, because a few bytes of
|
||||
the string need to be read in order to compute the size.
|
||||
.Pp
|
||||
.Fn Base64Encode
|
||||
and
|
||||
.Fn Base64Decode
|
||||
do the actual work of encoding and decoding base64 data. They both
|
||||
take a string and its length.
|
||||
.Pp
|
||||
.Fn Base64Unpad
|
||||
and
|
||||
.Fn Base64Pad
|
||||
are used to implement Matrix unpadded base64.
|
||||
.Fn Base64Unpad
|
||||
takes a valid base64 string and strips off the trailing equal signs,
|
||||
as per the specification.
|
||||
.Fn Base64Pad
|
||||
does the opposite; it takes an unpadded base64 input string, and pads
|
||||
it with equal signs as necessary, so that it can be properly decoded
|
||||
with
|
||||
.Fn Base64Decode
|
||||
if necessary. However, the Matrix specification envisons unpadded base64
|
||||
as opaque; that is, once it's encoded, it never needs to be decoded.
|
||||
In practice, a homeserver might need to decode an unpadded base64 string.
|
||||
.Sh RETURN VALUES
|
||||
.Fn Base64EncodedSize
|
||||
and
|
||||
.Fn Base64DecodedSize
|
||||
simply return unsigned integers representing a number of bytes generated
|
||||
from a simple computation.
|
||||
.Pp
|
||||
.Fn Base64Encode
|
||||
and
|
||||
.Fn Base64Decode
|
||||
return pointers to new strings, allocated on the heap, or
|
||||
.Dv NULL
|
||||
if a heap allocation fails. These pointers must be
|
||||
.Xr free 3 -ed
|
||||
at some point when they are no longer needed.
|
||||
.Pp
|
||||
.Fn Base64Unpad
|
||||
modifies the passed string in-place. It thus has no return value, because
|
||||
it cannot fail. If the passed pointer is invalid, the behavior is undefined.
|
||||
.Fn Base64Pad
|
||||
returns a boolean value indicating whether the pad operation was successful.
|
||||
In practice, this function will only fail if a bigger string is necessary, but
|
||||
could not be automatically allocated on the heap.
|
|
@ -1,69 +0,0 @@
|
|||
.Dd $Mdocdate: November 30 2022 $
|
||||
.Dt CANONICALJSON 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm CanonicalJson
|
||||
.Nd An extension of JSON that produces the Matrix spec's "canonical" JSON.
|
||||
.Sh SYNOPSIS
|
||||
.In CanonicalJson.h
|
||||
.Ft int
|
||||
.Fn CanonicalJsonEncode "HashMap *" "FILE *"
|
||||
.Ft char *
|
||||
.Fn CanonicalJsonEncodeToString "HashMap *"
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
is an extension of
|
||||
.Xr Json 3
|
||||
that is specifically designed to produce the Matrix specification's
|
||||
"canonical" JSON.
|
||||
.Pp
|
||||
Canonical JSON is defined as JSON that:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
Does not have any unecessary whitespace.
|
||||
.It
|
||||
Has all object keys lexicographically sorted.
|
||||
.It
|
||||
Does not contain any floating point numerical values.
|
||||
.El
|
||||
.Pp
|
||||
The regular JSON encoder has no such rules, because normally they are
|
||||
not needed. However, Canonical JSON is needed to consistently sign JSON
|
||||
objects.
|
||||
.Pp
|
||||
.Fn CanonicalJsonEncode
|
||||
encodes a JSON object following the rules of Canonical Json. See the
|
||||
documentation for
|
||||
.Fn JsonEncode ,
|
||||
documented in
|
||||
.Xr Json 3
|
||||
for more details on how JSON encoding operates. This function exists
|
||||
as an alternative to
|
||||
.Fn JsonEncode ,
|
||||
but should not be preferred to it in most circumstances. It is a lot
|
||||
more costly, as it must lexicographically sort all keys and strip out
|
||||
float values. If at all possible, use
|
||||
.Fn JsonEncode
|
||||
because it is much cheaper both in terms of memory and CPU time.
|
||||
.Pp
|
||||
.Fn CanonicalJsonEncodeToString
|
||||
encodes a JSON object to a string.
|
||||
.Xr Json 3
|
||||
doesn't have any way to send JSON to a string, because there's
|
||||
absolutely no reason to handle JSON strings in most cases. However,
|
||||
the sole reason Canonical JSON exists is so that JSON objects can
|
||||
be signed in a consistent way. Thus, you need a string to pass to
|
||||
the signing function.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn CanonicalJsonEncode
|
||||
returns whether or not the JSON encoding operation was sucessful.
|
||||
This function will fail only if NULL was given for any parameter.
|
||||
Otherwise, if an invalid pointer is given, undefined behavior results.
|
||||
.Pp
|
||||
.Fn CanonicalJsonEncodeToString
|
||||
returns a C string containing the canonical JSON representation of
|
||||
the given object, or NULL if the encoding failed.
|
||||
.Sh SEE ALSO
|
||||
.Xr Json 3
|
|
@ -1,96 +0,0 @@
|
|||
.Dd $Mdocdate: December 24 2022 $
|
||||
.Dt CRON 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Cron
|
||||
.Nd Basic periodic job scheduler.
|
||||
.Sh SYNOPSIS
|
||||
.In Cron.h
|
||||
.Ft Cron *
|
||||
.Fn CronCreate "unsigned long"
|
||||
.Ft void
|
||||
.Fn CronOnce "Cron *" "void (*) (void *)" "void *"
|
||||
.Ft void
|
||||
.Fn CronEvery "Cron *" "unsigned long" "void (*) (void *)" "void *"
|
||||
.Ft void
|
||||
.Fn CronStart "Cron *"
|
||||
.Ft void
|
||||
.Fn CronStop "Cron *"
|
||||
.Ft void
|
||||
.Fn CronFree "Cron *"
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
is an extremely basic job scheduler. So basic, in fact,
|
||||
that it runs all jobs on a single thread, which means that it
|
||||
is intended for short-lived jobs. In the future,
|
||||
.Nm
|
||||
might be extended to support a one-thread-per-job model, but
|
||||
for now, jobs should consider that they are sharing their
|
||||
thread, so they should not be long-running jobs.
|
||||
.Pp
|
||||
.Nm
|
||||
works by "ticking" at an interval defined by the caller of
|
||||
.Fn CronCreate .
|
||||
At each tick, all the jobs are queried, and if they are due
|
||||
to run again, their function is executed. As much as possible,
|
||||
.Nm
|
||||
tries to tick at constant intervals, however it is possible that
|
||||
a job may overrun the tick duration. If this happens,
|
||||
.Nm
|
||||
ticks again immediately after all the jobs for the previous tick
|
||||
have completed. This is in an effort to compensate for the lost
|
||||
time, however it is important to note that when jobs overrun the
|
||||
tick interval, the interval is pushed back. In this way,
|
||||
.Nm
|
||||
is best suited for scheduling jobs that should happen
|
||||
"approximately" every so often; it is not a real-time scheduler
|
||||
by any means.
|
||||
.Pp
|
||||
.Fn CronCreate
|
||||
creates a new
|
||||
.Nm
|
||||
object that all the other functions use. Like most of the other
|
||||
APIs in this project, it must be freed with
|
||||
.Fn CronFree
|
||||
when it is no longer needed.
|
||||
.Pp
|
||||
Jobs can be scheduled with
|
||||
.Fn CronOnce
|
||||
and
|
||||
.Fn CronEvery .
|
||||
.Fn CronOnce
|
||||
schedules a one-off job to be executed only at the next tick, and
|
||||
then discarded. This is useful for scheduling tasks that only have
|
||||
to happen once, or very infrequently depending on conditions other
|
||||
than the current time, but don't have to happen immediately. The
|
||||
caller simply indicates that it wishes for the task to execute at
|
||||
some time in the future. How far into the future this practically
|
||||
ends up being is determined by how long it takes other jobs to
|
||||
finish, and what the tick interval is.
|
||||
.Pp
|
||||
.Fn CronEvery
|
||||
schedules a repetitive task to be executed at approximately the
|
||||
given interval. As stated above, this is a fuzzy interval; depending
|
||||
on the jobs being run and the tick interval, tasks may not happen
|
||||
at exactly the scheduled time, but they will eventually happen.
|
||||
.Pp
|
||||
.Fn CronStart
|
||||
and
|
||||
.Fn CronStop
|
||||
start and stop the ticking, respectively.
|
||||
.Fn CronFree
|
||||
discards all the job references and frees all memory associated
|
||||
with the given instance of the
|
||||
.Nm
|
||||
instance.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn CronCreate
|
||||
returns a reference to a
|
||||
.Nm ,
|
||||
or NULL if it was unable to allocate memory for it.
|
||||
.Pp
|
||||
The other functions in this header don't return anything. They
|
||||
are assumed to usually work, unless their input is obviously
|
||||
wrong.
|
121
man/man3/Db.3
121
man/man3/Db.3
|
@ -1,121 +0,0 @@
|
|||
.Dd $Mdocdate: March 6 2023 $
|
||||
.Dt DB 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Db
|
||||
.Nd A minimal flat-file database with object locking and an efficient cache.
|
||||
.Sh SYNOPSIS
|
||||
.In Db.h
|
||||
.Ft Db *
|
||||
.Fn DbOpen "char *" "size_t"
|
||||
.Ft void
|
||||
.Fn DbClose "Db *"
|
||||
.Ft DbRef *
|
||||
.Fn DbCreate "Db *" "size_t" "..."
|
||||
.Ft int
|
||||
.Fn DbDelete "Db *" "size_t" "..."
|
||||
.Ft DbRef *
|
||||
.Fn DbLock "Db *" "size_t" "..."
|
||||
.Ft int
|
||||
.Fn DbUnlock "Db *" "DbRef *"
|
||||
.Ft int
|
||||
.Fn DbExists "Db *" "size_t" "..."
|
||||
.Ft Array *
|
||||
.Fn DbList "Db *" "size_t" "..."
|
||||
.Ft void
|
||||
.Fn DbListFree "Array *"
|
||||
.Ft HashMap *
|
||||
.Fn DbJson "DbRef *"
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
Telodendria operates on a flat-file database instead of a traditional relational
|
||||
database. This greatly simplifies the persistent storage code, and creates a
|
||||
relatively basic API, described by this page.
|
||||
.Pp
|
||||
.Fn DbOpen
|
||||
and
|
||||
.Fn DbClose
|
||||
open and close a data directory.
|
||||
.Fn DbOpen
|
||||
takes the path to open, and a cache size in bytes. This API relies heavily on
|
||||
caching, so the cache must be greater than the DB_MIN_CACHE preprocessor
|
||||
constant.
|
||||
.Pp
|
||||
.Fn DbCreate
|
||||
and
|
||||
.Fn DbLock
|
||||
load data objects from the database, with the notable difference being that
|
||||
.Fn DbCreate
|
||||
will fail if an object already exists, and
|
||||
.Fn DbLock
|
||||
will fail if an object does not exist. These are both variadic functions that
|
||||
take a variable number of C strings, with the exact number being specified by
|
||||
the second paramter. These C strings are used to generate a filesystem path from
|
||||
which an object is loaded, unless it is already in the cache.
|
||||
.Pp
|
||||
Objects, when loaded, are locked both in memory and on disk, so that other threads
|
||||
or processes cannot access them while they are locked. This is to ensure data
|
||||
integrity.
|
||||
.Pp
|
||||
.Fn DbUnlock
|
||||
unlocks an object and returns it back to the database, which syncs it to the
|
||||
filesystem and caches it, if it isn't in the cache already.
|
||||
.Pp
|
||||
.Fn DbExists
|
||||
checks the existence of the given database object in a more efficient
|
||||
manner than attempting to lock it with
|
||||
.Fn DbLock .
|
||||
.Fn DbExists
|
||||
does not lock the object, nor does it load it into memory if it exists. It
|
||||
takes the same arguments as
|
||||
.Fn DbLock
|
||||
and
|
||||
.Fn DbUnlock .
|
||||
.Pp
|
||||
.Fn DbJson
|
||||
converts a database reference into JSON. At this time, the database actually
|
||||
stores objects as JSON, so this just returns an internal pointer, but in the
|
||||
future it may have to be generated by decompressing a binary blob, or something
|
||||
of that nature.
|
||||
.Pp
|
||||
.Fn DbDelete
|
||||
completely removes an object from the database. It purges it from both the
|
||||
cache and the disk as soon as no more references to it are open.
|
||||
.Pp
|
||||
.Fn DbList
|
||||
lists all of the objects at a given path. Unlike the other varargs
|
||||
functions, it does not take a path to a specific object; it takes
|
||||
a directory to be iterated. Note that the resulting list only contains
|
||||
the objects in that directory, not subdirectories.
|
||||
.Fn DbListFree
|
||||
frees the list returned by this function.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn DbOpen
|
||||
returns a reference to a database pointer, or NULL if there was an error
|
||||
allocating memory, or opening the given directory with the given cache size.
|
||||
.Pp
|
||||
.Fn DbCreate
|
||||
and
|
||||
.Fn DbLock
|
||||
return a database reference, or NULL if there was an error obtaining a reference
|
||||
to the specified object.
|
||||
.Pp
|
||||
.Fn DbUnlock
|
||||
returns a boolean value indicating whether or not the reference was successfully
|
||||
unlocked. If the unlock is successful, then a non-zero value is returned. If it
|
||||
isn't, 0 is returned, and it is up to the caller to decide how to proceed.
|
||||
.Pp
|
||||
.Fn DbDelete
|
||||
follows the same return value conventions as
|
||||
.Fn DbUnlock ;
|
||||
it reports the status of the deletion operation as a boolean value.
|
||||
.Pp
|
||||
.Fn DbList
|
||||
returns an array of strings, or NULL if there was a memory or
|
||||
filesystem error.
|
||||
.Pp
|
||||
.Fn DbJson
|
||||
returns a JSON object. Consult
|
||||
.Xr Json 3
|
||||
for the API used to manipulate this object.
|
|
@ -1,146 +0,0 @@
|
|||
.Dd $Mdocdate: October 11 2022 $
|
||||
.Dt HASHMAP 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm HashMap
|
||||
.Nd A simple hash map implementation.
|
||||
.Sh SYNOPSIS
|
||||
.In HashMap.h
|
||||
.Ft HashMap *
|
||||
.Fn HashMapCreate "void"
|
||||
.Ft void
|
||||
.Fn HashMapFree "HashMap *"
|
||||
.Ft void
|
||||
.Fn HashMapLoadSet "HashMap *" "float"
|
||||
.Ft void
|
||||
.Fn HashMapFunctionSet "HashMap *" "unsigned long (*) (const char *)"
|
||||
.Ft void *
|
||||
.Fn HashMapSet "HashMap *" "char *" "void *"
|
||||
.Ft void *
|
||||
.Fn HashMapGet "HashMap *" "char *"
|
||||
.Ft void *
|
||||
.Fn HashMapDelete "HashMap *" "char *"
|
||||
.Ft int
|
||||
.Fn HashMapIterate "HashMap *" "char **" "void **"
|
||||
.Sh DESCRIPTION
|
||||
This is the public interface for Telodendria's hash map implementation.
|
||||
This hash map is designed to be simple, well documented, and generally
|
||||
readable and understandable, yet also performant enough to be useful,
|
||||
because it is used extensively in Telodendria.
|
||||
.Pp
|
||||
Fundamentally, this is an entirely generic map implementation. It can
|
||||
be used for many general purposes, but it is designed to only implement
|
||||
the features that Telodendria needs to function well. One example of a
|
||||
Telodendria-specific feature is that keys are
|
||||
.Dv NULL -terminated
|
||||
strings, not any arbitrary data.
|
||||
.Pp
|
||||
These functions operate on an opaque
|
||||
.Dv HashMap
|
||||
structure, which the caller has no knowledge about.
|
||||
.Pp
|
||||
.Fn HashMapCreate
|
||||
creates a new hash map that is ready to be used with the rest of the
|
||||
hash map functions.
|
||||
.Fn HashMapFree
|
||||
frees this hash map, such that it becomes invalid and any future use
|
||||
with the functions in this API results in undefined behavior. Note that
|
||||
it does not free the keys or values stored in the hash map, since this
|
||||
implementation has no way of knowing what is actually stored in it, and
|
||||
where it is located. You should use
|
||||
.Fn HashMapIterate
|
||||
to free the values appropriately.
|
||||
.Pp
|
||||
.Fn HashMapMaxLoadSet
|
||||
controls the maximum load of the hash map before it is expanded.
|
||||
When the hash map reaches the given capacity, it is grown. You don't
|
||||
want to only grow hash maps when they are full, because that makes
|
||||
them perform very poorly. The maximum load value is a percentage of how
|
||||
full the hash map is, and it should be between 0
|
||||
and 1, where 0 means that no elements will cause the map to be expanded,
|
||||
and 1 means that the hash map must be completely full before it is
|
||||
expanded. The default maximum load on a new
|
||||
.Dv HashMap
|
||||
object is 0.75, which should be good enough for most purposes, but
|
||||
if you need finer tuning, feel free to play with the max load with
|
||||
this function. The changes take effect on the next insert.
|
||||
.Pp
|
||||
.Fn HashMapFunctionSet
|
||||
allows the given hash map to use a custom hashing function. New hash
|
||||
maps have a sane hashing function that should work okay for most use
|
||||
cases, but if you have a better hash function, it can be specified with
|
||||
this function. Do not change the hash function after keys have been
|
||||
added; doing so results in undefined behavior. Only set the hash
|
||||
function immediately after constructing a new hash map.
|
||||
.Pp
|
||||
.Fn HashMapSet
|
||||
sets the given string key to the given value. Note neither the key nor the
|
||||
value is copied into the hash map's own memory space; only pointers are
|
||||
stored. It is the caller's job to ensure that the key and value memory
|
||||
remains valid for the life of the HashMap, and are freed when they are no
|
||||
longer needed.
|
||||
.Fn HashMapGet
|
||||
retrieves the value for the given key and .Fn HashMapDelete
|
||||
removes a value from a given key.
|
||||
.Pp
|
||||
.Fn HashMapIterate
|
||||
iterates over all the keys and values of a hash map. This function works
|
||||
very similarly to
|
||||
.Xr getopt 3 ,
|
||||
where calls are repeatedly made in a
|
||||
.Dv while
|
||||
loop until there are no more items to go over. The difference is that this
|
||||
function does not rely on globals; it takes pointer pointers, and stores all
|
||||
necessary state inside the hash map structure itself. Note that this does not
|
||||
make this function thread-safe; two threads cannot be iterating over any given
|
||||
hash map at the same time, though they can be iterating over different hash
|
||||
maps at the same time.
|
||||
.Pp
|
||||
This function can be tricky to use in some scenarios, as it continues where
|
||||
it left off on each call, until there are no more elements to go through in
|
||||
the hash map. If you are not iterating over the entire map in one go, and
|
||||
happen to break the loop, then the next time you attempt to iterate the
|
||||
hash map, you'll start somewhere in the middle. Thus, it's recommended to
|
||||
always iterate over the entire hash map if you're going to use this
|
||||
function. Also note that the behavior of this function is undefined if
|
||||
insertions or deletions occur during the iteration. It may work fine; it may
|
||||
not. That functionality has not been tested.
|
||||
.Pp
|
||||
.Fn HashMapIterate
|
||||
takes a pointer to a string ponter, and a pointer to a value pointer. When
|
||||
iterating over the keys and values of the hash map, it sets these pointers
|
||||
to the current position in the map so that the caller can use them for his
|
||||
own purposes.
|
||||
.Sh RETURN VALUES
|
||||
.Fn HashMapCreate
|
||||
may return
|
||||
.Dv NULL
|
||||
if memory could not be allocated on the heap. Otherwise, it returns a
|
||||
valid pointer to a
|
||||
.Dv HashMap
|
||||
structure which can be used with the other functions in this API.
|
||||
.Pp
|
||||
.Fn HashMapSet
|
||||
returns the previous value at the passed key, or
|
||||
.Dv NULL
|
||||
if no such value exists. All keys must have values; you can't set a key
|
||||
to
|
||||
.Dv NULL .
|
||||
To delete a key, use the
|
||||
.Fn HashMapDelete
|
||||
function.
|
||||
.Pp
|
||||
.Fn HashMapDelete
|
||||
returns the value that was deleted from the hash map at the given key,
|
||||
or
|
||||
.Dv NULL
|
||||
if no such value exists.
|
||||
.Pp
|
||||
.Fn HashMapIterate
|
||||
returns 1 if there are still elements left in the current iteration of the
|
||||
hash map, or 0 if no valid hash map was provided, or there are no more elements
|
||||
in it for the current iteration. Note that as soon as this function returns 0
|
||||
on a hash map, subsequent iterations will start from the beginning.
|
||||
.Sh SEE ALSO
|
||||
.Xr HashMap 3 ,
|
||||
.Xr Queue 3
|
171
man/man3/Http.3
171
man/man3/Http.3
|
@ -1,171 +0,0 @@
|
|||
.Dd $Mdocdate: December 13 2022 $
|
||||
.Dt HTTP 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Http
|
||||
.Nd Encode and decode various parts of the HTTP protocol.
|
||||
.Sh SYNOPSIS
|
||||
.In Http.h
|
||||
.Ft const char *
|
||||
.Fn HttpStatusToString "const HttpStatus"
|
||||
.Ft HttpRequestMethod
|
||||
.Fn HttpRequestMethodFromString "const char *"
|
||||
.Ft const char *
|
||||
.Fn HttpRequestMethodToString "const HttpRequestMethod"
|
||||
.Ft char *
|
||||
.Fn HttpUrlEncode "char *"
|
||||
.Ft char *
|
||||
.Fn HttpUrlDecode "char *"
|
||||
.Ft HashMap *
|
||||
.Fn HttpParamDecode "char *"
|
||||
.Ft char *
|
||||
.Fn HttpParamEncode "HashMap *"
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
is a collection of utility functions that are useful for dealing with
|
||||
HTTP. HTTP is not a complex protocol, but this API makes it a lot easier
|
||||
to work with.
|
||||
.Pp
|
||||
.Fn HttpStatusToString
|
||||
takes an HttpStatus and converts it into a string description of the
|
||||
status, which is to be used in an HTTP response. For example, calling
|
||||
.Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT"
|
||||
produces the string "Gateway Timeout".
|
||||
.Pp
|
||||
HttpStatus is an enumeration that corresponds to the actual integer
|
||||
values of the valid HTTP response codes. For example, calling
|
||||
.Fn HttpStatusToString "504"
|
||||
produces the same output. HttpStatus is defined as follows:
|
||||
.Bd -literal -offset indent
|
||||
typedef enum HttpStatus
|
||||
{
|
||||
/* Informational responses */
|
||||
HTTP_CONTINUE = 100,
|
||||
HTTP_SWITCHING_PROTOCOLS = 101,
|
||||
HTTP_EARLY_HINTS = 103,
|
||||
|
||||
/* Successful responses */
|
||||
HTTP_OK = 200,
|
||||
HTTP_CREATED = 201,
|
||||
HTTP_ACCEPTED = 202,
|
||||
HTTP_NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
HTTP_NO_CONTENT = 204,
|
||||
HTTP_RESET_CONTENT = 205,
|
||||
HTTP_PARTIAL_CONTENT = 206,
|
||||
|
||||
/* Redirection messages */
|
||||
HTTP_MULTIPLE_CHOICES = 300,
|
||||
HTTP_MOVED_PERMANENTLY = 301,
|
||||
HTTP_FOUND = 302,
|
||||
HTTP_SEE_OTHER = 303,
|
||||
HTTP_NOT_MODIFIED = 304,
|
||||
HTTP_TEMPORARY_REDIRECT = 307,
|
||||
HTTP_PERMANENT_REDIRECT = 308,
|
||||
|
||||
/* Client error messages */
|
||||
HTTP_BAD_REQUEST = 400,
|
||||
HTTP_UNAUTHORIZED = 401,
|
||||
HTTP_FORBIDDEN = 403,
|
||||
HTTP_NOT_FOUND = 404,
|
||||
HTTP_METHOD_NOT_ALLOWED = 405,
|
||||
HTTP_NOT_ACCEPTABLE = 406,
|
||||
HTTP_PROXY_AUTH_REQUIRED = 407,
|
||||
HTTP_REQUEST_TIMEOUT = 408,
|
||||
HTTP_CONFLICT = 409,
|
||||
HTTP_GONE = 410,
|
||||
HTTP_LENGTH_REQUIRED = 411,
|
||||
HTTP_PRECONDITION_FAILED = 412,
|
||||
HTTP_PAYLOAD_TOO_LARGE = 413,
|
||||
HTTP_URI_TOO_LONG = 414,
|
||||
HTTP_UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
HTTP_RANGE_NOT_SATISFIABLE = 416,
|
||||
HTTP_EXPECTATION_FAILED = 417,
|
||||
HTTP_TEAPOT = 418,
|
||||
HTTP_UPGRADE_REQUIRED = 426,
|
||||
HTTP_PRECONDITION_REQUIRED = 428,
|
||||
HTTP_TOO_MANY_REQUESTS = 429,
|
||||
HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||
|
||||
/* Server error responses */
|
||||
HTTP_INTERNAL_SERVER_ERROR = 500,
|
||||
HTTP_NOT_IMPLEMENTED = 501,
|
||||
HTTP_BAD_GATEWAY = 502,
|
||||
HTTP_SERVICE_UNAVAILABLE = 503,
|
||||
HTTP_GATEWAY_TIMEOUT = 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
HTTP_VARIANT_ALSO_NEGOTIATES = 506,
|
||||
HTTP_NOT_EXTENDED = 510,
|
||||
HTTP_NETWORK_AUTH_REQUIRED = 511
|
||||
} HttpStatus;
|
||||
.Ed
|
||||
.Pp
|
||||
.Fn HttpRequestMethodFromString
|
||||
and
|
||||
.Fn HttpRequestMethodToString
|
||||
convert an HttpRequestMethod enumeration value from and to a
|
||||
string, respectively. The HttpRequestMethod enumeration is
|
||||
defined as follows:
|
||||
.Bd -literal -offset indent
|
||||
typedef enum HttpRequestMethod
|
||||
{
|
||||
HTTP_METHOD_UNKNOWN,
|
||||
HTTP_GET,
|
||||
HTTP_HEAD,
|
||||
HTTP_POST,
|
||||
HTTP_PUT,
|
||||
HTTP_DELETE,
|
||||
HTTP_CONNECT,
|
||||
HTTP_OPTIONS,
|
||||
HTTP_TRACE,
|
||||
HTTP_PATCH
|
||||
} HttpRequestMethod;
|
||||
.Ed
|
||||
.Pp
|
||||
These can be used for parsing a request method and then storing
|
||||
it efficiently; it doesn't have be stored as a string, and it's
|
||||
much nicer to work with enumeration values than it is with
|
||||
strings in C. The very first enumeration value is not to be
|
||||
passed to
|
||||
.Fn HttpRequestMethodToString ,
|
||||
rather it may be returned by
|
||||
.Fn HttpRequestMethodFromString
|
||||
if it cannot identify the request method string passed to it.
|
||||
.Pp
|
||||
.Fn HttpUrlEncode
|
||||
and
|
||||
.Fn HttpUrlDecode
|
||||
deal with URL-safe strings.
|
||||
.Fn HttpUrlEncode
|
||||
encodes a C string such that it can appear in a URL, and
|
||||
.Fn HttpUrlDecode
|
||||
does the opposite; it decodes a URL string into the actual
|
||||
bytes it is supposed to represent.
|
||||
.Pp
|
||||
.Fn HttpParamDecode
|
||||
and
|
||||
.Fn HttpParamEncode
|
||||
convert HTTP parameters in the form of "param=value¶m2=val2"
|
||||
to and from a hash map for easy parsing, manipulation, and encoding.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn HttpStatusToString
|
||||
and
|
||||
.Fn HttpRequestMethodToString
|
||||
both return constant strings; they are not to be manipulated because
|
||||
doing so would result in a segmentation fault, as these strings
|
||||
are stored in the data segment of the program.
|
||||
.Pp
|
||||
.Fn HttpUrlEncode ,
|
||||
.Fn HttpUrlDecode ,
|
||||
.Fn HttpParamDecode ,
|
||||
and
|
||||
.Fn HttpParamEncode
|
||||
all return strings that were allocated on the heap using the
|
||||
Memory API, or NULL if there was an error allocating memory.
|
||||
Thee strings returned can be manipulated at will, and must be
|
||||
freed using the Memory API when they're no longer needed.
|
||||
.Sh SEE ALSO
|
||||
.Xr HashMap 3 ,
|
||||
.Xr Memory 3
|
|
@ -1,157 +0,0 @@
|
|||
.Dd $Mdocdate: December 13 2022 $
|
||||
.Dt HTTPSERVER 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm HttpServer
|
||||
.Nd Extremely simple HTTP server.
|
||||
.Sh SYNOPSIS
|
||||
.In HttpServer.h
|
||||
.Ft HttpServer *
|
||||
.Fn HttpServerCreate "unsigned short" "unsigned int" "unsigned int" "HttpHandler *" "void *"
|
||||
.Ft void
|
||||
.Fn HttpServerFree "HttpServer *"
|
||||
.Ft int
|
||||
.Fn HttpServerStart "HttpServer *"
|
||||
.Ft void
|
||||
.Fn HttpServerJoin "HttpServer *"
|
||||
.Ft void
|
||||
.Fn HttpServerStop "HttpServer *"
|
||||
.Ft HashMap *
|
||||
.Fn HttpRequestHeaders "HttpServerContext *"
|
||||
.Ft HttpRequestMethod
|
||||
.Fn HttpRequestMethodGet "HttpServerContext *"
|
||||
.Ft char *
|
||||
.Fn HttpRequestPath "HttpServerContext *"
|
||||
.Ft HashMap *
|
||||
.Fn HttpRequestParams "HttpServerContext *"
|
||||
.Ft char *
|
||||
.Fn HttpResponseHeader "HttpServerContext *" "char *" "char *"
|
||||
.Ft void
|
||||
.Fn HttpResponseStatus "HttpServerContext *" HttpStatus"
|
||||
.Ft FILE *
|
||||
.Fn HttpStream "HttpServerContext *"
|
||||
.Ft void
|
||||
.Fn HttpSendHeaders "HttpServerContext *"
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
builds on the
|
||||
.Xr Http 3
|
||||
API, and provides a very simple, yet very functional API for
|
||||
creating an HTTP server. It aims at being easy to use and minimal,
|
||||
yet also efficient. It uses non-blocking I/O, is fully multi-threaded,
|
||||
very configurable, yet also able to be set up in just two function calls.
|
||||
.Pp
|
||||
This API should be familiar to those that have dealt with the HTTP server
|
||||
libraries of other programming languages, particularly Java. In fact,
|
||||
much of the terminology used in this code came from Java, and you'll
|
||||
notice that the way responses are sent and received very closely resemble
|
||||
the way it's done in Java.
|
||||
.Pp
|
||||
An HTTP server itself is created with
|
||||
.Fn HttpServerCreate ,
|
||||
which takes the port number to create the server on, the number of threads to
|
||||
use, the maximum number of connections, a request handler function, and the
|
||||
arguments to that function, in that order. The request handler function is
|
||||
of the following type:
|
||||
.Bd -literal -offset indent
|
||||
typedef void (HttpHandler) (HttpServerContext *, void *)
|
||||
.Ed
|
||||
.Pp
|
||||
Where the void pointer received is the same pointer that was passed into
|
||||
.Fn HttpServerCreate
|
||||
as the last parameter.
|
||||
.Pp
|
||||
The returned HttpServer pointer is then managed by
|
||||
.Fn HttpServerStart ,
|
||||
.Fn HttpServerStop ,
|
||||
.Fn HttpServerJoin ,
|
||||
and
|
||||
.Fn HttpServerFree .
|
||||
.Fn HttpServerStart
|
||||
attempts to start the HTTP server, and returns immediately with the status.
|
||||
This API is fully threaded and asyncronous, so the caller can continue working
|
||||
while the HTTP server is running in a separate thread, and managing a pool
|
||||
of threads to handle responses. Typically at some point after calling
|
||||
.Fn HttpServerStart ,
|
||||
the program will have no more work to do, and so it will want to wait for
|
||||
the HTTP server to finish. This is accomplished with
|
||||
.Fn HttpServerJoin ,
|
||||
which joins the HTTP worker thread to the calling thread, making the
|
||||
calling thread wait until the HTTP server has stopped.
|
||||
.Pp
|
||||
The only condition that will cause the HTTP server to stop is when
|
||||
.Fn HttpServerStop
|
||||
is invoked. This will typically happen in a signal handler that catches
|
||||
signals instructing the program to shut down. Only after the server has
|
||||
been stopped can it be freed with
|
||||
.Fn HttpServerFree .
|
||||
Note that calling
|
||||
.Fn HttpServerFree
|
||||
while the server is running results in undefined behavior.
|
||||
.Pp
|
||||
The remainder of the functions in this API are used inside of the
|
||||
HTTP handler function passed by the caller of
|
||||
.Fn HttpServerCreate .
|
||||
They allow the handler to figure out the context of an HTTP request,
|
||||
which includes the path that was requested, any parameters, and the
|
||||
headers used to make the request. They also allow the handler
|
||||
to respond with a status code, headers, and a body.
|
||||
.Pp
|
||||
.Fn HttpRequestHeaders ,
|
||||
.Fn HttpRequestMethodGet ,
|
||||
.Fn HttpRequestPath ,
|
||||
and
|
||||
.Fn HttpRequestParams
|
||||
get the information about the request. They should all be passed the
|
||||
server context pointer that was passed into the handler function.
|
||||
The data returned by these functions should be treated as read-only,
|
||||
and should not be freed; their memory is handled outside of the HTTP
|
||||
server handler function.
|
||||
.Pp
|
||||
.Fn HttpResponseHeader
|
||||
and
|
||||
.Fn HttpResponseStatus
|
||||
are used to set response headers, and the response status of the
|
||||
request, respectively.
|
||||
.Pp
|
||||
.Fn HttpStream
|
||||
returns a stream that is both readable and writable. Reading from
|
||||
the stream reads the request body that the client sent, if there is
|
||||
one. Note that the request headers have already been read, so the stream
|
||||
is correctly positioned at the beginning of the body of the request.
|
||||
.Fn HttpSendHeaders
|
||||
must be called before the stream is written to, otherwise a malformed
|
||||
HTTP response will be sent. An HTTP handler should properly set all
|
||||
the headers it intends to send, send those headers, and then write the
|
||||
response body to the stream. Finally, note that the stream does not
|
||||
need to be closed by the HTTP handler; in fact, doing so results in
|
||||
undefined behavior. The stream is managed by the server itself.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn HttpRequestHeaders
|
||||
and
|
||||
.Fn HttpRequestParams
|
||||
return a hash map that can be used to access the request headers and
|
||||
parameters, if necessary. Note that the request parameters would be
|
||||
GET parameters, attached to the path that was requested. To get POST
|
||||
parameters, read the stream returned by
|
||||
.Fn HttpStream
|
||||
and pass the contents into
|
||||
.Fn HttpParamDecode
|
||||
to get a hash map.
|
||||
.Pp
|
||||
.Fn HttpRequestPath
|
||||
returns a string that represents the path that the client requested. Note
|
||||
that it is not normalized; it is exactly what the client sent, so it should
|
||||
be checked for path traversal attacks and other malformed paths that the
|
||||
client may sent.
|
||||
.Pp
|
||||
.Fn HttpResponseHeader
|
||||
returns the previous value of the given header, or NULL if there was no
|
||||
previous value.
|
||||
.Pp
|
||||
.Fn HttpStream
|
||||
returns a FILE pointer that can be read and written using the C standard
|
||||
I/O functions.
|
||||
.Sh SEE ALSO
|
||||
.Xr Http 3
|
252
man/man3/Json.3
252
man/man3/Json.3
|
@ -1,252 +0,0 @@
|
|||
.Dd $Mdocdate: March 6 2023 $
|
||||
.Dt JSON 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Json
|
||||
.Nd A fully-featured JSON API.
|
||||
.Sh SYNOPSIS
|
||||
.In Json.h
|
||||
.Ft JsonType
|
||||
.Fn JsonValueType "JsonValue *"
|
||||
.Ft JsonValue *
|
||||
.Fn JsonValueObject "HashMap *"
|
||||
.Ft HashMap *
|
||||
.Fn JsonValueAsObject "JsonValue *"
|
||||
.Ft JsonValue *
|
||||
.Fn JsonValueArray "Array *"
|
||||
.Ft Array *
|
||||
.Fn JsonValueAsArray "
|
||||
.Ft JsonValue *
|
||||
.Fn JsonValueString "char *"
|
||||
.Ft char *
|
||||
.Fn JsonValueAsString "JsonValue *"
|
||||
.Ft JsonValue *
|
||||
.Fn JsonValueInteger "long"
|
||||
.Ft long
|
||||
.Fn JsonValueAsInteger "JsonValue *"
|
||||
.Ft JsonValue *
|
||||
.Fn JsonValueFloat "double"
|
||||
.Ft double
|
||||
.Fn JsonValueAsFloat "JsonValue *"
|
||||
.Ft JsonValue *
|
||||
.Fn JsonValueBoolean "int"
|
||||
.Ft int
|
||||
.Fn JsonValueAsBoolean "JsonValue *"
|
||||
.Ft JsonValue *
|
||||
.Fn JsonValueNull "void"
|
||||
.Ft void
|
||||
.Fn JsonValueFree "JsonValue *"
|
||||
.Ft void
|
||||
.Fn JsonFree "HashMap *"
|
||||
.Ft void
|
||||
.Fn JsonEncodeString "const char *" "FILE *"
|
||||
.Ft void
|
||||
.Fn JsonEncodeValue "JsonValue *" "FILE *"
|
||||
.Ft int
|
||||
.Fn JsonEncode "HashMap *" "FILE *"
|
||||
.Ft HashMap *
|
||||
.Fn JsonDecode "FILE *"
|
||||
.Ft JsonValue *
|
||||
.Fn JsonGet "HashMap *" "size_t" "..."
|
||||
.Ft JsonValue *
|
||||
.Fn JsonSet "HashMap *" "size_t" "..."
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a fully-featured JSON API for C using
|
||||
.Xr Array 3
|
||||
and
|
||||
.Xr HashMap 3
|
||||
that can parse JSON, and serialize an in-memory structure
|
||||
to JSON.
|
||||
It builds on the foundation set up by those APIs because
|
||||
that's all JSON really is, just maps and arrays.
|
||||
.Nm
|
||||
also provides a structure for encapsulating an arbitrary
|
||||
value and identifying its type, making it easy for a program
|
||||
to work with JSON data.
|
||||
.Nm
|
||||
is very strict and tries to adhere as closely as possible to
|
||||
the proper defintion of JSON. It will fail on syntax errors
|
||||
of any kind, which is fine for a Matrix homeserver because we can
|
||||
just return M_BAD_JSON if anything here fails, but it may not
|
||||
be suitable for other purposes.
|
||||
.Pp
|
||||
This JSON implementation focuses primarily on serialization and
|
||||
deserialization to and from streams. It does not provide facilities
|
||||
for handling JSON strings; it only writes JSON to output streams, and
|
||||
reads them from input streams. If course, you can use the POSIX
|
||||
.Xr fmemopen 3
|
||||
and
|
||||
.Xr open_memstream 3
|
||||
if you want to deal with JSON strings, but JSON is intended to be an
|
||||
exchange format. Data should be converted to JSON right before it is
|
||||
leaving the program, and converted from JSON as soon as it is coming
|
||||
in.
|
||||
.Pp
|
||||
The
|
||||
.Nm
|
||||
header defines the following enumeration for identifying types of
|
||||
values:
|
||||
.Bd -literal -offset indent
|
||||
typedef enum JsonType
|
||||
{
|
||||
JSON_NULL, /* Maps to C NULL */
|
||||
JSON_OBJECT, /* Maps to a HashMap of JsonValues */
|
||||
JSON_ARRAY, /* Maps to an Array of JsonValues */
|
||||
JSON_STRING, /* Maps to a NULL-terminated C string */
|
||||
JSON_INTEGER, /* Maps to a C long */
|
||||
JSON_FLOAT, /* Maps to a C double */
|
||||
JSON_BOOLEAN /* Maps to a C boolean, 1 or 0 */
|
||||
} JsonType;
|
||||
.Ed
|
||||
.Pp
|
||||
A JsonValue encapsulates all the possible types that can be stored in
|
||||
JSON. It is an opaque structure that can be managed entirely by the
|
||||
functions defined in this API. It is important to note that in the case
|
||||
of objects, arrays, and strings, this structure only stores pointers to
|
||||
the allocated data, it doesn't store the data itself, but the data IS
|
||||
freed when using
|
||||
.Fn JsonFree .
|
||||
.Pp
|
||||
Objects are represented as hash maps consisting entirely of JsonValue
|
||||
structures, and arrays are represented as arrays consisting entirely
|
||||
of JsonValue structures. When generating a JSON object, any
|
||||
attempt to stuff a value into a hash map or array without encoding it
|
||||
as a JsonValue first will result in undefined behavior.
|
||||
.Pp
|
||||
.Fn JsonValueType
|
||||
determines the type of a given JsonValue.
|
||||
.Pp
|
||||
The
|
||||
.Fn JsonValue*
|
||||
functions wrap their input in a JsonValue that represents the given
|
||||
value. The
|
||||
.Fn JsonValueAs*
|
||||
functions do the opposite; they unwrap a JsonValue and return the
|
||||
actual usable value itself. They all closely resemble each other and
|
||||
they all behave the same way, so to save on time and effort, they're
|
||||
not explicitly documented individually. If something is unclear about
|
||||
how these functions work, consult the source code, and feel free
|
||||
to write the documentation yourself for clarification. Otherwise,
|
||||
reach out to the official Matrix rooms, and someone should be able
|
||||
to help you.
|
||||
.Pp
|
||||
.Fn JsonValueNull
|
||||
is a special case that represents a JSON null. Because
|
||||
.Xr Array 3
|
||||
and
|
||||
.Xr HashMap 3
|
||||
do not accept NULL values, this function should be used to represent
|
||||
NULL in JSON. Even though a small amount of memory is allocated just
|
||||
to point to NULL, this keeps the APIs clean.
|
||||
.Pp
|
||||
.Fn JsonValueFree
|
||||
frees the memory being used by a JSON value. Note that this will
|
||||
recursively free all Arrays, HashMaps, and other JsonValues that
|
||||
are reachable from the given value. It also invokes
|
||||
.Fn Free
|
||||
(documented in
|
||||
.Xr Memory )
|
||||
on all strings, so make sure passed string pointers point to strings
|
||||
on the heap, not the stack. This will be the case for all strings
|
||||
returned by
|
||||
.Fn JsonDecode ,
|
||||
but if you are manually creating JSON objects and stitching them
|
||||
together, you should be aware that calling this function on a value
|
||||
that contains a pointer to a stack string will result in undefined
|
||||
behavior.
|
||||
.Pp
|
||||
.Fn JsonFree
|
||||
recursively frees a JSON object, iterating over all the values and
|
||||
freeing them using
|
||||
.Fn JsonValueFree .
|
||||
.Pp
|
||||
.Fn JsonEncodeString
|
||||
encodes the given string in such a way that it can be embedded in a
|
||||
JSON stream. This entails:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
Escaping quotes, backslashes, and other special characters using
|
||||
their backslash escape.
|
||||
.It
|
||||
Encoding bytes that are not UTF-8 using escapes.
|
||||
.It
|
||||
Wrapping the entire string in double quotes.
|
||||
.El
|
||||
.Pp
|
||||
This function is provided via the public
|
||||
.Nm
|
||||
API so that it is accessible to custom JSON encoders, such as
|
||||
.Xr CanonicalJson 3 .
|
||||
This will typically be used for encoding JSON keys; for encoding
|
||||
values, just use
|
||||
.Fn JsonEncodeValue .
|
||||
.Pp
|
||||
.Fn JsonEncodeValue
|
||||
serializes a JsonValue as it would appear in JSON output. This is
|
||||
a recursive function that also encodes all child values reachable
|
||||
from the given value. This function is exposed via the public
|
||||
.Nm
|
||||
API so that it is accessible to custom JSON encoders. Normal users
|
||||
that are not writing custom encoders should in most cases just use
|
||||
.Fn JsonEncode
|
||||
to encode an entire object.
|
||||
.Pp
|
||||
.Fn JsonEncode
|
||||
encodes a JSON object as minimized JSON and writes it to the given
|
||||
output stream. This function is recursive; it will serialize
|
||||
everything accessible from the passed object.
|
||||
.Fn JsonDecode
|
||||
does the opposite; it reads from a JSON stream and decodes it
|
||||
into a hash map of JsonValues.
|
||||
.Pp
|
||||
.Fn JsonSet
|
||||
and
|
||||
.Fn JsonGet
|
||||
are convenience functions that allow the caller to retrieve and
|
||||
manipulate arbitrarily deep keys within a JSON object. They take
|
||||
a root JSON object, the number of levels deep to go, and then that
|
||||
number of keys as a varargs list. All keys must have objects
|
||||
as values, with the exception of the last one, which is the one
|
||||
being retrieved or set.
|
||||
.Fn JsonSet
|
||||
will create any intermediate objects as necessary to set the
|
||||
proper key.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn JsonValueType
|
||||
returns a JsonType, documented above, that tells what the given
|
||||
value actually is, or JSON_NULL if the passed value is NULL.
|
||||
Note that even a fully valid JsonValue may actually be of type
|
||||
JSON_NULL, so this function should not be used to determine
|
||||
whether or not a given value is valid.
|
||||
.Pp
|
||||
The
|
||||
.Fn JsonValue*
|
||||
functions return a JsonValue that holds a pointer to the passed
|
||||
value, or NULL if there was an error allocating memory. The
|
||||
.Fn JsonValueAs*
|
||||
functions return the actual value represented by the given
|
||||
JsonValue so that it can be manipulated by the program, or
|
||||
NULL if no value was provided, or the value is not of the
|
||||
correct type expected by the function.
|
||||
.Pp
|
||||
.Fn JsonEncode
|
||||
returns whether or not the encoding operation was successful.
|
||||
This function will fail if any passed parameters are NULL,
|
||||
otherwise it will assume all pointers are valid and return a
|
||||
success value.
|
||||
.Pp
|
||||
.Fn JsonDecode
|
||||
returns a hash map of JsonValues that can be manipulated by
|
||||
this API, or NULL if there was an error parsing the JSON.
|
||||
.Pp
|
||||
.Fn JsonGet
|
||||
returns a JsonValue, or NULL if the requested key is not set
|
||||
in the object.
|
||||
.Fn JsonGet
|
||||
returns the previous value of the provided key, or NULL if there
|
||||
was no previous value.
|
||||
.Sh SEE ALSO
|
||||
.Xr HashMap 3 ,
|
||||
.Xr Array 3
|
155
man/man3/Log.3
155
man/man3/Log.3
|
@ -1,155 +0,0 @@
|
|||
.Dd $Mdocdate: February 15 2023 $
|
||||
.Dt LOG 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Log
|
||||
.Nd A simple logging framework for logging to files, standard output, or the system log.
|
||||
.Sh SYNOPSIS
|
||||
.In Log.h
|
||||
.Ft LogConfig *
|
||||
.Fn LogConfigCreate "void"
|
||||
.Ft void
|
||||
.Fn LogConfigFree "LogConfig *"
|
||||
.Ft void
|
||||
.Fn LogConfigLevelSet "LogConfig *" "int"
|
||||
.Ft void
|
||||
.Fn LogConfigIndent "LogConfig *"
|
||||
.Ft void
|
||||
.Fn LogConfigUnindent "LogConfig *"
|
||||
.Ft void
|
||||
.Fn LogConfigIndentSet "LogConfig *" "size_t"
|
||||
.Ft void
|
||||
.Fn LogConfigOutputSet "LogConfig *" "FILE *"
|
||||
.Ft void
|
||||
.Fn LogConfigFlagSet "LogConfig *" "int"
|
||||
.Ft void
|
||||
.Fn LogConfigFlagClear "LogConfig *" "int"
|
||||
.Ft void
|
||||
.Fn LogConfigTimeStampFormatSet "LogConfig *" "char *"
|
||||
.Ft void
|
||||
.Fn Log "LogConfig *" "int" "const char *" "..."
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
A heavily-modifed version of Shlog, a simple C logging library facility
|
||||
that allows for colorful outputs, timestamps, and custom log levels.
|
||||
This library differs from Shlog in that the naming conventions have
|
||||
been updated to be consistent with Telodendria, and system log support
|
||||
has been added.
|
||||
.Pp
|
||||
Shlog was originally a learning project. It worked well and produced
|
||||
elegant logging output, so it was chosen to be the main logging
|
||||
mechanism of Telodendria. The original Shlog project is now dead; Shlog
|
||||
lives on now only as Telodendria's logging mechanism.
|
||||
.Pp
|
||||
One of the design choices made in this library, which unfortunately
|
||||
makes code using it a little more verbose, is that multiple logging
|
||||
configurations can exist in a program. No global variables are used,
|
||||
and all functions are thread-safe.
|
||||
.Pp
|
||||
.Fn LogConfigCreate
|
||||
creates a new log configuration with sane defaults that can be used
|
||||
immediately. Note that every call to
|
||||
.Fn Log
|
||||
requires a valid configuration pointer.
|
||||
.Fn LogConfigFree
|
||||
frees all memory associated with that configuration, invalidating
|
||||
it. Passing an invalid configuration pointer into any of the
|
||||
functions defined here result in undefined behavior. The
|
||||
.Fn LogConfig*Set
|
||||
functions manipulate the data pointed to by the pointer returned
|
||||
by
|
||||
.Fn LogConfigCreate .
|
||||
.Pp
|
||||
.Fn LogConfigLevelSet
|
||||
sets the current log level on the specified log configuration. This
|
||||
indicates that only messages at or above this level should be
|
||||
logged; all other messages are silently discarded by the
|
||||
.Fn Log
|
||||
function. The passed log level should be one of the log levels
|
||||
defined by
|
||||
.Xr syslog 3 .
|
||||
Refer to that page for a complete list of acceptable log levels,
|
||||
and note that passing in an invalid or unknown log level will
|
||||
result in undefined behavior.
|
||||
.Pp
|
||||
.Fn LogConfigIndent
|
||||
causes the output of
|
||||
.Fn Log
|
||||
to be indented two more spaces than it was previously. This can be
|
||||
helpful when generating stack traces, or otherwise producing
|
||||
hierarchical output. After calling this function, all future
|
||||
messages using the given config will be indented two more spaces
|
||||
than they were before. This is just a wrapper function around
|
||||
.Fn LogConfigIndentSet ,
|
||||
which allows the caller to specify an arbitrary indentation in
|
||||
spaces.
|
||||
.Fn LogConfigUnindent
|
||||
does the exact opposite of
|
||||
.Fn LogConfigIndent ;
|
||||
it subtracts two spaces from the indentation level, unless there
|
||||
is no indent, then it does nothing.
|
||||
.Pp
|
||||
.Fn LogConfigOutputSet
|
||||
sets the file stream that logging output should be written to. This
|
||||
defaults to standard output, but it can be standard error, or some
|
||||
other file stream. Passing a NULL value for the file pointer sets
|
||||
the log output to standard output. Note that the output file stream
|
||||
is only used if FLAG_OUTPUT_SYSLOG is not set.
|
||||
.Pp
|
||||
.Fn LogConfigFlagSet
|
||||
and
|
||||
.Fn LogConfigFlagClear
|
||||
are used for setting a number of boolean options on a log
|
||||
configuration. They utilize bitwise operators, so multiple options
|
||||
can be set or cleared with a single function call using bitwise OR
|
||||
operators. The flags defined as preprocessor macros, and are as
|
||||
follows:
|
||||
.Bl -tag -width Ds
|
||||
.It LOG_FLAG_COLOR
|
||||
When set, enable color-coded output on TTYs. Note that colors are
|
||||
implemented as ANSI escape sequences, and are not written to file
|
||||
streams that are not actually connected to a TTY, to prevent those
|
||||
sequences from being written to a file.
|
||||
.Xr isatty 3
|
||||
is checked before writing ANSI sequences.
|
||||
.It LOG_FLAG_SYSLOG
|
||||
When enabled, log output to the syslog using
|
||||
.Xr syslog 3 ,
|
||||
instead of logging to the file set by
|
||||
.Fn LogConfigOutputSet .
|
||||
This flag always overrides the file stream set by that function,
|
||||
regardless of when it was set.
|
||||
.El
|
||||
.Pp
|
||||
.Fn LogConfigTimeStampFormatSet
|
||||
allows a custom timestamp to be prepended to each message
|
||||
if the output is not going to the system log. Consult your
|
||||
system's documentation for
|
||||
.Xr strftime 3 .
|
||||
A value of NULL disables outputting a timestamp before messages.
|
||||
.Pp
|
||||
The
|
||||
.Fn Log
|
||||
function actually writes a log message to a console, file, system
|
||||
log, or other supported output device using the given configuration.
|
||||
This function is thread-safe; it locks a mutex before writing a
|
||||
message, and then unlocks it after the message was written. Each
|
||||
log configuration has its own mutex, so this function can be used
|
||||
with mutiple active log configurations.
|
||||
.Pp
|
||||
This function only logs messages if their level is above or equal to
|
||||
the currently configured log level, making it easy to turn some
|
||||
messages on or off. The function has the same usage as
|
||||
.Xr printf 3 .
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn LogConfigCreate
|
||||
returns a pointer to a configuration structure on the heap, or NULL
|
||||
if there was an error allocating memory for it. The returned
|
||||
structure is opaque to the caller; the
|
||||
.Fn LogConfig*
|
||||
functions should be used to manipulate it.
|
||||
.Pp
|
||||
All other functions do not return anything. They are simply
|
||||
assumed to always succeed. If any arguments are NULL, then the
|
||||
functions do nothing, unless otherwise specifically noted.
|
|
@ -1,141 +0,0 @@
|
|||
.Dd $Mdocdate: March 6 2023 $
|
||||
.Dt MATRIX 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Matrix
|
||||
.Nd Functions for writing Matrix API endpoints.
|
||||
.Sh SYNOPSIS
|
||||
.In Matrix.h
|
||||
.Ft void
|
||||
.Fn MatrixHttpHandler "HttpServerContext *" "void *"
|
||||
.Ft void
|
||||
.Fn MatrixErrorCreate "MatrixError"
|
||||
.Ft HashMap *
|
||||
.Fn MatrixRateLimit "HttpServerContext *" "Db *"
|
||||
.Ft HashMap *
|
||||
.Fn MatrixGetAccessToken "HttpServerContext *" "char **"
|
||||
.Ft HashMap *
|
||||
.Fn MatrixClientWellKnown "char *" "char *"
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
provides some helper functions that bind to the
|
||||
.Xr HttpServer 3
|
||||
interface and add basic Matrix functionality, turning an
|
||||
HTTP server into a Matrix homeserver.
|
||||
.Pp
|
||||
.Xr MatrixHttpHandler
|
||||
is the HTTP handler function that handles all Matrix homeserver
|
||||
functionality. It should be passed into
|
||||
.Fn HttpServerCreate ,
|
||||
and it expects that an instance of MatrixHttpHandlerArgs will also
|
||||
be provided, because that's what the void pointer is cast to.
|
||||
That structure is defined as follows:
|
||||
.Bd -literal -offset indent
|
||||
typedef struct MatrixHttpHandlerArgs
|
||||
{
|
||||
LogConfig *lc;
|
||||
TelodendriaConfig *config;
|
||||
Db *db;
|
||||
} MatrixHttpHandlerArgs;
|
||||
.Ed
|
||||
.Pp
|
||||
This structure should be populated once and then never modified again
|
||||
for the duration of the HTTP server.
|
||||
.Pp
|
||||
.Fn MatrixErrorCreate
|
||||
is a convenience function that constructs an error payload, including
|
||||
the error code and message, given just a MatrixError. MatrixErrors
|
||||
exactly follow the errors in the Matrix specification, and are
|
||||
defined as follows:
|
||||
.Bd -literal -offset indent
|
||||
typedef enum MatrixError
|
||||
{
|
||||
M_FORBIDDEN,
|
||||
M_UNKNOWN_TOKEN,
|
||||
M_MISSING_TOKEN,
|
||||
M_BAD_JSON,
|
||||
M_NOT_JSON,
|
||||
M_NOT_FOUND,
|
||||
M_LIMIT_EXCEEDED,
|
||||
M_UNKNOWN,
|
||||
M_UNRECOGNIZED,
|
||||
M_UNAUTHORIZED,
|
||||
M_USER_DEACTIVATED,
|
||||
M_USER_IN_USE,
|
||||
M_INVALID_USERNAME,
|
||||
M_ROOM_IN_USE,
|
||||
M_IVALID_ROOM_STATE,
|
||||
M_THREEPID_IN_USE,
|
||||
M_THREEPID_NOT_FOUND,
|
||||
M_THREEPID_AUTH_FAILED,
|
||||
M_THREEPID_DENIED,
|
||||
M_SERVER_NOT_TRUSTED,
|
||||
M_UNSUPPORTED_ROOM_VERSION,
|
||||
M_BAD_STATE,
|
||||
M_GUEST_ACCESS_FORBIDDEN,
|
||||
M_CAPTCHA_NEEDED,
|
||||
M_CAPTCHA_INVALID,
|
||||
M_MISSING_PARAM,
|
||||
M_INVALID_PARAM,
|
||||
M_TOO_LARGE,
|
||||
M_EXCLUSIVE,
|
||||
M_RESOURCE_LIMIT_EXCEEDED,
|
||||
M_CANNOT_LEAVE_SERVER_NOTICE_ROOM
|
||||
} MatrixError;
|
||||
.Ed
|
||||
.Pp
|
||||
.Fn MatrixRateLimit
|
||||
determines whether or not the request should be rate limited. It is
|
||||
expected that this will occur before most, if not all of the caller's
|
||||
logic.
|
||||
.Pp
|
||||
.Fn MatrixGetAccessToken
|
||||
reads the request headers and parameters, and attempts to obtain
|
||||
the access token it found. The matrix specification says that an
|
||||
access token can either be in an
|
||||
.Dv Authorization
|
||||
header, or in a
|
||||
.Dv access_token
|
||||
.Sy GET
|
||||
paramter. This function checks both, and stores the access token
|
||||
it finds in the passed character pointer.
|
||||
.Pp
|
||||
.Fn MatrixClientWellKnown
|
||||
builds a client ``well-known'' JSON object, which contains
|
||||
information about the homeserver base URL and identity server,
|
||||
both of which should be provided by the caller in that order. This
|
||||
object can be sent to a client as-is, as is the case with the
|
||||
.Pa /.well-known/matrix/client
|
||||
endpoint, or it can be added as a key in a response, as is the
|
||||
case with a few endpoints.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn MatrixErrorCreate
|
||||
returns a JSON object that represents the given error code. It can be
|
||||
immediately returned as the HTTP response body, or modified as needed.
|
||||
.Pp
|
||||
.Fn MatrixUserInteractiveAuth ,
|
||||
.Fn MatrixAuthenticate ,
|
||||
and
|
||||
.Fn MatrixRateLimit
|
||||
all return NULL when they are successful. That is, if these functions
|
||||
return NULL, then the caller can proceed assuming that all is well
|
||||
and no further action needs to be taken. If these functions do not
|
||||
return NULL, then the returned JSON object should be passed along to the
|
||||
client immediately without continuing.
|
||||
.Pp
|
||||
.Fn MatrixGetAccessToken
|
||||
returns a JSON object that should be immediately passed to the client
|
||||
if it is not NULL. This JSON object holds an error message, indicating
|
||||
that something went wrong. If this function does return NULL, then
|
||||
the access token can be checked for validity. Otherwise, the access
|
||||
token is either not valid or not provided so it should not be
|
||||
checked.
|
||||
.Pp
|
||||
.Fn MatrixClientWellKnown
|
||||
returns a JSON object, or NULL if something went wrong.
|
||||
.Sh SEE ALSO
|
||||
.Xr HttpServer 3 ,
|
||||
.Xr Log 3 ,
|
||||
.Xr TelodendriaConfig 3 ,
|
||||
.Xr Db 3
|
|
@ -1,145 +0,0 @@
|
|||
.Dd $Mdocdate: January 9 2023 $
|
||||
.Dt MEMORY 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Memory
|
||||
.Nd Smart memory management.
|
||||
.Sh SYNOPSIS
|
||||
.In Memory.h
|
||||
.Ft void *
|
||||
.Fn MemoryAllocate "size_t" "const char *" "int"
|
||||
.Ft void *
|
||||
.Fn MemoryReallocate "void *" "size_t" "const char *" "int"
|
||||
.Ft void
|
||||
.Fn MemoryFree "void *" "const char *" "int"
|
||||
.Ft size_t
|
||||
.Fn MemoryAllocated "void"
|
||||
.Ft void
|
||||
.Fn MemoryFreeAll "void"
|
||||
.Ft MemoryInfo *
|
||||
.Fn MemoryInfoGet "void *"
|
||||
.Ft size_t
|
||||
.Fn MemoryInfoGetSize "MemoryInfo *"
|
||||
.Ft const char *
|
||||
.Fn MemoryInfoGetFile "MemoryInfo *"
|
||||
.Ft int
|
||||
.Fn MemoryInfoGetLine "MemoryInfo *"
|
||||
.Ft void *
|
||||
.Fn MemoryInfoGetPointer "MemoryInfo *"
|
||||
.Ft void
|
||||
.Fn MemoryIterate "void (*) (MemoryInfo *, void *)" "void *"
|
||||
.Ft void
|
||||
.Fn MemoryHook "void (*) (MemoryAction, MemoryInfo *, void *" "void *"
|
||||
.Ft void
|
||||
.Fn MemoryHexDump "MemoryInfo *" "void (*) (size_t, char *, char *, void *)" "void *"
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is an API that allows for smart memory management and profiling. It wraps
|
||||
the standard library functions
|
||||
.Xr malloc 3 ,
|
||||
.Xr realloc 3 ,
|
||||
and
|
||||
.Xr free 3 ,
|
||||
and offers identical semantics, while providing functionality that the
|
||||
standard library doesn't have, such as getting statistics on the total
|
||||
memory allocated on the heap, and getting the size of a block of memory
|
||||
given a pointer. Additionally, thanks to preprocessor macros, the exact
|
||||
file and line number at which an allocation, reallocation, or free occured
|
||||
can be obtained given a pointer. Finally, all the blocks allocated on the
|
||||
heap can be iterated and evaluated, and a callback function can be executed
|
||||
every time a memory operation occurs.
|
||||
.Pp
|
||||
A number of macros are available, which make using the
|
||||
.Nm
|
||||
API much more useful.
|
||||
.Fn Malloc
|
||||
expands to
|
||||
.Fn MemoryAllocate
|
||||
with the __FILE__ and __LINE__ constants for the second and third
|
||||
arguments respectively. Likewise,
|
||||
.Fn Realloc
|
||||
and
|
||||
.Fn Free
|
||||
expand to
|
||||
.Fn MemoryReallocate
|
||||
and
|
||||
.Fn MemoryFree
|
||||
with __FILE__ and __LINE__ as the second and third parameters.
|
||||
This allows the API to be used exactly how the standard library
|
||||
would be used. In fact, the functions which these macros expand to
|
||||
are not intended to be called directly; only use the macros for the
|
||||
best results.
|
||||
.Pp
|
||||
If all memory used in the program is managed by this API, there are some
|
||||
helpful functions that allow the program to probe the state of the heap.
|
||||
These functions are described here.
|
||||
.Pp
|
||||
.Fn MemoryAllocated
|
||||
gets the total memory that the program has on the heap. This operation
|
||||
iterates over all the heap allocations made with
|
||||
.Fn MemoryAllocate
|
||||
and then returns a total count, in bytes.
|
||||
.Pp
|
||||
.Fn MemoryFreeAll
|
||||
iterates over all the heap allocations made with
|
||||
.Fn MemoryAllocate
|
||||
and calls
|
||||
.Fn MemoryFree
|
||||
on them. It immediately invalidates all pointers, and any subsequent
|
||||
reads or writes to heap memory result in undefined behavior. This
|
||||
is typically used at the end of the program.
|
||||
.Pp
|
||||
.Fn MemoryInfoGet
|
||||
takes a pointer and fetches information about it, storing it in a
|
||||
structure that can then be queried.
|
||||
.Pp
|
||||
.Fn MemoryInfoGetSize ,
|
||||
.Fn MemoryInfoGetFile ,
|
||||
.Fn MemoryInfoGetLine ,
|
||||
and
|
||||
.Fn MemoryInfoGetPointer
|
||||
all take in the structure returned by
|
||||
.Fn MemoryInfoGet ,
|
||||
and return the respective property about the given property. These are
|
||||
especially useful for logging and debugging with
|
||||
.Fn MemoryIterate
|
||||
and
|
||||
.Fn MemoryHook .
|
||||
.Pp
|
||||
.Fn MemoryIterate
|
||||
takes a pointer to a function that takes the memory information structure,
|
||||
as well as a void pointer for caller-provided arguments. It iterates over
|
||||
all the heap memory currently allocated at the time of calling.
|
||||
.Fn MemoryHook
|
||||
has a similar prototype, although the function pointer it takes is slightly
|
||||
different. It also takes a memory action as the first argument. The
|
||||
.Nm
|
||||
API stores the pointer to this function, and executes it every time memory
|
||||
is allocated, reallocated, or freed. This allows a program to execute code
|
||||
whenever memory is allocated.
|
||||
.Pp
|
||||
.Fn MemoryHexDump
|
||||
can be useful for debugging memory errors. It reads over a block of memory
|
||||
and generates a hexadecimal and an ASCII string for each chunk of the block.
|
||||
It takes a memory infomation structure and a callback function that processes
|
||||
the offset, hexadecimal string, and ASCII string. This callback function
|
||||
typically prints the strings out to a console, file, or other output
|
||||
device.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn MemoryAllocate
|
||||
and
|
||||
.Fn MemoryReallocate
|
||||
return the same as their standard library counterparts. That is, a pointer
|
||||
to memory on the heap, or NULL if there was an error allocating it.
|
||||
.Pp
|
||||
.Fn MemoryInfoGet
|
||||
returns a pointer to information about a block on the heap, or NULL if the
|
||||
passed pointer was not allocated by the
|
||||
.Nm
|
||||
API, or is no longer allocated.
|
||||
.Pp
|
||||
.Fn MemoryAllocated
|
||||
returns an unsigned integer that indicates the number of bytes currently
|
||||
allocated on the heap.
|
||||
|
100
man/man3/Queue.3
100
man/man3/Queue.3
|
@ -1,100 +0,0 @@
|
|||
.Dd $Mdocdate: November 25 2022 $
|
||||
.Dt QUEUE 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Array
|
||||
.Nd A simple static queue data structure.
|
||||
.Sh SYNOPSIS
|
||||
.In Queue.h
|
||||
.Ft Queue *
|
||||
.Fn QueueCreate "size_t"
|
||||
.Ft void
|
||||
.Fn QueueFree "Array *"
|
||||
.Ft int
|
||||
.Fn QueuePush "Queue *" "void *"
|
||||
.Ft void *
|
||||
.Fn QueuePop "Queue *"
|
||||
.Ft void *
|
||||
.Fn QueuePeek "Queue *"
|
||||
.Ft int
|
||||
.Fn QueueFull "Queue *"
|
||||
.Ft int
|
||||
.Fn QueueEmpty "Queue *"
|
||||
.Sh DESCRIPTION
|
||||
These functions implement a simple queue data structure that
|
||||
is statically sized.
|
||||
This implementation does not actually store the values of the
|
||||
items in it; it only stores pointers to the data. As such, you will
|
||||
still have to manually maintain all your data. The advantage of this
|
||||
is that these functions don't have to copy data, and thus don't care
|
||||
how big the data is. Furthermore, arbitrary data can be stored in the
|
||||
queue.
|
||||
.Pp
|
||||
This queue implementation operates on the heap. It is a circular
|
||||
queue, and it does not grow as it is used. Once the size is set, the
|
||||
queue never gets any bigger.
|
||||
.Pp
|
||||
These functions operate on a queue structure which is opaque to the
|
||||
caller.
|
||||
.Pp
|
||||
.Fn QueueCreate
|
||||
and
|
||||
.Fn QueueFree
|
||||
allocate and deallocate a queue, respectively.
|
||||
Note that
|
||||
.Fn QueueFree
|
||||
does not free any of the values stored in the queue; it is the caller's
|
||||
job to manage the memory for each item. Typically, the caller would
|
||||
dequeue all the items in the queue and free them before freeing
|
||||
the queue itself.
|
||||
.Pp
|
||||
.Fn QueuePush
|
||||
and
|
||||
.Fn QueuePop
|
||||
are the main functions used to modify the array. They enqueue and dequeue
|
||||
elements from the queue structure, respectively.
|
||||
.Pp
|
||||
.Fn QueuePeek
|
||||
simply returns the pointer that is next up in the queue without actually
|
||||
discarding it, such that the next call to
|
||||
.Fn QueuePeek
|
||||
or
|
||||
.Fn QueuePop
|
||||
return the same pointer.
|
||||
.Pp
|
||||
.Fn QueueFull
|
||||
and
|
||||
.Fn QueueEmpty
|
||||
return a boolean value that indicates whether or not the queue is full
|
||||
or empty, respectively.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn QueueCreate
|
||||
returns a queue structure, or
|
||||
.Dv NULL
|
||||
if there was a memory allocation error.
|
||||
.Pp
|
||||
.Fn QueuePush
|
||||
as well as
|
||||
.Fn QueueFull
|
||||
and
|
||||
.Fn QueueEmpty
|
||||
all return boolean values. In the case of
|
||||
.Fn QueuePush
|
||||
whether or not the push was actually successful is returned. This will
|
||||
only happen if the queue is already full, or a
|
||||
.Dv NULL
|
||||
pointer is passed.
|
||||
.Pp
|
||||
.Fn QueuePop
|
||||
and
|
||||
.Fn QueuePeek
|
||||
both return caller-managed pointers that would have been at some point
|
||||
pushed into the queue with the
|
||||
.Fn QueuePush
|
||||
function. They may also return
|
||||
.Dv NULL
|
||||
if the queue is empty.
|
||||
.Sh SEE ALSO
|
||||
.Xr Array 3 ,
|
||||
.Xr HashMap 3
|
|
@ -1,44 +0,0 @@
|
|||
.Dd $Mdocdate: February 16 2023 $
|
||||
.Dt RAND 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Rand
|
||||
.Nd Thread-safe random numbers.
|
||||
.Sh SYNOPSIS
|
||||
.In Rand.h
|
||||
.Ft int
|
||||
.Fn RandInt "unsigned int"
|
||||
.Ft void
|
||||
.Fn RandIntN "int *" "size_t" "unsigned int"
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is used for generating random numbers in a thread-safe way. Currently,
|
||||
one seed is shared across all threads, which means only one thread can
|
||||
generate random numbers at a time. In the future, a seed pool may be
|
||||
maintained. The seed is initialized on the first call to a function
|
||||
that needs it. It is initialized with the current timestamp,
|
||||
the process ID, and the thread ID. These should be sufficiently random
|
||||
sources, so the seed should be secure enough.
|
||||
.Pp
|
||||
.Fn RandInt
|
||||
generates a single random integer between 0 and the passed value.
|
||||
.Fn RandIntN
|
||||
takes an integer pointer, a buffer size, and the maximum value a
|
||||
random number is allowed to be. It generates the number of random
|
||||
integers specified by the buffer size, and stores them at the passed
|
||||
pointer. This allows a caller to get multiple random numbers at a
|
||||
time, as each call to
|
||||
.Fn RandInt
|
||||
will have to lock and unlock a mutex, whereas
|
||||
.Fn RandIntN
|
||||
can obtain multiple random integers in a single pass.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn RandInt
|
||||
returns the value of
|
||||
.Xr rand_r 3
|
||||
with the internally-stored seed. The return value should be in the
|
||||
range of 0 to RAND_MAX.
|
||||
.Sh SEE ALSO
|
||||
.Xr Util 3 ,
|
||||
.Xr rand 3
|
|
@ -1,87 +0,0 @@
|
|||
.Dd $Mdocdate: December 12 2022 $
|
||||
.Dt ROUTES 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Routes
|
||||
.Nd Matrix API endpoint abstractions.
|
||||
.Sh SYNOPSIS
|
||||
.In Routes.h
|
||||
.Ft char *
|
||||
.Fn MATRIX_PATH_POP "MATRIX_PATH"
|
||||
.Ft size_t
|
||||
.Fn MATRIX_PATH_PARTS "MATRIX_PATH"
|
||||
.Ft int
|
||||
.Fn MATRIX_PATH_EQUALS "char *" "char *"
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
provides all of the Matrix API route functions, as well as a few
|
||||
helpful macros to be used to declare those route functions, and some
|
||||
macros that are intended to be used inside them.
|
||||
.Pp
|
||||
The route macros are intended to increase the readability of the header,
|
||||
so the individual routes are not documented here; only the helper
|
||||
macros and structures are documented here. Consult the
|
||||
.Pa Routes.h
|
||||
file for a list of the registered route functions.
|
||||
.Pp
|
||||
.Fn MATRIX_PATH_POP
|
||||
and
|
||||
.Fn MATRIX_PATH_PARTS
|
||||
are macros that abstract away the underlying data structure of the
|
||||
path so that that routes don't have to care what it is. The reason
|
||||
this design choice was made was so that the data structure can be
|
||||
switched out without breaking all the routes. These macros should
|
||||
be preferred to the actual underlying data structure functions,
|
||||
because the data structure may change in the future.
|
||||
.Pp
|
||||
At the moment, the path data structure is just an array, but it would
|
||||
be much more efficient to switch to a queue (which can be easily done
|
||||
with the current Queue implementation if we just add a function that
|
||||
computes how many elements are in the queue.)
|
||||
.Pp
|
||||
.Fn MATRIX_PATH_POP
|
||||
returns the next available part of the path, and removes it from
|
||||
the path such that the next call to
|
||||
.Fn MATRIX_PATH_POP
|
||||
returns the part after.
|
||||
.Fn MATRIX_PATH_PARTS
|
||||
returns the number of path parts remaining.
|
||||
.Pp
|
||||
.Fn MATRIX_PATH_EQUALS
|
||||
is just a simple string comparison macro. It takes two strings and
|
||||
returns a boolean value indicating whether or not they're equal.
|
||||
.Pp
|
||||
.Nm
|
||||
also defines
|
||||
.Fn ROUTE
|
||||
and
|
||||
.Fn ROUTE_IMPL .
|
||||
.Fn ROUTE
|
||||
is intended to be used only inside the route header, and should be
|
||||
invoked to declare a new route function prototype. It takes the
|
||||
route function name, which by convention starts with "Route".
|
||||
.Fn ROUTE_IMPL
|
||||
may be used to actually implement a route function. It takes the
|
||||
route function name, and the name of the variable to put the
|
||||
RouteArgs in.
|
||||
.Pp
|
||||
Every route function takes a RouteArgs structure, which is defined
|
||||
as follows:
|
||||
.Bd -literal -offset indent
|
||||
typedef struct RouteArgs
|
||||
{
|
||||
MatrixHttpHandlerArgs *matrixArgs;
|
||||
HttpServerContext *context;
|
||||
MATRIX_PATH *path;
|
||||
} RouteArgs;
|
||||
.Ed
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
Each route returns a JSON hash map that contains the response it
|
||||
intends to return to the client making the request. Routes
|
||||
should NOT return NULL, because then no body will be returned to
|
||||
the client, and that is almost always a bug. The Matrix specification
|
||||
usually mandates that at least an empty JSON object is returned.
|
||||
.Sh SEE ALSO
|
||||
.Xr Matrix 3
|
|
@ -1,27 +0,0 @@
|
|||
.Dd $Mdocdate: December 19 2022 $
|
||||
.Dt SHA2 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Sha2
|
||||
.Nd A simple implementation of the SHA2 hashing functions.
|
||||
.Sh SYNOPSIS
|
||||
.In Sha2.h
|
||||
.Ft char *
|
||||
.Fn Sha256 "char *"
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
This API defines simple functions for computing SHA2 hashes. At the
|
||||
moment, it only defines
|
||||
.Fn Sha256 ,
|
||||
which computes the SHA-256 hash of the given C string. It is not trivial
|
||||
to implement SHA-512 in ANSI C due to the lack of a 64-bit integer
|
||||
type, so that hash function has been omitted.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn Sha256
|
||||
returns a string allocated on the heap using the Memory API, or NULL
|
||||
if there was an error allocating memory for it. The returned string
|
||||
should be freed when it is no longer needed.
|
||||
.Sh SEE ALSO
|
||||
.Xr Memory 3 ,
|
||||
.Xr Base64 3
|
|
@ -1,55 +0,0 @@
|
|||
.Dd $Mdocdate: February 15 2023 $
|
||||
.Dt STR 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Str
|
||||
.Nd Functions for manipulating and creating strings.
|
||||
.Sh SYNOPSIS
|
||||
.In Str.h
|
||||
.Ft char *
|
||||
.Fn StrUtf8Encode "unsigned long"
|
||||
.Ft char *
|
||||
.Fn StrDuplicate "const char *"
|
||||
.Ft char *
|
||||
.Fn StrConcat "size_t" "..."
|
||||
.Ft char *
|
||||
.Fn StrRandom "size_t"
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
provides string-related functions. It is called
|
||||
.Nm ,
|
||||
not String, because some platforms (Windows) do not have
|
||||
case-sensitive filesystems, so String and string are the same thing, which poses
|
||||
a problem because string is a standard library header.
|
||||
.Pp
|
||||
.Fn StrUtf8Encode
|
||||
takes a UTF-8 codepoint and encodes it into a string buffer containing between
|
||||
1 and 4 bytes. The string buffer is allocated on the heap, so it should be freed
|
||||
when it is no longer needed.
|
||||
.Pp
|
||||
.Fn StrDuplicate
|
||||
duplicates a NULL-terminated string, and returns a new string on the heap. This is
|
||||
useful when a function takes in a string that it needs to store for long amounts
|
||||
of time, even perhaps after the original string is gone.
|
||||
.Pp
|
||||
.Fn StrConcat
|
||||
is a var-args function that takes the number of NULL-terminated strings specified
|
||||
by the first argument, and returns a new string that contains their concatenation.
|
||||
It works a lot like
|
||||
.Xr strcat 3 ,
|
||||
but it takes care of allocating memory big enough to hold all the strings. Any
|
||||
strings may be NULL. If a string is NULL, it is treated like an empty string.
|
||||
.Pp
|
||||
.Fn StrRandom
|
||||
generates a random string of the specified length.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn StrUtf8Encode ,
|
||||
.Fn StrDuplicate ,
|
||||
.Fn StrConcat ,
|
||||
and
|
||||
.Fn StrRandom
|
||||
return a pointer to a NULL-terminated C string on the heap, or NULL if a memory
|
||||
allocation error occurs. Returned pointers should be freed using the Memory API.
|
||||
.Sh SEE ALSO
|
||||
.Xr Memory 3
|
|
@ -1,95 +0,0 @@
|
|||
.Dd $Mdocdate: December 10 2022 $
|
||||
.Dt TELODENDRIACONFIG 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm TelodendriaConfig
|
||||
.Nd Parse the configuration file into a structure.
|
||||
.Sh SYNOPSIS
|
||||
.In TelodendriaConfig.h
|
||||
.Ft TelodendriaConfig *
|
||||
.Fn TelodendriaConfigParse "HashMap *" "LogConfig *"
|
||||
.Ft void
|
||||
.Fn TelodendriaConfigFree "TelodendriaConfig *"
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
Validate and maintain the Telodendria server's configuration data. This API
|
||||
builds on the JSON API to add Telodendria-specific parsing. It takes a
|
||||
fully-parsed JSON object and converts it into a TelodendriaConfig, which is
|
||||
much more structured and easier to work with than the JSON. The config
|
||||
structure is not opaque like many other structures in Telodendria. This is
|
||||
intentional; defining functions for all of the fields would just add a lot
|
||||
of unecessary overhead. The structure is defined as follows:
|
||||
.Bd -literal -offset indent
|
||||
typedef struct TelodendriaConfig
|
||||
{
|
||||
char *serverName;
|
||||
char *baseUrl;
|
||||
char *identityServer;
|
||||
|
||||
char *uid;
|
||||
char *gid;
|
||||
char *dataDir;
|
||||
|
||||
unsigned short listenPort;
|
||||
unsigned int flags;
|
||||
unsigned int threads;
|
||||
unsigned int maxConnections;
|
||||
|
||||
size_t maxCache;
|
||||
|
||||
char *logTimestamp;
|
||||
int logLevel;
|
||||
} TelodendriaConfig;
|
||||
.Ed
|
||||
.Pp
|
||||
Since the configuration will live in memory for a long time, it is important
|
||||
that unused values are freed as soon as possible. Therefore, the Telodendria
|
||||
structure is not opaque; values are accessed directly, and they can be
|
||||
freed as the program wishes. Do note that if you're going to free a value, you
|
||||
should set it to NULL, because
|
||||
.Fn TelodendriaConfigFree
|
||||
will unconditionally call
|
||||
.Fn Free
|
||||
on all values.
|
||||
.Pp
|
||||
The flags variable in this structure is a bit field that contains the OR-ed values
|
||||
of any of the given flags:
|
||||
.Bd -literal -offset indent
|
||||
typedef enum TelodendriaConfigFlag
|
||||
{
|
||||
TELODENDRIA_FEDERATION,
|
||||
TELODENDRIA_REGISTRATION,
|
||||
|
||||
TELODENDRIA_LOG_COLOR,
|
||||
|
||||
TELODENDRIA_LOG_FILE,
|
||||
TELODENDRIA_LOG_STDOUT,
|
||||
TELODENDRIA_LOG_SYSLOG
|
||||
} TelodendriaConfigFlag;
|
||||
.Ed
|
||||
.Pp
|
||||
Do note that the actual values of these enums are omitted, but they can be
|
||||
OR-ed together and added to flags.
|
||||
.Pp
|
||||
.Fn TelodendriaConfigParse
|
||||
parses a JSON map, extracting the necessary values, validating them, and then
|
||||
adding them to a new TelodendriaConfig for future use by the program. All values
|
||||
are copied, so the JSON hash map can be safely freed if this function
|
||||
succeeds. It takes a working log configuration so that messages can be written
|
||||
to the log as the parsing progresses, to warn users about default values and
|
||||
report errors, for example.
|
||||
.Pp
|
||||
.Fn TelodendriaConfigFree
|
||||
frees all of the memory allocated for the given configuration. This function
|
||||
unconditionally calls
|
||||
.Fn Free
|
||||
on all items in the structure, so make sure that items that were already freed
|
||||
are NULL.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn TelodendriaConfigParse
|
||||
returns a TelodendriaConfig that is completely independent of the passed
|
||||
configuration hash map, or NULL if one or more required values is missing, or
|
||||
there was some other error while parsing the configuration.
|
||||
.Sh SEE ALSO
|
||||
.Xr Json 3
|
123
man/man3/Uia.3
123
man/man3/Uia.3
|
@ -1,123 +0,0 @@
|
|||
.Dd $Mdocdate: March 7 2023 $
|
||||
.Dt UIA 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Uia
|
||||
.Nd User Interactive Authentication API.
|
||||
.Sh SYNOPSIS
|
||||
.In Uia.h
|
||||
.Ft UiaStage *
|
||||
.Fn UiaStageBuild "char *" "HashMap *"
|
||||
.Ft Array *
|
||||
.Fn UiaDummyFlow "void"
|
||||
.Ft void
|
||||
.Fn UiaCleanup "MatrixHttpHandlerArgs *"
|
||||
.Ft int
|
||||
.Fn UiaComplete "Array *" "HttpServerContext *" "Db *" "HashMap *" "HashMap **" "TelodendriaConfig *"
|
||||
.Ft void
|
||||
.Fn UiaFlowsFree "Array *"
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
takes care of all the logic for performing user interactive
|
||||
authentication as defined by the Matrix specification. API endpoints
|
||||
that require authentication via user interactive authentication
|
||||
build up flows and any necessary parameters, and pass them into
|
||||
.Fn UiaComplete ,
|
||||
which validates
|
||||
.Dv auth
|
||||
objects and maintains session state to track the progress of a
|
||||
client through the user interactive authentication flows. The idea
|
||||
is that an API endpoint will not progress until user interactive
|
||||
authentication has succeeded.
|
||||
.Nm
|
||||
makes it easy for the numerous API endpoints that utilize this
|
||||
authentication mechanism to implement it.
|
||||
.Pp
|
||||
.Fn UiaStageBuild
|
||||
builds a single stage. A stage consists of a string identifying its
|
||||
type, which is used to instruct the client as to what should be
|
||||
done, and parameters, which is a JSON object that contains
|
||||
implementation-specific parameters for completing the stage. This
|
||||
function takes those two parameters in that order.
|
||||
.Pp
|
||||
.Fn UiaDummyFlow
|
||||
builds a flow that consists only of a dummy stage. This is useful
|
||||
when an endpoint is required to use user interactive authentication,
|
||||
but doesn't actually want to require the user to do anything. Since
|
||||
the dummy flow is a pretty common flow, it seemed sensible to have
|
||||
a function for it. Other flows are built by the caller that wishes
|
||||
to perform user interactive authentication.
|
||||
.Pp
|
||||
.Fn UiaCleanup
|
||||
should be called periodically to purge old sessions. Session are
|
||||
only valid for a few minutes after their last access. After that, they
|
||||
should be purged so the database doesn't fill up with session files.
|
||||
This function is specifically designed to be called via
|
||||
.Xr Cron 3 .
|
||||
.Pp
|
||||
.Fn UiaComplete
|
||||
does the bulk of the work for user interactive authentication. It
|
||||
takes many paramters:
|
||||
.Bl -bullet
|
||||
.It
|
||||
An array of arrays of stages. Stages should be created with
|
||||
.Fn UiaStageBuild ,
|
||||
and then put into an array to create a flow. Those flows should then
|
||||
be put into an array and passed as this paramter. Do note that
|
||||
because of the loose typing of Telodendria's Array API, it is very
|
||||
easy to make mistakes here; if you are implementing a new route that
|
||||
requires user interactive authentication, then refer to an existing
|
||||
route so you can see how it works.
|
||||
.It
|
||||
An HTTP server context. This is required to set the response headers
|
||||
in the event of an error.
|
||||
.It
|
||||
The database where user interactive authentication sessons are
|
||||
persisted.
|
||||
.It
|
||||
The JSON request body that contains the client's
|
||||
.Dv auth
|
||||
object, which will be read, parsed, and handled as appropriate.
|
||||
.It
|
||||
A pointer to a pointer where a JSON response can be placed if
|
||||
necessary. If
|
||||
.Fn UiaComplete
|
||||
encounters a client error, such as a failure to authenticate, or
|
||||
outstanding stages that have not been completed, it will place a
|
||||
JSON response here that is expected to be returned to the client.
|
||||
This response will include a description of all the flows, stages,
|
||||
and their parameters.
|
||||
.It
|
||||
A valid Telodendria configuration structure, because a few values
|
||||
are read from the configuration during certain stages of the
|
||||
authentication.
|
||||
.El
|
||||
.Pp
|
||||
.Fn UiaFlowsFree
|
||||
frees an array of flows, as described above. Even though the
|
||||
caller constructs this array, it is convenient to free it in its
|
||||
entirety in a single function call.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn UiaStageBuild
|
||||
returns an opaque structure that represents a user interactive
|
||||
authentication stage, and any parameters the client needs to complete
|
||||
it. It may return NULL if there is an error allocating memory.
|
||||
.Pp
|
||||
.Fn UiaDummyFlow
|
||||
returns an array that represents a dummy authentication flow, or
|
||||
NULL if it could not allocate memory for it.
|
||||
.Pp
|
||||
.Fn UiaComplete
|
||||
returns an integer less than zero if it experiences an internal
|
||||
failure, such as a failure to allocate memory, or a corrupted
|
||||
database. It returns 0 if the client has remaining stages to
|
||||
complete. In this case, it will have set the response headers
|
||||
and the passed response pointer, so the caller should immediately
|
||||
return the response to the client. This function returns 1 if the
|
||||
user has successfully completed all stages. Only in this case shall
|
||||
the caller proceed with its logic.
|
||||
.Sh SEE ALSO
|
||||
.Xr User 3 ,
|
||||
.Xr Db 3 ,
|
||||
.Xr Cron 3
|
219
man/man3/User.3
219
man/man3/User.3
|
@ -1,219 +0,0 @@
|
|||
.Dd $Mdocdate: March 6 2023 $
|
||||
.Dt USER 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm User
|
||||
.Nd Convenience functions for working with local users.
|
||||
.Sh SYNOPSIS
|
||||
.In User.h
|
||||
.Ft int
|
||||
.Fn UserValidate "char *" "char *"
|
||||
.Ft int
|
||||
.Fn UserHistoricalValidate "char *" "char *"
|
||||
.Ft int
|
||||
.Fn UserExists "Db *" "char *"
|
||||
.Ft User *
|
||||
.Fn UserCreate "Db *" "char *" "char *"
|
||||
.Ft User *
|
||||
.Fn UserLock "Db *" "char *"
|
||||
.Ft User *
|
||||
.Fn UserAuthenticate "Db *" "char *"
|
||||
.Ft int
|
||||
.Fn UserUnlock "User *"
|
||||
.Ft UserLoginInfo *
|
||||
.Fn UserLogin "User *" "char *" "char *" "char *" "int"
|
||||
.Ft char *
|
||||
.Fn UserGetName "User *"
|
||||
.Ft int
|
||||
.Fn UserCheckPassword "User *" "char *"
|
||||
.Ft int
|
||||
.Fn UserSetPassword "User *" "char *"
|
||||
.Ft int
|
||||
.Fn UserDeactivate "User *"
|
||||
.Ft HashMap *
|
||||
.Fn UserGetDevices "User *"
|
||||
.Ft UserAccessToken *
|
||||
.Fn UserGenerateAccessToken "User *" "char *" "int"
|
||||
.Ft int
|
||||
.Fn UserAccessTokenSave "Db *" "UserAccessToken *"
|
||||
.Ft void
|
||||
.Fn UserAccessTokenFree "UserAccessToken *"
|
||||
.Ft int
|
||||
.Fn UserDeleteToken "User *" "char *"
|
||||
.Ft int
|
||||
.Fn UserDeleteTokens "User *"
|
||||
.Ft UserId *
|
||||
.Fn UserIdParse "char *" "char *"
|
||||
.Ft void
|
||||
.Fn UserIdFree "UserId *"
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
API provides a wrapper over the database and offers an easy way for managing
|
||||
local users. It supports all of the locking mechanisms that the database does,
|
||||
and provides features for authenticating local users, among other tasks.
|
||||
.Pp
|
||||
.Bd -literal -offset indent
|
||||
typedef struct UserLoginInfo
|
||||
{
|
||||
UserAccessToken *accessToken;
|
||||
char *refreshToken;
|
||||
} UserLoginInfo;
|
||||
|
||||
typedef struct UserAccessToken
|
||||
{
|
||||
char *user;
|
||||
char *string;
|
||||
char *deviceId;
|
||||
long lifetime;
|
||||
} UserAccessToken;
|
||||
|
||||
typedef struct UserId
|
||||
{
|
||||
char *localpart;
|
||||
char *server;
|
||||
} UserId;
|
||||
.Ed
|
||||
.Pp
|
||||
.Fn UserValidate
|
||||
takes a localpart and domain as separate parameters and validates it against the
|
||||
rules of the Matrix specification. The reason the domain is required is because
|
||||
the spec imposes limitations on the length of the user name, and the longer the
|
||||
domain name is, the shorter the local part can be. This function is used to
|
||||
ensure that client-provided localparts are valid on this server.
|
||||
.Fn UserHistoricalValidate
|
||||
is called the exact same way, except it is a little more lenient. It is used to
|
||||
validate user parts on other servers, since some usernames might exist that are
|
||||
not fully spec compliant, but remain in use due to historical reasons.
|
||||
.Pp
|
||||
.Fn UserExists
|
||||
takes a localpart and checks whether or not it exists in the database.
|
||||
.Pp
|
||||
.Fn UserCreate
|
||||
creates a new user. It takes a localpart, which is assumed to be valid, and
|
||||
a password.
|
||||
.Pp
|
||||
.Fn UserLock
|
||||
takes a localpart and obtains a database reference to the user represented by that
|
||||
localpart. It behaves analogously to
|
||||
.Fn DbLock ,
|
||||
and in fact uses it under the hood to ensure that the user can only be modified
|
||||
by the thread that has locked the user.
|
||||
.Fn UserUnlock
|
||||
returns the user reference back to the database. It uses
|
||||
.Fn DbUnlock
|
||||
under the hood.
|
||||
.Pp
|
||||
.Fn UserAuthenticate
|
||||
takes an access token, figures out what user it belongs to, and returns the
|
||||
reference to that user. This function should be used by most endpoints that
|
||||
require valid user authentication, since most endpoints are authenticated via
|
||||
access tokens.
|
||||
.Pp
|
||||
.Fn UserLogin
|
||||
is used for logging in a user. It takes the user's password, device ID, device
|
||||
display name, and a boolean value indicating whether or not the client supports
|
||||
refresh tokens. This function logs in the user and generates an access token to be
|
||||
returned to the client.
|
||||
.Pp
|
||||
.Fn UserGetName
|
||||
gets the name attached to a user object. It can be used for the few cases where
|
||||
it's necessary to know the localpart of a user.
|
||||
.Pp
|
||||
.Fn UserCheckPassword
|
||||
takes a password and verifies it against a user object. Telodendria does not
|
||||
store passwords in plain text, so this function hashes the password and and
|
||||
checks it against what's stored in the database.
|
||||
.Pp
|
||||
.Fn UserSetPassword
|
||||
resets the given user's password by hashing a plain text password and
|
||||
storing it in the database.
|
||||
.Pp
|
||||
.Fn UserDeactivate
|
||||
deactivates a user such that it can no longer be used to log in, but
|
||||
the username is still taken. This is to prevent future users from
|
||||
pretending to be previous users of a given localpart.
|
||||
.Pp
|
||||
.Fn UserGetDevices
|
||||
fetches the devices that belong to the user, in JSON format,
|
||||
identical to what's stored in the database. In fact, this JSON is
|
||||
still linked to the database, so it should not be freed with
|
||||
.Fn JsonFree .
|
||||
.Pp
|
||||
.Fn UserAccessTokenGenerate ,
|
||||
.Fn UserAccessTokenSave ,
|
||||
and
|
||||
.Fn UserAccessTokenFree
|
||||
are used for managing individual access tokens on a user. They
|
||||
operate on the UserAccessToken structure.
|
||||
.Fn UserAccessTokenGenerate
|
||||
takes the user localpart to generate the token for, the device ID,
|
||||
for the token, and a boolean value indicating whether or not the token
|
||||
should expire.
|
||||
.Fn UserAccessTokenSave
|
||||
writes the access token to the database.
|
||||
.Pp
|
||||
.Fn UserDeleteToken
|
||||
and
|
||||
.Fn UserDeleteTokens
|
||||
delete a specific access token/refresh token pair, or all the access
|
||||
and refresh tokens for a given user, respectively.
|
||||
.Pp
|
||||
.Fn UserIdParse
|
||||
parses either a localpart or a fully-qualified Matrix ID.
|
||||
.Fn UserIdFree
|
||||
frees the result of this parsing.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn UserValidate ,
|
||||
.Fn UserHistoricalValidate ,
|
||||
.Fn UserExists ,
|
||||
.Fn UserUnlock ,
|
||||
.Fn UserCheckPassword ,
|
||||
.Fn UserSetPassword ,
|
||||
.Fn UserDeactivate ,
|
||||
.Fn UserAccessTokenSave ,
|
||||
.Fn UserDeleteToken ,
|
||||
and
|
||||
.Fn UserDeleteTokens
|
||||
all return a boolean value. Non-zero values indicate success, and zero values
|
||||
indicate failure.
|
||||
.Pp
|
||||
.Fn UserCreate ,
|
||||
.Fn UserLock ,
|
||||
and
|
||||
.Fn UserAuthenticate
|
||||
return a pointer to a User, or NULL if an error occurred.
|
||||
.Pp
|
||||
.Fn UserGetName
|
||||
returns a pointer to the string that holds the localpart of the user represented
|
||||
by the given user pointer. This pointer should not be freed by the caller , as it
|
||||
is used internally and will be freed when the user is unlocked.
|
||||
.Pp
|
||||
.Fn UserLogin
|
||||
returns a UserLoginInfo struct, or
|
||||
.Dv NULL
|
||||
if something goes wrong.
|
||||
All this information should be returned to the client that is logging in. If the
|
||||
client doesn't support refresh tokens, then refreshToken will be NULL.
|
||||
.Pp
|
||||
.Fn UserGetDevices
|
||||
returns a JSON object that is linked to the database, or NULL if
|
||||
there was an error. The result should not be freed with
|
||||
.Fn JsonFree
|
||||
because it is still directly attached to the database. This object
|
||||
is an exact representation of what is stored on the disk.
|
||||
.Pp
|
||||
.Fn UserAccessTokenGenerate
|
||||
generates an access token structure that should be freed when it is
|
||||
no longer needed, or
|
||||
.Dv NULL
|
||||
if there was a memory error.
|
||||
.Pp
|
||||
.Fn UserIdParse
|
||||
returns a UserId structure that should be freed when it is no longer
|
||||
needed, or
|
||||
.Dv NULL
|
||||
if there was a memory error.
|
||||
.Sh SEE ALSO
|
||||
.Xr Db 3
|
101
man/man3/Util.3
101
man/man3/Util.3
|
@ -1,101 +0,0 @@
|
|||
.Dd $Mdocdate: February 15 2023 $
|
||||
.Dt UTIL 3
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Util
|
||||
.Nd Some misc. helper functions that don't need their own headers.
|
||||
.Sh SYNOPSIS
|
||||
.In Util.h
|
||||
.Ft unsigned long
|
||||
.Fn UtilServerTs "void"
|
||||
.Ft unsigned long
|
||||
.Fn UtilLastModified "char *"
|
||||
.Ft int
|
||||
.Fn UtilMkdir "const char *" "const mode_t"
|
||||
.Ft int
|
||||
.Fn UtilSleepMillis "long"
|
||||
.Ft size_t
|
||||
.Fn UtilParseBytes "char *"
|
||||
.Ft ssize_t
|
||||
.Fn UtilGetDelim "char **" "size_t *" "int" "FILE *"
|
||||
.Ft ssize_t
|
||||
.Fn UtilGetLine "char **" "size_t *" "FILE *"
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
This header holds a number of random functions related to strings,
|
||||
time, and other tasks that don't require a full API, just one or
|
||||
two functions. For the most part, the functions here are entirely
|
||||
standalone, depending only on POSIX functions, however there are a
|
||||
few that specifically utilize Telodendria APIs. Those are noted.
|
||||
.Pp
|
||||
.Fn UtilServerTs
|
||||
gets the current time in milliseconds since the Unix epoch. This
|
||||
uses
|
||||
.Xr gettimeofday 2
|
||||
and time_t, and converts it to a single number, which is then
|
||||
returned to the caller. A note on the 2038 problem: as long as
|
||||
sizeof(long) >= 8, that is, as long as the long datatype is 64 bits
|
||||
or more, which it is on all modern 64-bit Unix-like operating
|
||||
systems, then everything should be fine. Expect Telodendria on 32 bit
|
||||
machines to break in 2038. I didn't want to try to hack together
|
||||
some system to store larger numbers than the architecture supports.
|
||||
We can always re-evaluate things over the next decade.
|
||||
.Pp
|
||||
.Fn UtilMkdir
|
||||
behaves just like the system call
|
||||
.Xr mkdir 2 ,
|
||||
but it creates any intermediate directories if necessary, unlike
|
||||
.Xr mkdir 2 .
|
||||
.Pp
|
||||
.Fn UtilSleepMillis
|
||||
sleeps the calling thread for the given number of milliseconds. It
|
||||
occurred to me that POSIX does not specify a super friendly way to
|
||||
sleep, so this is a wrapper around the POSIX
|
||||
.Xr nanosleep 2
|
||||
designed to make its usage much, much simpler.
|
||||
.Pp
|
||||
.Fn UtilLastModified
|
||||
uses
|
||||
.Xr stat 2
|
||||
to get the last modified time of the given file. This is used
|
||||
primarily for caching file data.
|
||||
.Pp
|
||||
.Fn UtilParseBytes
|
||||
is a highly specialized function used in parsing the configuration file.
|
||||
It takes in a string which is supposed to represent a number of bytes.
|
||||
It must consist of an integer, followed by an optional suffix of k, K, m, M,
|
||||
g, or G, indicating the value is kilobytes, megabytes, or gigabytes.
|
||||
.Pp
|
||||
.Fn UtilGetDelim
|
||||
and
|
||||
.Fn UtilGetLine
|
||||
work identically to the POSIX equivalents, documented in
|
||||
.Xr getdelim 3 ,
|
||||
except it assumes pointers were allocated using the Memory API, and it
|
||||
uses the Memory API itself to reallocate necessary pointers.
|
||||
.Sh RETURN VALUES
|
||||
.Pp
|
||||
.Fn UtilServerTs
|
||||
and
|
||||
.Fn UtilLastModified
|
||||
return timestamps in the form of milliseconds since the Unix epoch as an unsigned
|
||||
long. The Matrix specification requires timestamps be in milliseconds, so these
|
||||
functions are designed to make that easy and convenient.
|
||||
.Pp
|
||||
.Fn UtilMkdir
|
||||
returns 0 on success, and -1 on failure, just like
|
||||
.Xr mkdir 2 .
|
||||
It also sets errno as appropriate.
|
||||
.Pp
|
||||
.Fn UtilSleepMillis
|
||||
returns the result of calling
|
||||
.Xr nanosleep 2 .
|
||||
.Pp
|
||||
.Fn UtilParseBytes
|
||||
returns a number of bytes, or 0 if there was an error parsing the byte string.
|
||||
.Pp
|
||||
.Fn UtilGetDelim
|
||||
and
|
||||
.Fn UtilGetLine
|
||||
return the same value as their POSIX equivalents, documented in
|
||||
.Xr getdelim 3 .
|
|
@ -1,209 +0,0 @@
|
|||
.Dd $Mdocdate: February 15 2023 $
|
||||
.Dt TELODENDRIA.CONF 5
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm telodendria.conf
|
||||
.Nd Configuration file for Telodendria.
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is the configuration file for the Telodendria homeserver,
|
||||
.Xr telodendria 8 .
|
||||
Telodendria is designed to be extremely configurable. As such,
|
||||
it has a fairly extensive configuration file. The configuration
|
||||
file can be passed to the Telodendria binary with the
|
||||
.Sy -f
|
||||
option, and is typically located at
|
||||
.Pa /etc/telodendria.conf
|
||||
.sp
|
||||
.Nm
|
||||
uses JSON for its configuration file syntax, which should be
|
||||
familiar. Very early versions of
|
||||
.Nm
|
||||
used a custom OpenBSD-style configuration file, but this was
|
||||
not as versatile or familiar as JSON.
|
||||
.Sh DIRECTIVES
|
||||
Here are the top-level directives:
|
||||
.Bl -tag -width Ds
|
||||
.It Ic listen Ar port
|
||||
The port to listen on. Telodendria will bind to all interfaces, but it
|
||||
is recommended to configure your firewall so that it only listens on
|
||||
localhost, and then configure a reverse proxy such as
|
||||
.Xr relayd 8
|
||||
in front of it, because Telodendria does not implement TLS. Note that
|
||||
Telodendria doesn't provide multiple ports for the various services it
|
||||
offers. ALl APIs are made available over the same port, so care should
|
||||
be taken in
|
||||
.Xr relayd.conf 5
|
||||
to ensure that only the public Matrix API paths are made available over
|
||||
the internet.
|
||||
.Ar port
|
||||
should be a decimal port number. This directive is entirely optional. If
|
||||
it is omitted, then Telodendria will listen on port 8008 by default.
|
||||
.It Ic serverName Ar name
|
||||
Configure the domain name of your homeserver. Note that Matrix servers
|
||||
cannot be migrated to other domains, so once this is set, it should never
|
||||
change unless you want unexpected things to happen, or you want to start
|
||||
over.
|
||||
.Ar name
|
||||
should be a DNS name that can be publically resolved. This directive
|
||||
is required.
|
||||
.It Ic baseUrl Ar url
|
||||
Set the server's base URL.
|
||||
.Ar url
|
||||
should be a valid URL, complete with the protocol. It does not need to
|
||||
be the same as the server name; in fact, it is common for a subdomain of
|
||||
the server name to be the base URL for the Matrix homeserver.
|
||||
.Pp
|
||||
This URL is the URL at which Matrix clients will connect to the server,
|
||||
and is thus served as a part of the
|
||||
.Pa .well-known
|
||||
manifest.
|
||||
.Pp
|
||||
This directive is optional. If it is not specified, it is automatically
|
||||
deduced from the server name.
|
||||
.It Ic identityServer Ar url
|
||||
The identity server that clients should use to perform identity lookups.
|
||||
.Pp
|
||||
.Ar url
|
||||
follows the same rules as
|
||||
.Ic baseUrl .
|
||||
.Pp
|
||||
This directive is optional. If it is not specified, it is automatically
|
||||
set to be the same as the base URL.
|
||||
.It Ic runAs Ar uidObj
|
||||
The effective UNIX user and group to drop to after binding to the socket
|
||||
and changing the filesystem root for the process. This only works if
|
||||
Telodendria is running as the root user, and is used as a security mechanism.
|
||||
If this option is set and Telodendria is started as a non-priviledged user,
|
||||
then a warning is printed to the log if that user does not match what's
|
||||
specified here. This directive is optional, but should be used as a sanity
|
||||
check, if nothing more, to make sure the permissions are working properly.
|
||||
.Pp
|
||||
This directive takes an object with the following directives:
|
||||
.Bl -tag -width Ds
|
||||
.It Ic uid Ar user
|
||||
The UNIX username to drop to. If
|
||||
.Ic runAs
|
||||
is specified, this directive is required.
|
||||
.It Ic gid Ar group
|
||||
The UNIX group to drop to. This directive is optional; if it is not
|
||||
specified, then the value of
|
||||
.Ic uid
|
||||
is copied.
|
||||
.El
|
||||
.It Ic dataDir Ar directory
|
||||
The data directory into which Telodendria will write all user and event
|
||||
information. Telodendria doesn't use a database like other Matrix homeserver
|
||||
implementations; it uses a flat-file directory structure, similar to how an
|
||||
SMTP server uses Maildirs to deliver email. This directive is required.
|
||||
.Pp
|
||||
Telodendria will
|
||||
.Xr chroot 2
|
||||
into this directory as soon as possible for security reasons. If the
|
||||
.Ic log
|
||||
directive is configured to write to a file, the log file will be written
|
||||
in the data directory.
|
||||
.Ar directory
|
||||
should be an absolute path, under which all Telodendria data will live.
|
||||
.It Ic federation Ar true|false
|
||||
Whether to enable federation with other Matrix homeservers or not. Matrix is
|
||||
by its very nature a federated protocol, but if you just want to run your
|
||||
own internal chat server with no contact with the outside, then you can use
|
||||
this option to disable federation. It is highly recommended to set this to
|
||||
.Ar true ,
|
||||
however, if you wish to be able to communicate with users on other Matrix
|
||||
servers. This directive is required.
|
||||
.It Ic registration Ar true|false
|
||||
Whether or not to enable new user registration or not. For security and anti-spam
|
||||
reasons, you can set this to
|
||||
.Ar false .
|
||||
If you do, you can still add users via the administrator API. In an ideal world,
|
||||
everyone would run their own homeserver, so no public registration would ever
|
||||
be required. Unfortunately, not everyone has the means to run their own homeserver,
|
||||
especially because of the fact that public IPv4 addresses are becoming increasingly
|
||||
harder to come by. If you would like to provide a service to those that are unable
|
||||
to run their own homeserver, you can aset this to
|
||||
.Ar true ,
|
||||
which will allow anyone to create an account. Telodendria should be capable of handling
|
||||
a large amount of users without difficulty or security issues. This directive is
|
||||
required.
|
||||
.It Ic log Ar logObj
|
||||
The log file configuration. Telodendria uses its own logging facility, which can
|
||||
output logs to standard output, a file, or the syslog. This directive is required,
|
||||
and it takes an object with the following directives:
|
||||
.Bl -tag -width Ds
|
||||
.It Ic output Ar stdout|file|syslog
|
||||
The lot output destination. If set to
|
||||
.Ar file ,
|
||||
Telodendria will log to
|
||||
.Pa telodendria.log
|
||||
inside the
|
||||
.Ic dataDir .
|
||||
.It Ic level Ar error|warning|notice|message|debug
|
||||
The level of messages to log at. Each level shows all the levels above it. For
|
||||
example, setting the level to
|
||||
.Ar error
|
||||
will show only errors, while setting the level to
|
||||
.Ar warning
|
||||
will show warnings and errors.
|
||||
.Ar notice
|
||||
shows notices, warnings, and errors, and so on. The
|
||||
.Ar debug
|
||||
level shows all messages.
|
||||
.It Ic timestampFormat Ar format|none|default
|
||||
If you want to customize the timestamp format shown in the log, or disable it
|
||||
altogether, you can do so via this option. Acceptable values are
|
||||
.Ar none ,
|
||||
.Ar default ,
|
||||
or a formatter string as described by your system's
|
||||
.Xr strftime 3 .
|
||||
This option only applies if
|
||||
.Ic log
|
||||
is "stdout" or "file".
|
||||
.It Ic color Ar true|false
|
||||
Whether or not to enable colored output on TTYs. Note that ANSI color sequences
|
||||
will not be written to a log file, only a real terminal, so this option only
|
||||
applies if the log is being written to a standard output which is connected to
|
||||
a terminal.
|
||||
.Pp
|
||||
This option only applies if
|
||||
.Ic log
|
||||
is "stdout".
|
||||
.El
|
||||
.It Ic threads Ar count
|
||||
How many worker threads to spin up to handle requests. This should generally be
|
||||
less than the total CPU core count, to prevent overloading the system. The most
|
||||
efficient number of threads ultimately depends on the configuration of the
|
||||
machine running Telodendria, so you may just have to play around with different
|
||||
values here to see which gives the best performance.
|
||||
.It Ic maxConnections Ar count
|
||||
The maximum number of simultanious connections to allow to the daemon. This option
|
||||
prevents the daemon from allocating large amounts of memory in the even that it
|
||||
undergoes a denial of service attack. It typically does not need to be adjusted.
|
||||
.It Ic maxCache Ar bytes
|
||||
The maximum size of the cache. Telodendria relies heavily on caching to speed
|
||||
things up. The cache grows as data is loaded from the data directory. All cache
|
||||
is stored in memory. This option limits the size of the memory cache. If you have
|
||||
a system that has a lot of memory, you'll get better performance if this option
|
||||
is set higher. Otherwise, this value should be lowered on systems that have
|
||||
minimal memory available.
|
||||
.El
|
||||
.Sh FILES
|
||||
.Bl -tag -width Ds
|
||||
.It Pa /etc/telodendria.conf
|
||||
The default
|
||||
.Xr telodendria 8
|
||||
configuration file.
|
||||
.It Pa /var/telodendria
|
||||
The recommended data directory.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
Please consult the default
|
||||
.Nm
|
||||
that ships with Telodendria for a complete example. If you installed Telodendria
|
||||
as a package, then the example should be located at the default location. If you
|
||||
are building from source, you can find the default config in the
|
||||
.Pa contrib/
|
||||
directory.
|
||||
.Sh SEE ALSO
|
||||
.Xr telodendria 8
|
|
@ -1,159 +0,0 @@
|
|||
.Dd $Mdocdate: November 20 2022 $
|
||||
.Dt PORTING 7
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm porting
|
||||
.Nd Some guidelines for packaging Telodendria for your operating system.
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
Telodendria is distributed at source code, and does not offer a convenient
|
||||
install process. This is intentional; the Telodendria project is primarily
|
||||
concerned with developing Telodendria itself, not packaging it for the
|
||||
hundreds of different operating systems and Linux distributions that exist.
|
||||
It is my firm belief that distributing an open source project is not the
|
||||
job of the open source developer; that's the reason software distributions
|
||||
exist, to collect and distribute software.
|
||||
.Pp
|
||||
It would be impossible to single-handedly package Telodendria for every
|
||||
platform, because each platform has very different expectations for
|
||||
software. Even different Linux distributions have different conventions
|
||||
for where manual pages, binaries, and configuration files go.
|
||||
.Pp
|
||||
That being said, this page aims to assist those who want to package
|
||||
Telodendria for their operating system or software distribution.
|
||||
.Pp
|
||||
See
|
||||
.Xr td 8
|
||||
for instructions on how to build Telodendria. Only proceed with packaging
|
||||
Telodendria after you have successfully built it on your operating system.
|
||||
.Pp
|
||||
To package Telodendria, you should collect the following files, and figure
|
||||
out where they should be installed for your system:
|
||||
.Bl -bullet
|
||||
.It
|
||||
The telodendria server binary itself:
|
||||
.Pa build/telodendria
|
||||
.It
|
||||
All manual pages in the
|
||||
.Pa man/
|
||||
directory that are prefixed with "telodendria". These are the user documentation
|
||||
pages. All pages that do not have the "telodendria" prefix are intended only
|
||||
for developers, and so do not need to be installed to the system.
|
||||
.It
|
||||
An init script. People that wish to install Telodendria to their system
|
||||
expect it to be integrated enough that Telodendria can be easily started at
|
||||
boot, and otherwise managed by the system's daemon tools, be it systemd, or
|
||||
another init system. Consult your system's documentation for writing an
|
||||
init script. Do note that Telodendria does not fork itself to the background;
|
||||
the init script should do that. Also note that Telodendria responds to SIGINT,
|
||||
so a SIGINT should be sent to stop Telodendria instead of a SIGTERM or SIGKILL.
|
||||
.It
|
||||
A sample
|
||||
.Pa telodendria.conf
|
||||
file. Whether this file is placed at the actual configuration file location,
|
||||
or a directory containing configuration file samples is entirely up to the
|
||||
packager. You can use or adapt any of the configuration files in
|
||||
.Pa contrib/ ,
|
||||
or write your own specifically for your package.
|
||||
.El
|
||||
.Pp
|
||||
Once you have collected the files that need to be installed, make sure your
|
||||
package performs the following tasks on install:
|
||||
.Bl -bullet
|
||||
.It
|
||||
If necessary, depending on the config used, create a new system user for
|
||||
the Telodendria daemon to run as.
|
||||
.It
|
||||
If conventional for your system, enable the Telodendria init script so
|
||||
that Telodendria is started on system boot.
|
||||
.It
|
||||
Insruct the user to carefully read the sample
|
||||
.Pa telodendria.conf
|
||||
as well as the
|
||||
.Xr telodendria.conf 5
|
||||
manual page before starting Telodendria.
|
||||
.El
|
||||
.Pp
|
||||
The goal of a package should be to get everything as ready-to-run as possible.
|
||||
The user should only have to change one or two default options in the configuration
|
||||
file before Telodendria can be started.
|
||||
.Pp
|
||||
Remember to publicly document the setup of Telodendria on your platform so
|
||||
that users can easily get Telodendria running. If you're packaging Telodendria
|
||||
for a containerization system such as Docker, you can omit the things that
|
||||
containers typically do not have, such as the init script and man pages.
|
||||
.Pp
|
||||
Also remember that your port should feel like it belongs on your target system.
|
||||
Follow all of your system's conventions when placing files on the filesystem,
|
||||
so your users know what to expect. The goal is not to have a unified experience
|
||||
across all operating systems, rather, you should cater to the opinions of your
|
||||
operating system. Telodendria is architected in such a way that it does not
|
||||
impose the developers opinions of where things should go.
|
||||
.Sh PORTS REPOSITORY
|
||||
.Pp
|
||||
The Telodendria project provides a
|
||||
.Pa Telodendria-Ports
|
||||
repository that is intended to serve as the official staging environment for
|
||||
ports and packages of Telodendria to various operating systems. You can
|
||||
most likely take inspiration from the files stored in this repository, or even
|
||||
straight up copy and modify files for your own port if you'd like.
|
||||
Telodendria-Ports is a convenient resource for new porters. You can grab
|
||||
a copy of the ports repository like this:
|
||||
.Bd -literal -offset indent
|
||||
$ cvs -d anoncvs@bancino.net:/cvs checkout -P Telodendria-Ports
|
||||
.Ed
|
||||
.Pp
|
||||
(It is assumed that you have read
|
||||
.Xr telodendria-contributing 7 ,
|
||||
so you already have the proper tools for getting the ports repository.)
|
||||
.Pp
|
||||
The repository is structured in such a way that each operating system or
|
||||
software distribution has a directory. For example, the OpenBSD port has an
|
||||
.Pa OpenBSD/
|
||||
directory. If you make a HaikuOS port, then make a
|
||||
.Pa HaikuOS/
|
||||
directory.
|
||||
.Pp
|
||||
The structure of the operating system directories themselves is really defined
|
||||
by the conventions of the packaging system you're working with. There's no standard
|
||||
structure, as each system does things differently. Just use the directory as a
|
||||
working space that stores all the files your packaging system needs to build
|
||||
a package for Telodendria.
|
||||
.Pp
|
||||
The exact procedure for interacting with this repository is also defined by how
|
||||
your packaging system works. For OpenBSD, one is required to copy the
|
||||
.Pa OpenBSD/
|
||||
directory to
|
||||
.Pa /usr/ports/net/telodendria ,
|
||||
and then copy files back and forth when modifications are made. You may be able
|
||||
to get away with building your package in place, without having to copy files
|
||||
anywhere. Otherwise, you can try symlinking directories, but OpenBSD ports did
|
||||
not like this at all.
|
||||
.Pp
|
||||
Submitting your port files to Telodendria-Ports is by no means required,
|
||||
but it may be helpful to have a public record that you're working on a port,
|
||||
and it's definitely helpful to have a consolidated list of all the ports out
|
||||
there, making it much easier to determine whether or not a given platform
|
||||
has a port, especially if you're unfamilier with that platform's port system.
|
||||
If you are capable of managing your port entirely within your packaging system,
|
||||
then go for it! I just wanted a staging environment that I have commit access to
|
||||
for my ports, allowing me to prototype and test my port before submitting it
|
||||
to the actual ports tree.
|
||||
.Pp
|
||||
It is important to note that I only maintain the OpenBSD port, because that's
|
||||
the operating system I use. But, notice that I follow my own rules; nothing
|
||||
inherently OpenBSD-specific, besides a few optional files in
|
||||
.Pa contrib/ ,
|
||||
actually exists in the main repository. All operating-system specific files,
|
||||
such as init scripts and the like, should go to Telodendria-Ports. It's also
|
||||
important to note that the files placed in Telodendria-Ports are not automatically
|
||||
assumed to be official builds. The developer that committed the files to
|
||||
Telodendria-Ports will most likely also have to get them submitted upstream,
|
||||
because I'm not going to go to all these upstream packagers with the port files
|
||||
here, I'll only do that with ports I officially maintain, which is the
|
||||
OpenBSD port.
|
||||
.Pp
|
||||
.Sh SEE ALSO
|
||||
.Xr telodendria-contributing 7 ,
|
||||
.Xr td 8 ,
|
||||
.Xr telodendria 7
|
|
@ -1,186 +0,0 @@
|
|||
.Dd $Mdocdate: March 7 2023 $
|
||||
.Dt TELODENDRIA-CHANGELOG 7
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Telodendria
|
||||
.Nd The change log for the Telodendria project.
|
||||
.Sh PROJECT STATUS
|
||||
.Pp
|
||||
.Nm
|
||||
is a very ambitious project. There's a lot that needs to happen yet
|
||||
before it is usable. At the moment,
|
||||
.Nm
|
||||
is starting to resemble a Matrix homeserver, but you can't really
|
||||
call it one yet. The foundation is mostly in place; now there's the
|
||||
Matrix specification to implement.
|
||||
.Pp
|
||||
Just because there's not much here yet doesn't mean you should go
|
||||
away! I could always use help, so you are more than welcome to get
|
||||
involved in the project if you want to see things move quicker.
|
||||
Feel free to donate using the links on the project website, or
|
||||
see the
|
||||
.Xr contributing 7
|
||||
page for details on how to get involved. The CVS repository has
|
||||
a file called
|
||||
.Pa TODO.txt ,
|
||||
which contains a checklist of the items that need to be completed.
|
||||
Feel free to grab an item on that list and start writing patches!
|
||||
It's a good idea to join the Matrix rooms noted in
|
||||
.Xr telodendria 7
|
||||
as well, so you can discuss your progress and ask questions.
|
||||
.Sh v0.2.0
|
||||
.Pp
|
||||
Not released yet.
|
||||
.Pp
|
||||
This release is focused on providing a decent amount of the
|
||||
client authentication API. You can now create accounts on a
|
||||
Telodendria homeserver, and log in to get access tokens.
|
||||
.Pp
|
||||
New:
|
||||
.Bl -bullet
|
||||
.It
|
||||
Added the basic form of the user registration API. If
|
||||
registration is enabled in the configuration file, clients
|
||||
can now register for Matrix accounts.
|
||||
.It
|
||||
Added the basic form of the user login API. Clients can now
|
||||
log in to their accounts and generate access tokens to be
|
||||
used to authenticate requests.
|
||||
.It
|
||||
Added the basic form of the user interactive authentication API,
|
||||
which can be used by any endpoints that the spec says require
|
||||
it. Currently, it only implements the dummy and password stages,
|
||||
but more stages, such as the registration token stage, will be
|
||||
added in future releases.
|
||||
.It
|
||||
Added a simple landing page that allows those setting up
|
||||
.Nm
|
||||
to quickly verify that it is accessible where it needs to be.
|
||||
.It
|
||||
Added the static login page for clients that don't support
|
||||
regular login.
|
||||
.El
|
||||
.Pp
|
||||
Changes:
|
||||
.Bl -bullet
|
||||
.It
|
||||
Improved HTTP request logging by removing unnecessary
|
||||
log entries and making errors more specific.
|
||||
.It
|
||||
Leaked memory is now hexdump-ed out to the log if the log
|
||||
level is set to debug. This greatly simplifies debugging,
|
||||
because developers can now easily see exactly what the
|
||||
contents of the leaked memory are. Note that in some
|
||||
circumstances, this memory may contain sensitive data,
|
||||
such as access tokens, usernames, or passwords. However,
|
||||
.Nm
|
||||
should not be leaking memory at all, so if you encounter
|
||||
any leaks, please report them.
|
||||
.It
|
||||
Refactored a lot of the code and accompanying documentation
|
||||
to be more readable and maintainable.
|
||||
.El
|
||||
.Pp
|
||||
Bug fixes:
|
||||
.Pp
|
||||
.Bl -bullet
|
||||
.It
|
||||
Fixed a memory leak that would occur when parsing an invalid
|
||||
JSON object.
|
||||
.It
|
||||
Fixed an edge case where HTTP response headers were being
|
||||
sent before they were properly set, causing the server to
|
||||
report a status of 200 even when that wasn't the desired
|
||||
status.
|
||||
.It
|
||||
Fixed a few memory leaks in the HTTP parameter decoder that
|
||||
would occur in some edge cases.
|
||||
.It
|
||||
Fixed an "off-by-one" error in the HTTP server request
|
||||
parser that prevented GET parameters from being parsed.
|
||||
.It
|
||||
Fixed the database file name generator to prevent directory
|
||||
traversal attacks by replacing special characters with
|
||||
safer ones.
|
||||
.It
|
||||
Fixed a memory leak that would occur when closing a
|
||||
database that contains cached objects.
|
||||
.It
|
||||
Fixed a memory leak that would occur when deleting database
|
||||
objects.
|
||||
.It
|
||||
Fixed a few non-fatal memory warnings that would show up
|
||||
as a result of passing a constant string into certain functions.
|
||||
.El
|
||||
.Pp
|
||||
Misc:
|
||||
.Bl -bullet
|
||||
.It
|
||||
Fixed a bug in
|
||||
.Xr td 8
|
||||
that caused
|
||||
.Xr cvs 1
|
||||
to be invoked in the wrong directory when tagging a new release.
|
||||
.It
|
||||
Added support for environment variable substitution in all site
|
||||
files. This makes it easier to release
|
||||
.Nm
|
||||
versions.
|
||||
.It
|
||||
Fix whitespace issues in various shell scripts.
|
||||
.It
|
||||
Fixed the debug log output so that it only shows the file name,
|
||||
not the entire file path in the repository.
|
||||
.It
|
||||
Updated the copyright year in the source code and compiled output.
|
||||
.It
|
||||
Switch the -std=c89 flag to -ansi instead, as -ansi might be more
|
||||
supported.
|
||||
.It
|
||||
Fixed the -v flag. It now sets the log level to debug as soon
|
||||
as possible to allowe debugging the configuration file parsing
|
||||
if necessary.
|
||||
.El
|
||||
.Pp
|
||||
\&... And many more bug fixes and feature additions! Too much
|
||||
has changed to make a comprehensive change log. A lot of things
|
||||
have been done under the hood to make
|
||||
.Nm
|
||||
easier to develop in the future. Please test the current
|
||||
functionality, and report bugs to the Matrix rooms.
|
||||
.Pp
|
||||
The following platforms have been known to compile and run
|
||||
.Nm :
|
||||
.Bl -bullet
|
||||
.It
|
||||
OpenBSD
|
||||
.It
|
||||
Linux (GNU and non-GNU)
|
||||
.It
|
||||
Windows (via Cygwin)
|
||||
.It
|
||||
FreeBSD
|
||||
.It
|
||||
NetBSD
|
||||
.It
|
||||
DragonFlyBSD
|
||||
.It
|
||||
Haiku OS
|
||||
.It
|
||||
Android (via Termux)
|
||||
.El
|
||||
.Pp
|
||||
.Nm
|
||||
is about being portable; if you compile it on an obscure
|
||||
operating system, do let me know about it!
|
||||
.Sh v0.1.0
|
||||
.Pp
|
||||
Tuesday, December 13, 2022
|
||||
.Pp
|
||||
This is the first public release of
|
||||
.Nm
|
||||
so there are no changes to report. Future releases will
|
||||
have a complete change log entry here.
|
||||
.Pp
|
||||
This is a symbolic release targeted at developers, so there's nothing
|
||||
useful to ordinary users yet. Stay tuned for future releases though!
|
|
@ -1,268 +0,0 @@
|
|||
.Dd $Mdocdate: January 6 2023 $
|
||||
.Dt TELODENDRIA-CONTRIBUTING 7
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm telodendria-contributing
|
||||
.Nd Guide to contributing to the Telodendria project.
|
||||
.Sh DESCRIPTION
|
||||
Telodendria is an open source project. As such, it welcomes
|
||||
contributions. There are many ways you can contribute, and any
|
||||
way you can is greatly appreciated. This page contains some of
|
||||
the ways you can help out.
|
||||
.Sh REPORTING ISSUES
|
||||
Please reach out to the Matrix rooms mentioned at the top of
|
||||
.Xr telodendria 7 .
|
||||
All issue tracking takes place in those rooms. Start by reaching
|
||||
out to the general room, and if you think there's a legitimate
|
||||
problem with Telodendria itself, then stick the issue in the
|
||||
issues room, where it can be discussed further. Issues usually
|
||||
remain in the Matrix rooms, but severe enough issues may be put
|
||||
in a
|
||||
.Pa TODO
|
||||
file in the
|
||||
.Xr cvs 1
|
||||
repository so that they don't get lost.
|
||||
.Sh DEVELOPING
|
||||
The primary language used to write Telodendria code is ANSI C.
|
||||
Other languages you'll find in the Telodendria repository include
|
||||
shell scripts,
|
||||
.Xr mdoc 7 ,
|
||||
and a little bit of HTML and CSS. If you have any experience with
|
||||
any of these languages, your contributions are valuable! Please follow
|
||||
the guidelines on this page to ensure the contribution workflow goes
|
||||
as smoothly as possible.
|
||||
.Ss Getting the Code
|
||||
If you'd like to hack on Telodendria, you'll need the following tools
|
||||
in addition to a C compiler and POSIX shell:
|
||||
.Bl -tag
|
||||
.It Xr cvs 1
|
||||
For checking out and updating your local copy of the source code.
|
||||
.It Xr indent 1
|
||||
For formatting your code before generating patches.
|
||||
.It Xr patch 1
|
||||
For applying patches to your local copy of the source code.
|
||||
.El
|
||||
.sp
|
||||
All of these tools are built into OpenBSD. While you don't have to
|
||||
use OpenBSD to develop Telodendria, it may make the process a bit
|
||||
easier. In fact, these tools where chosen precisely because they
|
||||
were built into my operating system of choice.
|
||||
.sp
|
||||
You can download an official release tarball from the website if
|
||||
you would really like, but the preferred way to get the source
|
||||
code for development is to check it out from CVS. This makes generating
|
||||
patches a lot easier.
|
||||
.Bd -literal -offset indent
|
||||
$ cvs -d anoncvs@bancino.net:/cvs checkout -P Telodendria
|
||||
$ cd Telodendria
|
||||
.Ed
|
||||
.sp
|
||||
If you already checked out the code previously, make sure you update
|
||||
your local copy before you start developing:
|
||||
.Bd -literal -offset indent
|
||||
$ cvs -q update -dP
|
||||
.Ed
|
||||
.sp
|
||||
You should now have the latest source code. Follow the
|
||||
.Sx CODE STYLE
|
||||
as you make your changes. If the
|
||||
.Xr cvs 1
|
||||
command fails with a "Connection refused" error message, try setting
|
||||
the
|
||||
.Ev CVS_RSH
|
||||
environment variable to "ssh", like this:
|
||||
.Bd -literal -offset indent
|
||||
$ export CVS_RSH=ssh
|
||||
.Ed
|
||||
.sp
|
||||
Then run the checkout command again. Some versions of CVS on some
|
||||
systems don't use SSH to checkout by default, so if yours doesn't,
|
||||
you might want to put the above line into your shell init script.
|
||||
.Ss Submitting Patches
|
||||
Telodendria aims at remaining as minimal as possible. This doesn't just
|
||||
mean minimal code, it also means a minimal development process, which is
|
||||
why Telodendria doesn't use GitHub, GitLab, or even SourceHut. Instead,
|
||||
the contribution workflow operates on submitting patch files to a public
|
||||
Matrix room, sort of like the OpenBSD project operates on patch files
|
||||
sent to a public mailing list.
|
||||
.sp
|
||||
If you're not used to manually creating and submitting patches instead of
|
||||
just opening a "pull request," you should be pleased to hear that submitting
|
||||
patches is fairly easy to do if you've got the CVS sources checked out. In
|
||||
fact, I find it easier than having to make a GitHub account, forking a
|
||||
project's repository, and then making a pull request for it. Once you have
|
||||
made your changes in your local copy of the code, and you've configured your
|
||||
environment properly as noted in the manual for
|
||||
.Xr td 8 ,
|
||||
just run the patch recipe:
|
||||
.Bd -literal -offset indent
|
||||
$ td patch
|
||||
.Ed
|
||||
.sp
|
||||
This will automatically generate a patch file for all your changes, and then
|
||||
open it in your preferred editor. You can also generate a patch file for only
|
||||
certain files and directories. To do that, set
|
||||
.Ev PATCHSET ,
|
||||
like this:
|
||||
.Bd -literal -offset indent
|
||||
# Only write a patch for README.txt and the files in docs/
|
||||
$ PATCHSET="README.txt docs/" td patch
|
||||
.Ed
|
||||
.sp
|
||||
As you'll notice, the top of the patch file should have some email-style
|
||||
headers that look like this:
|
||||
.Bd -literal -offset indent
|
||||
From: Jordan Bancino <@jordan:bancino.net>
|
||||
Date: Fri Jul 29 03:21:21 PM EDT 2022
|
||||
Subject: Document Patch Procedure
|
||||
.Ed
|
||||
.sp
|
||||
As much information should be filled out for you, such as the date. An
|
||||
attempt to fill out the From header was also made by
|
||||
.Xr td 8 ,
|
||||
but the information there can be modifed as necessary. Consult the manual
|
||||
for
|
||||
.Xr td 8
|
||||
for more details. The Subject should very briefly describe what the patch
|
||||
is about.
|
||||
.sp
|
||||
You'll also notice these lines in the patch:
|
||||
.Bd -literal -offset indent
|
||||
[ ] I have read the Telodendria Project development certificate of
|
||||
origin, and certify that I have permission to submit this patch
|
||||
under the conditions specified in it.
|
||||
.Ed
|
||||
.sp
|
||||
This is a checkbox that tells me whether or not you actually have the
|
||||
rights to submit your patch, and that once you submit your patch, your
|
||||
code is bound by the Telodendria project license, which you can and
|
||||
should view in
|
||||
.Xr telodendria 7 .
|
||||
The full text of the developer certificate of origin is as follows:
|
||||
.Bl -enum
|
||||
.It
|
||||
The contribution was created in whole or in part by me, and I have the right
|
||||
to submit it under the open source licenses of the Telodendria project; or
|
||||
.It
|
||||
The contribution is based upon a previous work that, to the best of my knowledge,
|
||||
is covered under an appropriate open source license and I have the right under
|
||||
that license to submit that work with modifications, whether created in whole
|
||||
or in part by me, under the Telodendria project license; or
|
||||
.It
|
||||
The contribution whas provided directly to me by some other person who certified
|
||||
(1), (2), or (3), and I have not modifed it.
|
||||
.It
|
||||
I understand and agree that this project and the contribution are made public
|
||||
and that a record of the contribution\(emincluding all personal information
|
||||
I submit with it\(emis maintained indefinitely and may be redistributed
|
||||
consistent with this project or the open source licenses involved.
|
||||
.El
|
||||
.sp
|
||||
If you agree to the above, fill in the square brackets with an 'x', and then after
|
||||
the headers, but before the checkbox, write a more thorough description of the
|
||||
patch and why it was created. Then, send the resulting patch file to the public
|
||||
Matrix room using
|
||||
.Xr send-patch 1 .
|
||||
.sp
|
||||
Try to keep your patches on topic\(emmake one patch file per feature or bug fix
|
||||
being implemented. It is okay if your patches depend on previous patches, just
|
||||
indicate that in the patch description. Note that it may take a while for
|
||||
patches to be committed, and some patches may not be comitted at all. In either
|
||||
case, all sent patches are queued from the Matrix room into the public patch
|
||||
directory, so they can be referenced easier in the future. If you want to
|
||||
reference a submitted patch in a Matrix message, email, or other digital medium,
|
||||
it might be a good idea to link to it in the public patch directory.
|
||||
.sp
|
||||
The public patch directory works as follows: when you send your patch to the
|
||||
Matrix room, it is downloaded by Telodendria Bot and placed in the
|
||||
.Pa ingress/
|
||||
directory, named as the message ID. Then, it is assigned a patch ID and
|
||||
copied to the
|
||||
.Pa p/
|
||||
directory as just "%d.patch", where "%d" is obviously the patch ID. This is
|
||||
a permanent link that will always reference your patch. Then, your patch will
|
||||
be symlinked into the
|
||||
.Pa queue/
|
||||
directory. I have a script that automatically ingresses patches and queues them
|
||||
for me, and I use this to review patches. If your patch is accepted, the queue
|
||||
symlink will be moved to
|
||||
.Pa accepted/
|
||||
and the submitted patch will be committed to the official CVS repository.
|
||||
If your patch is rejected for some reason, its symlink will be moved to the
|
||||
.Pa rejected/
|
||||
directory. Regardless of the state of your patch, it will always remain
|
||||
permalinked in the
|
||||
.Pa p/
|
||||
directory, and when it is accepted or rejected, Telodendria Bot will send a
|
||||
message to the Matrix room.
|
||||
.sp
|
||||
You're always welcome to inquire about rejected patches, and request that they
|
||||
be reviewed again, or you can use them as a starting point for future patches.
|
||||
.sp
|
||||
The public patch directory is located at
|
||||
.Lk https://telodendria.io/patches/
|
||||
.Sh CODE STYLE
|
||||
In general, these are the conventions used by the code base. This guide
|
||||
may be slightly outdated or subject to change, but it should be a good
|
||||
start. The source code itself is always the absolute source of truth, so
|
||||
as long as you make your code look like the code surrounding it, you should
|
||||
be fine.
|
||||
.Bl -bullet
|
||||
.It
|
||||
All function, enumeration, structure, and header names are CamelCase. This
|
||||
is preferred to snake_case because it is more compact.
|
||||
.It
|
||||
All variable names are lowerCamelCase. This is preferred to snake_case
|
||||
because it is more compact.
|
||||
.It
|
||||
enumerations and structures are always typedef-ed to their same name. The
|
||||
typedef should occur in the public API header, and the actual declaration
|
||||
should live in the implementation file.
|
||||
.It
|
||||
A feature of the code base lives in a single C source file that has a
|
||||
matching header. The header file should only export public symbols;
|
||||
everything else in the C source should be static.
|
||||
.It
|
||||
Except where absolutely necessary, global variables are forbidden to
|
||||
prevent problems with threads and whatnot. Every variable a function
|
||||
needs should be passed to it either through a structure, or as a
|
||||
separate argument.
|
||||
.It
|
||||
Anywhere curly braces are optional, there still must be curly braces. This
|
||||
makes it easier to add on to the code later, and just makes things a bit
|
||||
less ambiguous.
|
||||
.El
|
||||
.sp
|
||||
As far as actually formatting the code goes, such as where to put brackets,
|
||||
and whether or not to use tabs or spaces, use
|
||||
.Xr indent 1
|
||||
to take care of all of that. The root of the CVS repository has a
|
||||
.Pa .indent.pro
|
||||
that should automatically be loaded by
|
||||
.Xr indent 1
|
||||
to set the correct rules. If you don't have a working
|
||||
.Xr indent 1 ,
|
||||
then just indicate in your patch that I should run my
|
||||
.Xr indent 1
|
||||
on the code after applying it. Although in reality, I'll likely
|
||||
run my own
|
||||
.Xr indent 1
|
||||
on the code anyway, just to make sure the spacing is consistent, if nothing
|
||||
else.
|
||||
.Pp
|
||||
This project places a strong emphasis on documentation. Well-documented
|
||||
code is fundamental to a successful project, so when you are writing code,
|
||||
please also make sure it is documented appropriately.
|
||||
.Bl -bullet
|
||||
.It
|
||||
If you are adding a header, make sure you add a man page that documents
|
||||
all the functions in the header.
|
||||
.It
|
||||
If you're adding a function, make sure you add documentation for it
|
||||
to the appropriate man page for the header that your function resides
|
||||
in. Do note that you do not have to document static functions, only
|
||||
public API functions.
|
||||
.El
|
||||
.Pp
|
||||
If your patch does not also include proper documentation, it will
|
||||
likely be rejected.
|
|
@ -1,221 +0,0 @@
|
|||
.Dd $Mdocdate: February 15 2023 $
|
||||
.Dt TELODENDRIA 7
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Telodendria
|
||||
.Nd Start here. This page contains the project introduction, and provides information about it, such as its status, how to contact the developers, and what the source code license is.
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is an open source Matrix homeserver written entirely from scratch in ANSI C and
|
||||
designed to be lightweight and simple, yet functional.
|
||||
.Pp
|
||||
.Nm
|
||||
differentiates itself from the other Matrix homeserver
|
||||
implementations because it:
|
||||
.Bl -bullet
|
||||
.It
|
||||
Is written in C, a stable, low-level programming language with a
|
||||
long history, low build and runtime overhead, and wide compatibility.
|
||||
.It
|
||||
Is written with minimalism as a primary design goal. Whenever possible
|
||||
and practical, no third-party libraries are pulled into the source code.
|
||||
Everything
|
||||
.Nm
|
||||
needs is custom written. As a result,
|
||||
.Nm
|
||||
depends only on a standard C compiler and a POSIX C library to be built,
|
||||
both of which should come with any good Unix-style operating system already,
|
||||
which means you shouldn't have to install anything extra.
|
||||
.It
|
||||
Uses a flat-file directory structure to store data instead of a database.
|
||||
This has a number of advantages:
|
||||
.Bl -bullet
|
||||
.It
|
||||
It makes setup and maintenance much easier.
|
||||
.It
|
||||
It allows
|
||||
.Nm
|
||||
to run on systems with fewer resources.
|
||||
.El
|
||||
.It
|
||||
Is packaged as a single small, statically-linked and highly-optimized binary
|
||||
that can be run just about anywhere. It is designed to be extremely easy to
|
||||
set up and consume as few resources as possible.
|
||||
.It
|
||||
Is permissively licensed.
|
||||
.Nm
|
||||
is licensed under a modified MIT license, which imposes very few restrictions
|
||||
on what you can do with it.
|
||||
.El
|
||||
.Pp
|
||||
.Nm
|
||||
is on Matrix! Check out the official Matrix rooms:
|
||||
.Pp
|
||||
.TS
|
||||
box tab(;);
|
||||
ll.
|
||||
#telodendria:bancino.net;The public "space" room.
|
||||
#telodendria-releases:bancino.net;Get notified of new releases.
|
||||
#telodendria-general:bancino.net;General discussion and support.
|
||||
#telodendria-newsletter:bancino.net;Periodic status updates.
|
||||
#telodendria-issues:bancino.net;Report bugs and issues.
|
||||
#telodendria-patches:bancino.net;Submit code patches to the project.
|
||||
#telodendria-ports:bancino.net;Discussion about porting and packaging.
|
||||
.TE
|
||||
.Pp
|
||||
.Nm
|
||||
is designed to be fairly straightforward, but that doesn't mean there
|
||||
won't be hiccups along the way. If you're struggling to get
|
||||
.Nm
|
||||
up and running, you're more than welcome to reach out for support. Just
|
||||
hop into the appropriate Matrix rooms and talk to me!
|
||||
.Sh RATIONALE
|
||||
I want a lightweight Matrix homeserver designed specifically for OpenBSD,
|
||||
and other Unix-like operating systems. I want a homeserver that can be
|
||||
developed and compiled with built-in tools. I want it to function entirely
|
||||
on a base OS install without having to install any packages whatsoever. I've
|
||||
found that as far as these goals are concerned, the existing homeserver
|
||||
implementations fall short. This project aims to prove that Matrix homeservers
|
||||
can be lightweight and written in such a way that very few, if any, third-party
|
||||
libraries need to be pulled in.
|
||||
.Pp
|
||||
I also want to learn how Matrix works, and I want to understand the code I'm
|
||||
running on my server, which is why I'm writing every component from scratch,
|
||||
even the HTTP server and router.
|
||||
.Pp
|
||||
The advantage of using a flat-file database instead of a real database is that
|
||||
your data remains in a format that is incredibly easy to digest. Backups are
|
||||
incredibly easy as well\(emjust
|
||||
.Xr tar 1
|
||||
up the data directory and you're good to go.
|
||||
.Sh PROJECT GOALS
|
||||
The goals of this project are generally divided into user goals and developer
|
||||
goals, depending on who they impact the most. This isn't an exaustive list
|
||||
of the project's goals, but it is a list of things that I want to prioritize,
|
||||
because other server implementations lack them.
|
||||
.Pp
|
||||
The user goals are as follows:
|
||||
.Bl -bullet
|
||||
.It
|
||||
To implement the latest Matrix specification as fully and completely as possible.
|
||||
All features defined by the specification should eventually be present in
|
||||
.Nm .
|
||||
.It
|
||||
To be as privacy-friendly as possible.
|
||||
.Nm
|
||||
should not store any information it does not absolutely need to function as a
|
||||
Matrix homeserver. While
|
||||
.Nm
|
||||
strives to be feature-complete, it should not implement anything not explicitly
|
||||
defined in the Matrix specification.
|
||||
.It
|
||||
To be a production-ready Matrix server capable of working in constrained environments,
|
||||
such as embedded devices, cheap VPSs, or a peer-to-peer device.
|
||||
.Nm
|
||||
should also work on traditional setups as well, and be able to handle a decent
|
||||
amount of users. It should work well for personal Matrix homeservers that also
|
||||
host a few friends and/or family members.
|
||||
.It
|
||||
To be easier to get up and running, and yet also be more configurable than other
|
||||
Matrix homeserver implementations. The configuration file should be flexible,
|
||||
well-documented, and easy to understand and modify. An intuitive command-line
|
||||
tool for administrative tasks should also be available.
|
||||
.El
|
||||
.Pp
|
||||
The developer goals are as follows:
|
||||
.Bl -bullet
|
||||
.It
|
||||
To have as few build and runtime dependencies as possible. It should be possible
|
||||
to compile and run
|
||||
.Nm
|
||||
on any POSIX operating system right out of the box.
|
||||
.Nm
|
||||
should be fully statically-linked, so it can run under a
|
||||
.Xr chroot 3 .
|
||||
Furthermore, it should be possible to read all the documentation offline, and
|
||||
without any overly complicated tools. You'll notice that this documentation is
|
||||
a collection of
|
||||
.Xr man 1
|
||||
pages, not HTML or Markdown, to remove the dependency on a browser or Markdown
|
||||
parser. Any Unix-like system has a manual page viewer, which makes the
|
||||
documentation more accessible, even on remote systems that have no graphical
|
||||
interface to read the documentation. This ensures that you can read the
|
||||
documentation for the installed version of
|
||||
.Nm
|
||||
without having to go online.
|
||||
.It
|
||||
To have a simple yet elegant workflow, and not depend on any large or complex
|
||||
development tools such as code forges. The entire development workflow should
|
||||
be able to be successfully and efficiently completed on a base OpenBSD install.
|
||||
Of course, you don't have to use OpenBSD for development, but the point is that
|
||||
the workflow should require fairly standardized and simple tools.
|
||||
.It
|
||||
To write clean, elegant, well-tested, and well-documented code. The goal is to build
|
||||
a Matrix homeserver from the ground up, not just because I don't like the way existing
|
||||
homeservers are implemented, but also because I want to learn how Matrix works,
|
||||
make it more accessible, and potentially teach others a little bit along the way.
|
||||
.It
|
||||
To foster a welcoming community. Many big communities such as Linux and OpenBSD
|
||||
have hostile leaders.
|
||||
.Nm
|
||||
shouldn't have a hostile leader. I want to be as understanding as I can, and talk
|
||||
through issues politely and in a civil manner. If I'm failing in this way, don't
|
||||
be afraid to call me out!
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr telodendria 8 ,
|
||||
.Xr telodendria.conf 5 ,
|
||||
.Xr td 8
|
||||
.Sh STANDARDS
|
||||
The installed version of
|
||||
.Nm
|
||||
conforms to the latest Matrix specification available at the time
|
||||
of its release.
|
||||
.Sh HISTORY
|
||||
.Pp
|
||||
.Nm
|
||||
was started in early July of 2022. For a change log of this
|
||||
project, see
|
||||
.Xr telodendria-changelog 7 .
|
||||
.Sh AUTHORS
|
||||
.Nm
|
||||
was started by and is almost exclusively developed by
|
||||
Jordan Bancino <@jordan:bancino.net>. Contributions to the code,
|
||||
website, documentation, or other components of this project have
|
||||
been made by various open source developers.
|
||||
.Sh LICENSE
|
||||
All of the code and documentation for
|
||||
.Nm
|
||||
is licensed under a modified MIT license. Please consult the
|
||||
.Pa LICENSE.txt
|
||||
file for the actual license text. The
|
||||
.Nm
|
||||
license text differs from the MIT license in the following ways:
|
||||
.Bl -bullet
|
||||
.It
|
||||
Where the MIT license states that the copyright notice and permission
|
||||
notice shall be included in all copies or
|
||||
.Pa substantial
|
||||
portions of the software, the
|
||||
.Nm
|
||||
license requires the copyright notice and permission notice be included
|
||||
with
|
||||
.Pa all
|
||||
portions, regardless of the size, of the software by omitting the word
|
||||
.Pa substantial .
|
||||
.El
|
||||
.Pp
|
||||
The
|
||||
.Nm
|
||||
logo in all forms, including the ASCII representation, belongs solely
|
||||
to the
|
||||
.Nm
|
||||
project. It must only be used to represent the official
|
||||
.Nm
|
||||
project, and may only appear in official
|
||||
.Nm
|
||||
media. If
|
||||
.Nm
|
||||
is forked, the logo must be removed completely from the project, and
|
||||
optionally replaced with a different one. The logo may not be modified
|
||||
in any way or for any purpose.
|
|
@ -1,63 +0,0 @@
|
|||
.Dd $Mdocdate: November 27 2022 $
|
||||
.Dt SEND-PATCH 8
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm send-patch
|
||||
.Nd Submit a patch file to the Telodendria Patches Matrix room
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op patch
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a simple shell script for submitting patch files to Telodendria's patch
|
||||
room for review. Do note that it depends on
|
||||
.Xr jq 1
|
||||
and
|
||||
.Xr curl 1 ,
|
||||
and so may not work out of the box on some systems. However, these tools are
|
||||
readily available for most systems. Please consult your package manager's
|
||||
manual for installing packages.
|
||||
.Pp
|
||||
.Nm
|
||||
takes a single argument, a patch file. It also reads a number of environment
|
||||
variables, as described in the following section. This script is designed to be
|
||||
simple; it only pushes files into a hard-coded Matrix room. Thus, as far as
|
||||
Matrix clients go, this one is a rather minimal one, and that is by design.
|
||||
.Pp
|
||||
This script exists so that users who are working on a machine that doesn't have
|
||||
a Matrix client installed can still submit work to the Telodendria project. The
|
||||
goal is to make development as accessible as possible.
|
||||
.Pp
|
||||
This script only supports password login, so if your homeserver does not
|
||||
support password login, it will not work.
|
||||
.Sh ENVIRONMENT
|
||||
.Pp
|
||||
.Nm
|
||||
utilizes the following environment variables:
|
||||
.Bl -tag -width Ds
|
||||
.It Ev MXID
|
||||
Your Matrix ID in the standard format. This is used to connect to your
|
||||
homeserver to send the message.
|
||||
.It Ev MXPW
|
||||
Your Matrix account's password. If not set, you will be prompted for your
|
||||
password by the script, unless
|
||||
.Ev ACCESS_TOKEN
|
||||
is set.
|
||||
.It Ev ACCESS_TOKEN
|
||||
If you already have an access token for your account, such as one from an
|
||||
existing session, then you can set this environment variable to bypass the
|
||||
password authentication flow.
|
||||
.El
|
||||
.Sh FILES
|
||||
.Pp
|
||||
.Nm
|
||||
does utilize the
|
||||
.Pa .env
|
||||
file, just like
|
||||
.Xr td 8 .
|
||||
Consult that page for the specifics of the
|
||||
.Pa .env
|
||||
file.
|
||||
.Sh SEE ALSO
|
||||
.Xr td 8
|
||||
|
251
man/man8/td.8
251
man/man8/td.8
|
@ -1,251 +0,0 @@
|
|||
.Dd $Mdocdate: February 15 2023 $
|
||||
.Dt TD 8
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm td
|
||||
.Nd Telodendria build script and patch generation instructions.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op recipe
|
||||
.Sh DESCRIPTION
|
||||
Telodendria uses a custom build script called
|
||||
.Nm .
|
||||
The
|
||||
.Nm
|
||||
script is not only a build script, however. It does all kinds of
|
||||
cool things like format the source code, and generate patch files.
|
||||
.Nm
|
||||
is the only supported way to develop Telodendria.
|
||||
.sp
|
||||
I opted to write a custom build script instead of just writing a
|
||||
.Pa Makefile
|
||||
because I felt that there is really no way to make a truly portable
|
||||
.Pa Makefile
|
||||
that could do everything I wanted, with the flexibility I wanted. I
|
||||
was doing a lot of research on the differences between the GNU and BSD
|
||||
versions, and I felt it just wasn't worth trying to reconsile the two
|
||||
when I could write a small and relatively robust POSIX shell script that
|
||||
would run on both GNU and BSD systems without any problems. I also
|
||||
think that shell scripts are a lot easier to read than complex
|
||||
.Pa Makefiles.
|
||||
They're easier to follow because they're not so cryptic.
|
||||
.sp
|
||||
The
|
||||
.Nm
|
||||
script is fairly intuitive. It operates somewhat like
|
||||
.Xr make 1
|
||||
in that it has recipes that you specify on the command line. To start
|
||||
using it, just run the following command in the Telodendria source
|
||||
directory:
|
||||
.Bd -literal -offset indent
|
||||
$ . tools/env.sh
|
||||
.Ed
|
||||
.sp
|
||||
.Sy Note:
|
||||
You will have to run the above command every time you start a new
|
||||
terminal session, as nothing is persisted to your system. I believe in
|
||||
non-invasive, fully self-contained tooling, so it is up to you to hook the
|
||||
Telodendria tools into your environment as you see fit if you want them to
|
||||
persist.
|
||||
.sp
|
||||
If you're going to be submitting patches, you should also configure a
|
||||
.Pa .env
|
||||
file in the project directory root, which
|
||||
.Nm
|
||||
will include automatically for you. See
|
||||
.Em FILES
|
||||
and
|
||||
.Em ENVIRONMENT .
|
||||
.sp
|
||||
Telodendria is designed to be light enough that it can be built from source
|
||||
on just about any operating system. It only requires an ANSI C compiler and a
|
||||
standard POSIX environment. To build the Telodendria binary, run
|
||||
.Nm
|
||||
with no arguments, or with the
|
||||
.Pa build
|
||||
recipe. This will produce
|
||||
.Pa build/telodendria ,
|
||||
which you can then install to your system and run as a daemon.
|
||||
.sp
|
||||
A complete list of recipes is below. Note that you can run multiple recipes
|
||||
with a single invocation of
|
||||
.Nm ,
|
||||
but recipes are run unconditionally; that is, even if a recipe fails, all the
|
||||
following recipes are still executed.
|
||||
.Bl -tag
|
||||
.It build
|
||||
Build the source code and generate the output binary. This is the default recipe,
|
||||
which means it runs if no other recipes are specified. This recipe is incremental;
|
||||
it only rebuilds sources that have been modifed since the last build, making
|
||||
subsequent builds faster.
|
||||
.It run
|
||||
Run the build binary with the development configuration in the
|
||||
.Pa contrib/
|
||||
directory. This recipe is used for quick testing during development. It is
|
||||
.Sy not
|
||||
the recommended way to run Telodendria in a production environment; it should only
|
||||
be used for development.
|
||||
.It clean
|
||||
Remove the
|
||||
.Pa build/
|
||||
directory and any ephemeral files in the source tree, such as
|
||||
.Pa .orig
|
||||
files. The build recipe does not place anything outside of
|
||||
.Pa build/ ,
|
||||
so you can usually just delete that directory and get the same effect.
|
||||
.It install
|
||||
Install Telodendria to the system. This recipe assumes you're running a
|
||||
Unix-like system.
|
||||
.It uninstall
|
||||
Uninstall Telodendria from the system if it was installed with the install
|
||||
recipe.
|
||||
.It format
|
||||
Make sure the source code copyright headers are up to date, and format the code
|
||||
using the system's
|
||||
.Xr indent 1 .
|
||||
This should be run before generating patch files, to ensure that the code follows
|
||||
the project conventions. Note that the provided
|
||||
.Pa .indent.pro
|
||||
assumes an OpenBSD indent, which may cause the GNU implementation to choke. In
|
||||
that case, don't send patch files with totally different formatting; just submit
|
||||
the patch as-is and they will get formatted before committing.
|
||||
.It test
|
||||
Run all of the unit tests and report the results. It is highly recommended to
|
||||
ensure that all the tests pass before submitting a patch, because patches that
|
||||
break the tests are likely to be rejected.
|
||||
.It site
|
||||
Deploy the Telodendria website by generating HTML files for the documentation,
|
||||
and copying them along with the front page to the specified web root. This is
|
||||
used to deploy the official website, but it could be used to deploy a local
|
||||
development site as necessary. See
|
||||
.Em ENVIRONMENT .
|
||||
.It release
|
||||
Generate a release tarball, checksum and sign it, and push it to the web root.
|
||||
See the relevant environment variables below.
|
||||
.It patch
|
||||
Generate a formatted patch file. The Telodendria project isn't super picky about
|
||||
how patches look as long as they apply cleanly, but this recipe generates patches
|
||||
in the format we like them, and is therefore recommended. It makes patches easy
|
||||
to read. This recipe will use your configured editor to open your formatted patch
|
||||
so you can review and edit it as necessary before sending it off.
|
||||
.It diff
|
||||
Generate a temporary preview patch that is opened in the system pager. This can
|
||||
be used for quickly quickly previewing your changes and the patch file you'll
|
||||
be creating.
|
||||
.El
|
||||
.sp
|
||||
.Sh ENVIRONMENT
|
||||
Any of the following environment variables are used in the build recipes.
|
||||
They can all be specified in your shell when invoking
|
||||
.Nm ,
|
||||
or they can be placed in a
|
||||
.Pa .env
|
||||
file. For most of these variables, if you would like to append or prepend
|
||||
to the default values, do so in the
|
||||
.Pa .env
|
||||
file, which is sourced after the defaults are set, allowing you to reference
|
||||
the default values in your new value.
|
||||
.Bl -tag
|
||||
.It Ev CC
|
||||
The C compiler to use. This defaults to
|
||||
.Pa cc,
|
||||
which is usually a symlink to your system's preferred compiler. If for some
|
||||
reason you want to use a diferent compiler, do so with this environment
|
||||
variable.
|
||||
.It Ev PREFIX
|
||||
When installing/uninstalling Telodendria, the systeme prefix to use. This
|
||||
defaults to
|
||||
.Pa /usr/local .
|
||||
.It Ev CFLAGS
|
||||
The compiler flags used to generate object files.
|
||||
.Nm
|
||||
comes with reasonable defaults that shouldn't need to be changed in most
|
||||
scenarios, but if you do need to change the compiler flags, you can do
|
||||
so with this environment variable.
|
||||
.It Ev LDFLAGS
|
||||
The compiler flags used to link the object files to create an output
|
||||
binary.
|
||||
.Nm
|
||||
comes with reasonable defaults that shouldn't need to be changed in most
|
||||
scenarios, but if you need to change the linker flags, you do so with this
|
||||
environment variable.
|
||||
.It Ev PROG
|
||||
The name of the output binary. This defaults to
|
||||
.Pa build/telodendria.
|
||||
.It Ev DEFINES
|
||||
Global preprocessor definitions to append to
|
||||
.Ev CFLAGS.
|
||||
This is just kept separate to keep things organized.
|
||||
.It Ev INCLUDES
|
||||
Header directories to make available. This is appended to
|
||||
.Ev CFLAGS,
|
||||
it is just kept separate to keep things organized.
|
||||
.It Ev DEBUG
|
||||
If set to "1", append some debug flags to
|
||||
.Ev CFLAGS
|
||||
and whipe out any
|
||||
.Ev LDFLAGS
|
||||
that awould cause the output binary to be optimized in any way. This also
|
||||
depends "-debug" to
|
||||
.Ev PROG .
|
||||
.It Ev TELODENDRIA_VERSION
|
||||
This variable does make its way into the output binary, but it is primarily
|
||||
used for generating and publishing releases. This variable affects the
|
||||
.Sy release
|
||||
recipe.
|
||||
.It Ev TELODENDRIA_PUB
|
||||
The web root where the Telodendria website lives. This is where the site
|
||||
is pushed to, and where generated releases go.
|
||||
.It Ev PATCHSET
|
||||
This variable restricts the files that
|
||||
.Nm
|
||||
operates on when generating patches or diffs. If you only want to generate
|
||||
a diff or patch for a certain file, directory, or collection of files and
|
||||
directories, set this variable to those files and directories, separated
|
||||
by a space. You can mix files and directories as necessary.
|
||||
.It Ev MXID
|
||||
Your Matrix ID in standard format. This is used when generating patches,
|
||||
so that you can be assigned credit for your patches, as well as be contacted
|
||||
about your patches.
|
||||
.Nm
|
||||
will automatically deduce this from your system, but it will most
|
||||
likely get it wrong. Please make sure you are reachable at this ID.
|
||||
.It Ev DISPLAY_NAME
|
||||
The display name you want to appear on your patches. This should probably
|
||||
match your Matrix display name, although it doesn't necessarily have to.
|
||||
.Nm
|
||||
will deduce this from your system, and if you set it up properly, you may
|
||||
not even have to set this variable. If
|
||||
.Nm
|
||||
gets it wrong, this allows you to override your display name.
|
||||
.It Ev EDITOR
|
||||
Your preferred editor for writing patch file descriptions. This can be a
|
||||
GUI or terminal editor. If unset, this defaults to the system's
|
||||
.Xr vi 1
|
||||
editor.
|
||||
.It Ev PAGER
|
||||
Your preferred pager for previewing patches. If left unset, this defaults
|
||||
to
|
||||
.Xr less 1 .
|
||||
.Sh FILES
|
||||
.Bl -tag
|
||||
.It Pa .env
|
||||
An environment file that contains lines in the form of
|
||||
.Pa VARIABLE=value
|
||||
with environment variables to set in the
|
||||
.Nm
|
||||
script. See
|
||||
.Em ENVIRONMENT .
|
||||
Note that
|
||||
.Nm
|
||||
simply sources this file, which means that any shell code in it will be
|
||||
executed each time
|
||||
.Nm
|
||||
is invoked.
|
||||
.Sh EXIT STATUS
|
||||
.Sh HISTORY
|
||||
.Sh BUGS
|
||||
.Nm
|
||||
unconditionally exits with code 0, even if errors occurred. Furthermore,
|
||||
recipes are run unconditionally, regardless of whether or not any recipes
|
||||
before have failed.
|
|
@ -1,57 +0,0 @@
|
|||
.Dd $Mdocdate: February 15 2023 $
|
||||
.Dt TELODENDRIA 8
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm telodendria
|
||||
.Nd Daemon command line manual for Telodendria administrators.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl nVv
|
||||
.Op Fl f Ar file
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a Matrix homeserver written entirely from scratch in ANSI C.
|
||||
It is designed to be lightweight and simple, yet functional.
|
||||
.sp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl f Ar file
|
||||
Specify an alternate configuration file. The default is
|
||||
.Pa /etc/telodendria.conf .
|
||||
.It Fl n
|
||||
Configtest mode. Only check the configuration file for validity.
|
||||
.It Fl V
|
||||
Only print the version information header.
|
||||
.It Fl v
|
||||
Verbose mode. This overrides the configuration file and sets the
|
||||
log level to
|
||||
.Em LOG_DEBUG
|
||||
.El
|
||||
.Sh ENVIRONMENT
|
||||
.Nm
|
||||
does not read any environment variables. All configuration should
|
||||
be done via the configuration file.
|
||||
.Sh FILES
|
||||
Just the configuration file and the data directory; see
|
||||
.Xr telodendria.conf 5
|
||||
for more details.
|
||||
.El
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
exits with a non-0 exit code if the configuration file is invalid, or
|
||||
one or more required paths is inaccessible.
|
||||
.Nm
|
||||
will print an error to the log and then terminate abnormally.
|
||||
.Pp
|
||||
.Nm
|
||||
exits with a code of 0 if the configuration file is valid, all
|
||||
paths and files required are accessible, and the HTTP listener starts
|
||||
as intended. If
|
||||
.Nm
|
||||
is sent a signal that it catches after it begins servicing requests, it
|
||||
will still exit with a code of 0 after it safely shuts down, because
|
||||
the bootstrap process completed successfully, and by all accounts,
|
||||
it ran normally and exitted normally.
|
||||
.Sh SEE ALSO
|
||||
.Xr telodendria 7 ,
|
||||
.Xr telodendria.conf 5
|
|
@ -1,448 +0,0 @@
|
|||
.Dd $Mdocdate: March 4 2023 $
|
||||
.Dt ADMIN 7
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Admin
|
||||
.Nd Suggestion on a Telodendria Admin API
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
This is a suggestion of how the admin API should work; it is subject
|
||||
to heavy changes.
|
||||
.Pp
|
||||
It also acts as a suggestion on how suggestions should be written. It
|
||||
might not be accepted, or the format might be changed heavily.
|
||||
.Ss Admin Users
|
||||
.Pp
|
||||
Like in Synapse, there should be a field dictating if the user is an
|
||||
admin, and unlike Synapse, there should be privilege levels.
|
||||
.Pp
|
||||
In the
|
||||
.Pa users/[user].json
|
||||
file, there should be a field called
|
||||
.Dv privileges
|
||||
which contains a list of strings.
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"devices": {
|
||||
"foobar": { ... }
|
||||
},
|
||||
"salt": "salt goes here",
|
||||
"deactivated": false,
|
||||
"createdOn": 1700000000000,
|
||||
"password": "hash goes here",
|
||||
"privileges": [
|
||||
"DEACTIVATE",
|
||||
"ISSUE_TOKENS"
|
||||
]
|
||||
}
|
||||
.Ed
|
||||
.Ss Privileges list
|
||||
.Pp
|
||||
Here are all of the admin privileges a user can have:
|
||||
.Bl -tag -width Ds
|
||||
.It Dv DEACTIVATE
|
||||
This allows an user to deactivate any users using
|
||||
the
|
||||
.Em DELETE
|
||||
/_telodendria/admin/disable/[localpart]
|
||||
endpoint.
|
||||
.It Dv ISSUE_TOKENS
|
||||
This allows users to create, modify and delete registration
|
||||
tokens.
|
||||
.It Dv ALL
|
||||
Users with this privilege can use
|
||||
.Em any
|
||||
admin endpoint(and some others).
|
||||
.Em THIS PRIVILEGE SHOULD ONLY BE USED WITH TRUSTED USERS.
|
||||
.Sh API ENDPOINTS
|
||||
.Ss Sy GET No /_telodendria/admin/privileges
|
||||
.Pp
|
||||
Get the priviledges of the user that owns the provided access token.
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l.
|
||||
Requires Token;Rate Limited
|
||||
Yes;Yes
|
||||
.TE
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l.
|
||||
Error Response;Description
|
||||
200;Privileges successfully returned.
|
||||
.TE
|
||||
.Pp
|
||||
200 Response JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
privileges;list;The same data structure described in the database.
|
||||
.TE
|
||||
.Pp
|
||||
200 Response Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"privileges": [
|
||||
"DEACTIVATE",
|
||||
"REMOVE_DEVICES"
|
||||
]
|
||||
}
|
||||
.Ed
|
||||
.Ss Sy DELETE No /_telodendria/admin/deactivate/[localpart]
|
||||
.Pp
|
||||
Deactivates a local user, optionally with a reason.
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Requires Token;Rate Limited;Permissions
|
||||
Yes;Yes;DEACTIVATE
|
||||
.TE
|
||||
.Pp
|
||||
Request JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l l.
|
||||
Field;Type;Description;Required
|
||||
reason;string;A reason why the user was deactivated;No
|
||||
.TE
|
||||
.Pp
|
||||
Request Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"reason": "Being mean in a lot of rooms."
|
||||
}
|
||||
.Ed
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l.
|
||||
Error Response;Description
|
||||
200;User was successfully deactivated.
|
||||
403;User does not have the DEACTIVATE permission.
|
||||
.TE
|
||||
.Pp
|
||||
200 Response JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
user;localpart;The deactivated user's localpart
|
||||
reason;string;T{
|
||||
The reason why the user was deactivated.
|
||||
Defaults to: ``Deactivated by admin''
|
||||
T}
|
||||
banned_by;localpart;T{
|
||||
The localpart of the admin who deactivated the user.
|
||||
T}
|
||||
.TE
|
||||
.Pp
|
||||
403 Response JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
errcode;string;Set to ``M_FORBIDDEN''
|
||||
error;string;Human-readable explanation of the privilege issue.
|
||||
.TE
|
||||
.Pp
|
||||
200 Response Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"user": "evan",
|
||||
"reason": "Being mean in a lot of rooms",
|
||||
"banned_by": "alice"
|
||||
}
|
||||
.Ed
|
||||
.Pp
|
||||
403 Response Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Forbidden access. Bad permissions or not authenticated."
|
||||
}
|
||||
.Ed
|
||||
.Ss Sy PUT No /_telodendria/admin/deactivate/[localpart]
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Requires Token;Rate Limited;Permissions
|
||||
Yes;Yes;DEACTIVATE
|
||||
.TE
|
||||
.Pp
|
||||
.Em Description:
|
||||
Reactivates a local user.
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l.
|
||||
Error Response;Description
|
||||
204;User was successfully reactivated.
|
||||
403;User does not have the DEACTIVATE permission.
|
||||
.TE
|
||||
.Pp
|
||||
403 Response JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
errcode;string;Set to ``M_FORBIDDEN''
|
||||
error;string;Human-readable explanation of the privilege issue.
|
||||
.TE
|
||||
.Pp
|
||||
403 Response Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Forbidden access. Bad permissions or not authenticated."
|
||||
}
|
||||
.Ed
|
||||
.Ss Sy GET No /_telodendria/admin/tokens
|
||||
.Pp
|
||||
Gets a list of
|
||||
.Em all
|
||||
tokens present, and additional information.
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Requires Token;Rate Limited;Permissions
|
||||
Yes;Yes;ISSUE_TOKENS
|
||||
.TE
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l.
|
||||
Error Response;Description
|
||||
200;Token list was successfully retrieved.
|
||||
403;User does not have the ISSUE_TOKENS permission.
|
||||
.TE
|
||||
.Pp
|
||||
200 Response JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
tokens;list[TokenInfo];A list of tokens and other information.
|
||||
.TE
|
||||
.Pp
|
||||
.Dv TokenInfo
|
||||
JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
name;string;The token's name.
|
||||
created_by;localpart;The user who has created token.
|
||||
created_on;timestamp;The creation date of the token.
|
||||
expires_on;timestamp;T{
|
||||
The token's expiration date, or 0 if it does not
|
||||
expire.
|
||||
T}
|
||||
used;integer;The number of times the token was used.
|
||||
uses;integer;T{
|
||||
The number of uses remaining for the token, or -1 if
|
||||
there are an unlimited number of uses remaining.
|
||||
T}
|
||||
.TE
|
||||
.Pp
|
||||
403 Response JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
errcode;string;Set to ``M_FORBIDDEN''
|
||||
error;string;Human-readable explanation of the privilege issue.
|
||||
.TE
|
||||
.Pp
|
||||
200 Response Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"tokens": [
|
||||
{
|
||||
"name": "forbob",
|
||||
"created_by": "alice",
|
||||
"created_on": 1234567890123,
|
||||
"expires_on": 2147483647000,
|
||||
"used": 1,
|
||||
"uses": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
.Ed
|
||||
.Pp
|
||||
403 Response JSON Format:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Forbidden access. Bad permissions or not authenticated."
|
||||
}
|
||||
.Ed
|
||||
.Ss Sy GET No /_telodendria/admin/tokens/[token]
|
||||
.Pp
|
||||
Returns information about a specific registration token.
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Requires Token;Rate Limited;Permissions
|
||||
Yes;Yes;ISSUE_TOKENS
|
||||
.TE
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l.
|
||||
Error Response;Description
|
||||
200;Token information successfully retrieved.
|
||||
403;User does not have the ISSUE_TOKENS permission.
|
||||
404;The specified token does not exist.
|
||||
.TE
|
||||
.Pp
|
||||
200 Response JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
name;string;The token's name.
|
||||
created_by;localpart;The user who created the token.
|
||||
created_on;timestamp;The creation date of the token.
|
||||
expires_on;timestamp;The token's expiration date, if provided.
|
||||
used;integer;The number of times the token was used.
|
||||
uses;integer;T{
|
||||
The number of remaining uses for the token, if set.
|
||||
Otherwise, there are unlimited uses remaining.
|
||||
T}
|
||||
.TE
|
||||
.Pp
|
||||
200 Response Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"name": "forbob",
|
||||
"created_by": "alice",
|
||||
"created_on": 1234567890123,
|
||||
"used": 1,
|
||||
"uses": 3
|
||||
}
|
||||
.Ed
|
||||
.Ss Sy POST No /_telodendria/admin/tokens
|
||||
.Pp
|
||||
Adds a registration token, and setup expiry date and max uses.
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Requires Token;Rate Limited;Permissions
|
||||
Yes;Yes;ISSUE_TOKENS
|
||||
.TE
|
||||
.Pp
|
||||
Request JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l l.
|
||||
Field;Type;Description;Required
|
||||
lifetime;timestamp;T{
|
||||
How long this token should be good for
|
||||
T};NO
|
||||
max_uses;integer;T{
|
||||
The maximum number of uses for this token
|
||||
T};NO
|
||||
name;string;T{
|
||||
A name for the token. If none is provided, then a name
|
||||
is randomly generated.
|
||||
T};NO
|
||||
.TE
|
||||
.Pp
|
||||
Request Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"name": "OnlyClownsM7iAhUJD",
|
||||
"expires": 2147484637000,
|
||||
"max_uses": 5
|
||||
}
|
||||
.Ed
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l.
|
||||
Error Response;Description
|
||||
200;Token was successfully created.
|
||||
403;User does not have the ISSUE_TOKENS permission.
|
||||
.TE
|
||||
.Pp
|
||||
200 Response JSON Format:
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Field;Type;Description
|
||||
name;string;The token's name.
|
||||
created_by;localpart;The user who created the token.
|
||||
created_on;timestamp;The creation date of the token.
|
||||
expires_on;timestamp;T{
|
||||
The token's expiration date, if set. If not set, the
|
||||
token never expires.
|
||||
T}
|
||||
used;integer;The number of times the token was used.
|
||||
uses;integer;T{
|
||||
The number of uses remaining for the token, if set. If
|
||||
not set, the token has an unlimited number of uses.
|
||||
T}
|
||||
.TE
|
||||
.Pp
|
||||
200 Response Example:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"name": "OnlyClownsM7iAhUJD",
|
||||
"created_by": "donald",
|
||||
"created_on": 1234567890123,
|
||||
"expires_on": 2147484637000,
|
||||
"used": 0,
|
||||
"uses": 5
|
||||
}
|
||||
.Ed
|
||||
.Pp
|
||||
403 Response JSON Format:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Forbidden access. Bad permissions or not authenticated."
|
||||
}
|
||||
.Ed
|
||||
.Ss Sy DELETE No /_telodendria/admin/tokens/[tokenname]
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l l.
|
||||
Requires Token;Rate Limited;Permissions
|
||||
Yes;Yes;ISSUE_TOKENS
|
||||
.TE
|
||||
.Pp
|
||||
.Em Description:
|
||||
Deletes an existing registration token.
|
||||
.Pp
|
||||
.TS
|
||||
tab(;) allbox center;
|
||||
l l.
|
||||
Error Response;Description
|
||||
204;Token was successfully deleted.
|
||||
403;User does not have the ISSUE_TOKENS permission.
|
||||
.TE
|
||||
.Pp
|
||||
403 Response JSON Format:
|
||||
.Bd -literal -offset indent
|
||||
{
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Forbidden access. Bad permissions or not authenticated."
|
||||
}
|
||||
.Ed
|
135
site/index.html
135
site/index.html
|
@ -1,135 +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}
|
||||
<p>
|
||||
<details>
|
||||
<summary>Developer Documentation:</summary>
|
||||
</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
|
313
src/Array.c
313
src/Array.c
|
@ -1,313 +0,0 @@
|
|||
/*
|
||||
* 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 limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Array.h>
|
||||
|
||||
#ifndef ARRAY_BLOCK
|
||||
#define ARRAY_BLOCK 16
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <Memory.h>
|
||||
|
||||
struct Array
|
||||
{
|
||||
void **entries; /* An array of void pointers, to
|
||||
* store any data */
|
||||
size_t allocated; /* Elements allocated on the heap */
|
||||
size_t size; /* Elements actually filled */
|
||||
};
|
||||
|
||||
int
|
||||
ArrayAdd(Array * array, void *value)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ArrayInsert(array, value, array->size);
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayCreate(void)
|
||||
{
|
||||
Array *array = Malloc(sizeof(Array));
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
array->size = 0;
|
||||
array->allocated = ARRAY_BLOCK;
|
||||
array->entries = Malloc(sizeof(void *) * ARRAY_BLOCK);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
Free(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
void *
|
||||
ArrayDelete(Array * array, size_t index)
|
||||
{
|
||||
size_t i;
|
||||
void *element;
|
||||
|
||||
if (!array || array->size <= index)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
element = array->entries[index];
|
||||
|
||||
for (i = index; i < array->size - 1; i++)
|
||||
{
|
||||
array->entries[i] = array->entries[i + 1];
|
||||
}
|
||||
|
||||
array->size--;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void
|
||||
ArrayFree(Array * array)
|
||||
{
|
||||
if (array)
|
||||
{
|
||||
Free(array->entries);
|
||||
Free(array);
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
ArrayGet(Array * array, size_t index)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (index >= array->size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return array->entries[index];
|
||||
}
|
||||
|
||||
|
||||
extern int
|
||||
ArrayInsert(Array * array, void *value, size_t index)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!array || !value || index > array->size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (array->size >= array->allocated)
|
||||
{
|
||||
void **tmp;
|
||||
size_t newSize = array->allocated + ARRAY_BLOCK;
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = Realloc(array->entries,
|
||||
sizeof(void *) * newSize);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
array->allocated = newSize;
|
||||
}
|
||||
|
||||
for (i = array->size; i > index; i--)
|
||||
{
|
||||
array->entries[i] = array->entries[i - 1];
|
||||
}
|
||||
|
||||
array->size++;
|
||||
|
||||
array->entries[index] = value;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t
|
||||
ArraySize(Array * array)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return array->size;
|
||||
}
|
||||
|
||||
int
|
||||
ArrayTrim(Array * array)
|
||||
{
|
||||
void **tmp;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = Realloc(array->entries,
|
||||
sizeof(void *) * array->size);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
ArraySwap(Array * array, size_t i, size_t j)
|
||||
{
|
||||
void *p = array->entries[i];
|
||||
|
||||
array->entries[i] = array->entries[j];
|
||||
array->entries[j] = p;
|
||||
}
|
||||
|
||||
static size_t
|
||||
ArrayPartition(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
|
||||
{
|
||||
void *pivot = array->entries[high];
|
||||
size_t i = low - 1;
|
||||
size_t j;
|
||||
|
||||
for (j = low; j <= high - 1; j++)
|
||||
{
|
||||
if (compare(array->entries[j], pivot) < 0)
|
||||
{
|
||||
i++;
|
||||
ArraySwap(array, i, j);
|
||||
}
|
||||
}
|
||||
ArraySwap(array, i + 1, high);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static void
|
||||
ArrayQuickSort(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
|
||||
{
|
||||
if (low < high)
|
||||
{
|
||||
size_t pi = ArrayPartition(array, low, high, compare);
|
||||
|
||||
ArrayQuickSort(array, low, pi - 1, compare);
|
||||
ArrayQuickSort(array, pi + 1, high, compare);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArraySort(Array * array, int (*compare) (void *, void *))
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ArrayQuickSort(array, 0, array->size, compare);
|
||||
}
|
||||
|
||||
/* Even though the following operations could be done using only the
|
||||
* public Array API defined above, I opted for low-level struct
|
||||
* manipulation because it allows much more efficient copying; we only
|
||||
* allocate what we for sure need upfront, and don't have to
|
||||
* re-allocate during the operation. */
|
||||
|
||||
Array *
|
||||
ArrayFromVarArgs(size_t n, va_list ap)
|
||||
{
|
||||
size_t i;
|
||||
Array *arr = Malloc(sizeof(Array));
|
||||
|
||||
if (!arr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr->size = n;
|
||||
arr->allocated = n;
|
||||
arr->entries = Malloc(sizeof(void *) * arr->allocated);
|
||||
|
||||
if (!arr->entries)
|
||||
{
|
||||
Free(arr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
arr->entries[i] = va_arg(ap, void *);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayDuplicate(Array * arr)
|
||||
{
|
||||
size_t i;
|
||||
Array *arr2 = Malloc(sizeof(Array));
|
||||
|
||||
if (!arr2)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr2->size = arr->size;
|
||||
arr2->allocated = arr->size;
|
||||
arr2->entries = Malloc(sizeof(void *) * arr->allocated);
|
||||
|
||||
if (!arr2->entries)
|
||||
{
|
||||
Free(arr2);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < arr2->size; i++)
|
||||
{
|
||||
arr2->entries[i] = arr->entries[i];
|
||||
}
|
||||
|
||||
return arr2;
|
||||
}
|
244
src/Base64.c
244
src/Base64.c
|
@ -1,244 +0,0 @@
|
|||
/*
|
||||
* 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 limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Base64.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
static const char Base64EncodeMap[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static const int Base64DecodeMap[] = {
|
||||
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
|
||||
59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
|
||||
6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
|
||||
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
|
||||
43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
};
|
||||
|
||||
size_t
|
||||
Base64EncodedSize(size_t inputSize)
|
||||
{
|
||||
size_t size = inputSize;
|
||||
|
||||
if (inputSize % 3)
|
||||
{
|
||||
size += 3 - (inputSize % 3);
|
||||
}
|
||||
|
||||
size /= 3;
|
||||
size *= 4;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t
|
||||
Base64DecodedSize(const char *base64, size_t len)
|
||||
{
|
||||
size_t ret;
|
||||
size_t i;
|
||||
|
||||
if (!base64)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = len / 4 * 3;
|
||||
|
||||
for (i = len; i > 0; i--)
|
||||
{
|
||||
if (base64[i] == '=')
|
||||
{
|
||||
ret--;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *
|
||||
Base64Encode(const char *input, size_t len)
|
||||
{
|
||||
char *out;
|
||||
size_t outLen;
|
||||
size_t i, j, v;
|
||||
|
||||
if (!input || !len)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outLen = Base64EncodedSize(len);
|
||||
out = Malloc(outLen + 1);
|
||||
if (!out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
out[outLen] = '\0';
|
||||
|
||||
for (i = 0, j = 0; i < len; i += 3, j += 4)
|
||||
{
|
||||
v = input[i];
|
||||
v = i + 1 < len ? v << 8 | input[i + 1] : v << 8;
|
||||
v = i + 2 < len ? v << 8 | input[i + 2] : v << 8;
|
||||
|
||||
out[j] = Base64EncodeMap[(v >> 18) & 0x3F];
|
||||
out[j + 1] = Base64EncodeMap[(v >> 12) & 0x3F];
|
||||
|
||||
if (i + 1 < len)
|
||||
{
|
||||
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
|
||||
}
|
||||
else
|
||||
{
|
||||
out[j + 2] = '=';
|
||||
}
|
||||
if (i + 2 < len)
|
||||
{
|
||||
out[j + 3] = Base64EncodeMap[v & 0x3F];
|
||||
}
|
||||
else
|
||||
{
|
||||
out[j + 3] = '=';
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static int
|
||||
Base64IsValidChar(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c == '+') ||
|
||||
(c == '/') ||
|
||||
(c == '=');
|
||||
}
|
||||
|
||||
char *
|
||||
Base64Decode(const char *input, size_t len)
|
||||
{
|
||||
size_t i, j;
|
||||
int v;
|
||||
size_t outLen;
|
||||
char *out;
|
||||
|
||||
if (!input)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
outLen = Base64DecodedSize(input, len);
|
||||
if (len % 4)
|
||||
{
|
||||
/* Invalid length; must have incorrect padding */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Scan for invalid characters. */
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
if (!Base64IsValidChar(input[i]))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
out = Malloc(outLen + 1);
|
||||
if (!out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out[outLen] = '\0';
|
||||
|
||||
for (i = 0, j = 0; i < len; i += 4, j += 3)
|
||||
{
|
||||
v = Base64DecodeMap[input[i] - 43];
|
||||
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
|
||||
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
|
||||
v = input[i + 3] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 3] - 43];
|
||||
|
||||
out[j] = (v >> 16) & 0xFF;
|
||||
if (input[i + 2] != '=')
|
||||
out[j + 1] = (v >> 8) & 0xFF;
|
||||
if (input[i + 3] != '=')
|
||||
out[j + 2] = v & 0xFF;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
extern void
|
||||
Base64Unpad(char *base64, size_t length)
|
||||
{
|
||||
if (!base64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (base64[length - 1] == '=')
|
||||
{
|
||||
length--;
|
||||
}
|
||||
|
||||
base64[length] = '\0';
|
||||
}
|
||||
|
||||
extern int
|
||||
Base64Pad(char **base64Ptr, size_t length)
|
||||
{
|
||||
char *tmp;
|
||||
size_t newSize;
|
||||
size_t i;
|
||||
|
||||
if (length % 4 == 0)
|
||||
{
|
||||
return length; /* Success: no padding needed */
|
||||
}
|
||||
|
||||
newSize = length + (4 - (length % 4));
|
||||
|
||||
tmp = Realloc(*base64Ptr, newSize + 100);;
|
||||
if (!tmp)
|
||||
{
|
||||
return 0; /* Memory error */
|
||||
}
|
||||
*base64Ptr = tmp;
|
||||
|
||||
for (i = length; i < newSize; i++)
|
||||
{
|
||||
(*base64Ptr)[i] = '=';
|
||||
}
|
||||
|
||||
(*base64Ptr)[newSize] = '\0';
|
||||
|
||||
return newSize;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
@ -23,36 +24,39 @@
|
|||
*/
|
||||
#include <CanonicalJson.h>
|
||||
|
||||
#include <HashMap.h>
|
||||
#include <Array.h>
|
||||
#include <Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Array.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int
|
||||
static int
|
||||
CanonicalJsonKeyCompare(void *k1, void *k2)
|
||||
{
|
||||
return strcmp((char *) k1, (char *) k2);
|
||||
}
|
||||
|
||||
static void
|
||||
CanonicalJsonEncodeValue(JsonValue * value, FILE * out)
|
||||
int
|
||||
CanonicalJsonEncodeValue(JsonValue * value, Stream * out)
|
||||
{
|
||||
Array *arr;
|
||||
size_t i, len;
|
||||
|
||||
int length = 0;
|
||||
|
||||
/* Override object type to encode using the canonical functions */
|
||||
switch (JsonValueType(value))
|
||||
{
|
||||
case JSON_OBJECT:
|
||||
CanonicalJsonEncode(JsonValueAsObject(value), out);
|
||||
length += CanonicalJsonEncode(JsonValueAsObject(value), out);
|
||||
break;
|
||||
case JSON_ARRAY:
|
||||
arr = JsonValueAsArray(value);
|
||||
len = ArraySize(arr);
|
||||
|
||||
fputc('[', out);
|
||||
StreamPutc(out, '[');
|
||||
length++;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
|
@ -64,39 +68,44 @@ CanonicalJsonEncodeValue(JsonValue * value, FILE * out)
|
|||
continue;
|
||||
}
|
||||
|
||||
CanonicalJsonEncodeValue(aVal, out);
|
||||
length += CanonicalJsonEncodeValue(aVal, out);
|
||||
if (i < len - 1)
|
||||
{
|
||||
fputc(',', out);
|
||||
StreamPutc(out, ',');
|
||||
length++;
|
||||
}
|
||||
}
|
||||
|
||||
fputc(']', out);
|
||||
StreamPutc(out, ']');
|
||||
length++;
|
||||
break;
|
||||
default:
|
||||
JsonEncodeValue(value, out);
|
||||
length += JsonEncodeValue(value, out, JSON_DEFAULT);
|
||||
break;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int
|
||||
CanonicalJsonEncode(HashMap * object, FILE * out)
|
||||
CanonicalJsonEncode(HashMap * object, Stream * out)
|
||||
{
|
||||
char *key;
|
||||
JsonValue *value;
|
||||
Array *keys;
|
||||
size_t i;
|
||||
size_t keyCount;
|
||||
int length;
|
||||
|
||||
if (!object || !out)
|
||||
if (!object)
|
||||
{
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
keys = ArrayCreate();
|
||||
if (!keys)
|
||||
{
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (HashMapIterate(object, &key, (void **) &value))
|
||||
|
@ -106,7 +115,11 @@ CanonicalJsonEncode(HashMap * object, FILE * out)
|
|||
|
||||
ArraySort(keys, CanonicalJsonKeyCompare);
|
||||
|
||||
fputc('{', out);
|
||||
/* The total number of bytes written */
|
||||
length = 0;
|
||||
|
||||
StreamPutc(out, '{');
|
||||
length++;
|
||||
|
||||
keyCount = ArraySize(keys);
|
||||
for (i = 0; i < keyCount; i++)
|
||||
|
@ -128,18 +141,21 @@ CanonicalJsonEncode(HashMap * object, FILE * out)
|
|||
continue;
|
||||
}
|
||||
|
||||
JsonEncodeString(key, out);
|
||||
fputc(':', out);
|
||||
CanonicalJsonEncodeValue(value, out);
|
||||
length += JsonEncodeString(key, out);
|
||||
StreamPutc(out, ':');
|
||||
length++;
|
||||
length += CanonicalJsonEncodeValue(value, out);
|
||||
|
||||
if (i < keyCount - 1)
|
||||
{
|
||||
fputc(',', out);
|
||||
StreamPutc(out, ',');
|
||||
length++;
|
||||
}
|
||||
}
|
||||
|
||||
fputc('}', out);
|
||||
StreamPutc(out, '}');
|
||||
length++;
|
||||
|
||||
ArrayFree(keys);
|
||||
return 1;
|
||||
return length;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Schema/Config.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Array.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Db.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
#include <Cytoplasm/Util.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#ifndef HOST_NAME_MAX
|
||||
#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
|
||||
#endif
|
||||
|
||||
void
|
||||
ConfigParse(HashMap * config, Config *tConfig)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!config)
|
||||
{
|
||||
tConfig->ok = 0;
|
||||
tConfig->err = "Invalid object given as config.";
|
||||
return;
|
||||
}
|
||||
|
||||
memset(tConfig, 0, sizeof(Config));
|
||||
|
||||
tConfig->maxCache = 0;
|
||||
|
||||
if (!ConfigFromJson(config, tConfig, &tConfig->err))
|
||||
{
|
||||
ConfigFree(tConfig);
|
||||
goto error;
|
||||
}
|
||||
if (!tConfig->baseUrl)
|
||||
{
|
||||
size_t len = strlen(tConfig->serverName) + 10;
|
||||
|
||||
tConfig->baseUrl = Malloc(len);
|
||||
if (!tConfig->baseUrl)
|
||||
{
|
||||
tConfig->err = "Couldn't allocate enough memory for 'baseUrl'.";
|
||||
goto error;
|
||||
}
|
||||
snprintf(tConfig->baseUrl, len, "https://%s/", tConfig->serverName);
|
||||
}
|
||||
if (!tConfig->log.timestampFormat)
|
||||
{
|
||||
tConfig->log.timestampFormat = StrDuplicate("default");
|
||||
}
|
||||
for (i = 0; i < ArraySize(tConfig->listen); i++)
|
||||
{
|
||||
ConfigListener *listener = ArrayGet(tConfig->listen, i);
|
||||
if (!listener->maxConnections)
|
||||
{
|
||||
listener->maxConnections = 32;
|
||||
}
|
||||
if (!listener->threads)
|
||||
{
|
||||
listener->threads = 4;
|
||||
}
|
||||
if (!listener->port)
|
||||
{
|
||||
listener->port = 8008;
|
||||
}
|
||||
}
|
||||
tConfig->ok = 1;
|
||||
tConfig->err = NULL;
|
||||
return;
|
||||
|
||||
error:
|
||||
tConfig->ok = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
ConfigExists(Db * db)
|
||||
{
|
||||
return DbExists(db, 1, "config");
|
||||
}
|
||||
|
||||
int
|
||||
ConfigCreateDefault(Db * db)
|
||||
{
|
||||
Config config;
|
||||
ConfigListener *listener;
|
||||
|
||||
HashMap *json;
|
||||
JsonValue *val;
|
||||
|
||||
DbRef *ref;
|
||||
|
||||
size_t len;
|
||||
|
||||
memset(&config, 0, sizeof(Config));
|
||||
|
||||
|
||||
config.log.output = CONFIG_LOG_OUTPUT_FILE;
|
||||
|
||||
config.runAs.gid = StrDuplicate(getgrgid(getgid())->gr_name);
|
||||
config.runAs.uid = StrDuplicate(getpwuid(getuid())->pw_name);
|
||||
|
||||
config.registration = 0;
|
||||
config.federation = 1;
|
||||
|
||||
/* Create serverName and baseUrl. */
|
||||
config.serverName = Malloc(HOST_NAME_MAX + 1);
|
||||
memset(config.serverName, 0, HOST_NAME_MAX + 1);
|
||||
gethostname(config.serverName, HOST_NAME_MAX);
|
||||
len = strlen(config.serverName) + 10;
|
||||
config.baseUrl = Malloc(len);
|
||||
snprintf(config.baseUrl, len, "https://%s/", config.serverName);
|
||||
|
||||
/* Add simple listener without TLS. */
|
||||
config.listen = ArrayCreate();
|
||||
listener = Malloc(sizeof(ConfigListener));
|
||||
listener->maxConnections = 32;
|
||||
listener->port = 8008;
|
||||
listener->threads = 4;
|
||||
|
||||
ArrayAdd(config.listen, listener);
|
||||
|
||||
/* Write it all out to the configuration file. */
|
||||
json = ConfigToJson(&config);
|
||||
val = JsonGet(json, 1, "listen");
|
||||
val = ArrayGet(JsonValueAsArray(val), 0);
|
||||
JsonValueFree(HashMapDelete(JsonValueAsObject(val), "tls"));
|
||||
|
||||
ref = DbCreate(db, 1, "config");
|
||||
if (!ref)
|
||||
{
|
||||
ConfigFree(&config);
|
||||
return 0;
|
||||
}
|
||||
DbJsonSet(ref, json);
|
||||
DbUnlock(db, ref);
|
||||
|
||||
ConfigFree(&config);
|
||||
JsonFree(json);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigLock(Db * db, Config *config)
|
||||
{
|
||||
DbRef *ref = DbLock(db, 1, "config");
|
||||
|
||||
if (!ref)
|
||||
{
|
||||
config->ok = 0;
|
||||
config->err = "Couldn't lock configuration.";
|
||||
}
|
||||
|
||||
ConfigParse(DbJson(ref), config);
|
||||
if (config->ok)
|
||||
{
|
||||
config->db = db;
|
||||
config->ref = ref;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ConfigUnlock(Config *config)
|
||||
{
|
||||
Db *db;
|
||||
DbRef *dbRef;
|
||||
|
||||
if (!config->ok)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
db = config->db;
|
||||
dbRef = config->ref;
|
||||
|
||||
ConfigFree(config);
|
||||
config->ok = 0;
|
||||
|
||||
return DbUnlock(db, dbRef);
|
||||
}
|
||||
int
|
||||
ConfigLogLevelToSyslog(ConfigLogLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case CONFIG_LOG_LEVEL_NOTICE:
|
||||
return LOG_NOTICE;
|
||||
case CONFIG_LOG_LEVEL_ERROR:
|
||||
return LOG_ERR;
|
||||
case CONFIG_LOG_LEVEL_MESSAGE:
|
||||
return LOG_INFO;
|
||||
case CONFIG_LOG_LEVEL_DEBUG:
|
||||
return LOG_DEBUG;
|
||||
case CONFIG_LOG_LEVEL_WARNING:
|
||||
return LOG_WARNING;
|
||||
}
|
||||
return LOG_INFO;
|
||||
}
|
249
src/Cron.c
249
src/Cron.c
|
@ -1,249 +0,0 @@
|
|||
/*
|
||||
* 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 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 <Cron.h>
|
||||
|
||||
#include <Array.h>
|
||||
#include <Memory.h>
|
||||
#include <Util.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct Cron
|
||||
{
|
||||
unsigned long tick;
|
||||
Array *jobs;
|
||||
pthread_mutex_t lock;
|
||||
volatile unsigned int stop:1;
|
||||
pthread_t thread;
|
||||
};
|
||||
|
||||
typedef struct Job
|
||||
{
|
||||
unsigned long interval;
|
||||
unsigned long lastExec;
|
||||
JobFunc *func;
|
||||
void *args;
|
||||
} Job;
|
||||
|
||||
static Job *
|
||||
JobCreate(long interval, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!func)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
job = Malloc(sizeof(Job));
|
||||
if (!job)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
job->interval = interval;
|
||||
job->lastExec = 0;
|
||||
job->func = func;
|
||||
job->args = args;
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
static void *
|
||||
CronThread(void *args)
|
||||
{
|
||||
Cron *cron = args;
|
||||
|
||||
while (!cron->stop)
|
||||
{
|
||||
size_t i;
|
||||
unsigned long ts; /* tick start */
|
||||
unsigned long te; /* tick end */
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
|
||||
ts = UtilServerTs();
|
||||
|
||||
for (i = 0; i < ArraySize(cron->jobs); i++)
|
||||
{
|
||||
Job *job = ArrayGet(cron->jobs, i);
|
||||
|
||||
if (ts - job->lastExec > job->interval)
|
||||
{
|
||||
job->func(job->args);
|
||||
job->lastExec = ts;
|
||||
}
|
||||
|
||||
if (!job->interval)
|
||||
{
|
||||
ArrayDelete(cron->jobs, i);
|
||||
Free(job);
|
||||
}
|
||||
}
|
||||
te = UtilServerTs();
|
||||
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
|
||||
/* Only sleep if the jobs didn't overrun the tick */
|
||||
if (cron->tick > (te - ts))
|
||||
{
|
||||
const unsigned long microTick = 100;
|
||||
unsigned long remainingTick = cron->tick - (te - ts);
|
||||
|
||||
/* Only sleep for microTick ms at a time because if the job
|
||||
* scheduler is supposed to stop before the tick is up, we
|
||||
* don't want to be stuck in a long sleep */
|
||||
while (remainingTick >= microTick && !cron->stop)
|
||||
{
|
||||
UtilSleepMillis(microTick);
|
||||
remainingTick -= microTick;
|
||||
}
|
||||
|
||||
if (remainingTick && !cron->stop)
|
||||
{
|
||||
UtilSleepMillis(remainingTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Cron *
|
||||
CronCreate(unsigned long tick)
|
||||
{
|
||||
Cron *cron = Malloc(sizeof(Cron));
|
||||
|
||||
if (!cron)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cron->jobs = ArrayCreate();
|
||||
if (!cron->jobs)
|
||||
{
|
||||
Free(cron);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cron->tick = tick;
|
||||
cron->stop = 1;
|
||||
|
||||
pthread_mutex_init(&cron->lock, NULL);
|
||||
|
||||
return cron;
|
||||
}
|
||||
|
||||
void
|
||||
CronOnce(Cron * cron, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!cron || !func)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
job = JobCreate(0, func, args);
|
||||
if (!job)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
ArrayAdd(cron->jobs, job);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
}
|
||||
|
||||
void
|
||||
CronEvery(Cron * cron, unsigned long interval, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!cron || !func)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
job = JobCreate(interval, func, args);
|
||||
if (!job)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
ArrayAdd(cron->jobs, job);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
}
|
||||
|
||||
void
|
||||
CronStart(Cron * cron)
|
||||
{
|
||||
if (!cron || !cron->stop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cron->stop = 0;
|
||||
|
||||
pthread_create(&cron->thread, NULL, CronThread, cron);
|
||||
}
|
||||
|
||||
void
|
||||
CronStop(Cron * cron)
|
||||
{
|
||||
if (!cron || cron->stop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cron->stop = 1;
|
||||
|
||||
pthread_join(cron->thread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
CronFree(Cron * cron)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!cron)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CronStop(cron);
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
for (i = 0; i < ArraySize(cron->jobs); i++)
|
||||
{
|
||||
Free(ArrayGet(cron->jobs, i));
|
||||
}
|
||||
|
||||
ArrayFree(cron->jobs);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
pthread_mutex_destroy(&cron->lock);
|
||||
|
||||
Free(cron);
|
||||
}
|
888
src/Db.c
888
src/Db.c
|
@ -1,888 +0,0 @@
|
|||
/*
|
||||
* 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 limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Db.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Json.h>
|
||||
#include <Util.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
struct Db
|
||||
{
|
||||
char *dir;
|
||||
pthread_mutex_t lock;
|
||||
|
||||
size_t cacheSize;
|
||||
size_t maxCache;
|
||||
HashMap *cache;
|
||||
|
||||
/*
|
||||
* The cache uses a double linked list (see DbRef
|
||||
* below) to know which objects are most and least
|
||||
* recently used. The following diagram helps me
|
||||
* know what way all the pointers go, because it
|
||||
* can get very confusing sometimes. For example,
|
||||
* there's nothing stopping "next" from pointing to
|
||||
* least recent, and "prev" from pointing to most
|
||||
* recent, so hopefully this clarifies the pointer
|
||||
* terminology used when dealing with the linked
|
||||
* list:
|
||||
*
|
||||
* mostRecent leastRecent
|
||||
* | prev prev | prev
|
||||
* +---+ ---> +---+ ---> +---+ ---> NULL
|
||||
* |ref| |ref| |ref|
|
||||
* NULL <--- +---+ <--- +---+ <--- +---+
|
||||
* next next next
|
||||
*/
|
||||
DbRef *mostRecent;
|
||||
DbRef *leastRecent;
|
||||
};
|
||||
|
||||
struct DbRef
|
||||
{
|
||||
HashMap *json;
|
||||
pthread_mutex_t lock;
|
||||
|
||||
unsigned long ts;
|
||||
size_t size;
|
||||
|
||||
Array *name;
|
||||
|
||||
DbRef *prev;
|
||||
DbRef *next;
|
||||
|
||||
FILE *fp;
|
||||
};
|
||||
|
||||
static void
|
||||
StringArrayFree(Array * arr)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ArraySize(arr); i++)
|
||||
{
|
||||
Free(ArrayGet(arr, i));
|
||||
}
|
||||
|
||||
ArrayFree(arr);
|
||||
}
|
||||
|
||||
static ssize_t DbComputeSize(HashMap *);
|
||||
|
||||
static ssize_t
|
||||
DbComputeSizeOfValue(JsonValue * val)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
ssize_t total = 0;
|
||||
|
||||
size_t i;
|
||||
|
||||
union
|
||||
{
|
||||
char *str;
|
||||
Array *arr;
|
||||
} u;
|
||||
|
||||
if (!val)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
a = MemoryInfoGet(val);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
switch (JsonValueType(val))
|
||||
{
|
||||
case JSON_OBJECT:
|
||||
total += DbComputeSize(JsonValueAsObject(val));
|
||||
break;
|
||||
case JSON_ARRAY:
|
||||
u.arr = JsonValueAsArray(val);
|
||||
a = MemoryInfoGet(u.arr);
|
||||
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
for (i = 0; i < ArraySize(u.arr); i++)
|
||||
{
|
||||
total += DbComputeSizeOfValue(ArrayGet(u.arr, i));
|
||||
}
|
||||
break;
|
||||
case JSON_STRING:
|
||||
u.str = JsonValueAsString(val);
|
||||
a = MemoryInfoGet(u.str);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
break;
|
||||
case JSON_NULL:
|
||||
case JSON_INTEGER:
|
||||
case JSON_FLOAT:
|
||||
case JSON_BOOLEAN:
|
||||
default:
|
||||
/* These don't use any extra heap space */
|
||||
break;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
DbComputeSize(HashMap * json)
|
||||
{
|
||||
char *key;
|
||||
JsonValue *val;
|
||||
MemoryInfo *a;
|
||||
size_t total;
|
||||
|
||||
if (!json)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
total = 0;
|
||||
|
||||
a = MemoryInfoGet(json);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
while (HashMapIterate(json, &key, (void **) &val))
|
||||
{
|
||||
a = MemoryInfoGet(key);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
total += DbComputeSizeOfValue(val);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static char *
|
||||
DbHashKey(Array * args)
|
||||
{
|
||||
size_t i;
|
||||
char *str = NULL;
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
char *tmp = StrConcat(2, str, ArrayGet(args, i));
|
||||
|
||||
Free(str);
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *
|
||||
DbDirName(Db * db, Array * args, size_t strip)
|
||||
{
|
||||
size_t i;
|
||||
char *str = StrConcat(2, db->dir, "/");
|
||||
|
||||
for (i = 0; i < ArraySize(args) - strip; i++)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
tmp = StrConcat(3, str, ArrayGet(args, i), "/");
|
||||
|
||||
Free(str);
|
||||
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *
|
||||
DbFileName(Db * db, Array * args)
|
||||
{
|
||||
size_t i;
|
||||
char *str = StrConcat(2, db->dir, "/");
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
char *tmp;
|
||||
char *arg = StrDuplicate(ArrayGet(args, i));
|
||||
size_t j = 0;
|
||||
|
||||
/* Sanitize name to prevent directory traversal attacks */
|
||||
while (arg[j])
|
||||
{
|
||||
switch (arg[j])
|
||||
{
|
||||
case '/':
|
||||
arg[j] = '_';
|
||||
break;
|
||||
case '.':
|
||||
arg[j] = '-';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
tmp = StrConcat(3, str, arg,
|
||||
(i < ArraySize(args) - 1) ? "/" : ".json");
|
||||
|
||||
Free(arg);
|
||||
Free(str);
|
||||
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static void
|
||||
DbCacheEvict(Db * db)
|
||||
{
|
||||
DbRef *ref = db->leastRecent;
|
||||
DbRef *tmp;
|
||||
|
||||
while (ref && db->cacheSize > db->maxCache)
|
||||
{
|
||||
char *hash = DbHashKey(ref->name);
|
||||
|
||||
if (pthread_mutex_trylock(&ref->lock) != 0)
|
||||
{
|
||||
/* This ref is locked by another thread, don't evict it. */
|
||||
ref = ref->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonFree(ref->json);
|
||||
pthread_mutex_unlock(&ref->lock);
|
||||
pthread_mutex_destroy(&ref->lock);
|
||||
|
||||
hash = DbHashKey(ref->name);
|
||||
HashMapDelete(db->cache, hash);
|
||||
Free(hash);
|
||||
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
db->cacheSize -= ref->size;
|
||||
|
||||
ref->next->prev = ref->prev;
|
||||
if (!ref->prev)
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
|
||||
tmp = ref->next;
|
||||
Free(ref);
|
||||
|
||||
ref = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
Db *
|
||||
DbOpen(char *dir, size_t cache)
|
||||
{
|
||||
Db *db;
|
||||
|
||||
if (!dir)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db = Malloc(sizeof(Db));
|
||||
if (!db)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db->dir = dir;
|
||||
db->maxCache = cache;
|
||||
|
||||
pthread_mutex_init(&db->lock, NULL);
|
||||
|
||||
if (db->maxCache)
|
||||
{
|
||||
db->cache = HashMapCreate();
|
||||
if (!db->cache)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db->mostRecent = NULL;
|
||||
db->leastRecent = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->cache = NULL;
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
void
|
||||
DbClose(Db * db)
|
||||
{
|
||||
char *key;
|
||||
DbRef *val;
|
||||
|
||||
if (!db)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&db->lock);
|
||||
|
||||
while (HashMapIterate(db->cache, &key, (void **) &val))
|
||||
{
|
||||
JsonFree(val->json);
|
||||
StringArrayFree(val->name);
|
||||
pthread_mutex_destroy(&val->lock);
|
||||
Free(val);
|
||||
}
|
||||
HashMapFree(db->cache);
|
||||
|
||||
Free(db);
|
||||
}
|
||||
|
||||
static DbRef *
|
||||
DbLockFromArr(Db * db, Array * args)
|
||||
{
|
||||
char *file;
|
||||
char *hash;
|
||||
DbRef *ref;
|
||||
FILE *fp;
|
||||
struct flock lock;
|
||||
|
||||
if (!db || !args)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ref = NULL;
|
||||
hash = NULL;
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
/* Check if the item is in the cache */
|
||||
hash = DbHashKey(args);
|
||||
ref = HashMapGet(db->cache, hash);
|
||||
file = DbFileName(db, args);
|
||||
|
||||
/* Open the file for reading and writing so we can lock it */
|
||||
fp = fopen(file, "r+");
|
||||
if (!fp)
|
||||
{
|
||||
if (ref)
|
||||
{
|
||||
pthread_mutex_lock(&ref->lock);
|
||||
|
||||
HashMapDelete(db->cache, hash);
|
||||
JsonFree(ref->json);
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
db->cacheSize -= ref->size;
|
||||
|
||||
if (ref->next)
|
||||
{
|
||||
ref->next->prev = ref->prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->mostRecent = ref->prev;
|
||||
}
|
||||
|
||||
if (ref->prev)
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ref->lock);
|
||||
pthread_mutex_destroy(&ref->lock);
|
||||
Free(ref);
|
||||
}
|
||||
ref = NULL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
lock.l_start = 0;
|
||||
lock.l_len = 0;
|
||||
lock.l_type = F_WRLCK;
|
||||
lock.l_whence = SEEK_SET;
|
||||
|
||||
/* Lock the file on the disk */
|
||||
if (fcntl(fileno(fp), F_SETLK, &lock) < 0)
|
||||
{
|
||||
printf("fcntl() failed on %s (%s)\n", file, strerror(errno));
|
||||
fclose(fp);
|
||||
ref = NULL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (ref) /* In cache */
|
||||
{
|
||||
unsigned long diskTs = UtilLastModified(file);
|
||||
|
||||
pthread_mutex_lock(&ref->lock);
|
||||
ref->fp = fp;
|
||||
|
||||
if (diskTs > ref->ts)
|
||||
{
|
||||
/* File was modified on disk since it was cached */
|
||||
HashMap *json = JsonDecode(fp);
|
||||
|
||||
if (!json)
|
||||
{
|
||||
pthread_mutex_unlock(&ref->lock);
|
||||
fclose(fp);
|
||||
ref = NULL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
JsonFree(ref->json);
|
||||
ref->json = json;
|
||||
ref->ts = diskTs;
|
||||
ref->size = DbComputeSize(ref->json);
|
||||
|
||||
}
|
||||
|
||||
/* Float this ref to mostRecent */
|
||||
if (ref->next)
|
||||
{
|
||||
ref->next->prev = ref->prev;
|
||||
|
||||
if (!ref->prev)
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
|
||||
ref->prev = db->mostRecent;
|
||||
ref->next = NULL;
|
||||
db->mostRecent = ref;
|
||||
}
|
||||
|
||||
/* The file on disk may be larger than what we have in memory,
|
||||
* which may require items in cache to be evicted. */
|
||||
DbCacheEvict(db);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array *name = ArrayCreate();
|
||||
size_t i;
|
||||
|
||||
/* Not in cache; load from disk */
|
||||
|
||||
ref = Malloc(sizeof(DbRef));
|
||||
if (!ref)
|
||||
{
|
||||
fclose(fp);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
ref->json = JsonDecode(fp);
|
||||
ref->fp = fp;
|
||||
|
||||
if (!ref->json)
|
||||
{
|
||||
Free(ref);
|
||||
fclose(fp);
|
||||
ref = NULL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&ref->lock, NULL);
|
||||
pthread_mutex_lock(&ref->lock);
|
||||
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
ArrayAdd(name, StrDuplicate(ArrayGet(args, i)));
|
||||
}
|
||||
ref->name = name;
|
||||
|
||||
if (db->cache)
|
||||
{
|
||||
ref->ts = UtilServerTs();
|
||||
ref->size = DbComputeSize(ref->json);
|
||||
HashMapSet(db->cache, hash, ref);
|
||||
db->cacheSize += ref->size;
|
||||
|
||||
ref->next = NULL;
|
||||
ref->prev = db->mostRecent;
|
||||
db->mostRecent = ref;
|
||||
|
||||
/* Adding this item to the cache may case it to grow too
|
||||
* large, requiring some items to be evicted */
|
||||
DbCacheEvict(db);
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
Free(file);
|
||||
Free(hash);
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbCreate(Db * db, size_t nArgs,...)
|
||||
{
|
||||
FILE *fp;
|
||||
char *file;
|
||||
char *dir;
|
||||
va_list ap;
|
||||
Array *args;
|
||||
DbRef *ret;
|
||||
|
||||
if (!db)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
file = DbFileName(db, args);
|
||||
|
||||
if (UtilLastModified(file))
|
||||
{
|
||||
Free(file);
|
||||
ArrayFree(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dir = DbDirName(db, args, 1);
|
||||
if (UtilMkdir(dir, 0750) < 0)
|
||||
{
|
||||
Free(file);
|
||||
ArrayFree(args);
|
||||
Free(dir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Free(dir);
|
||||
|
||||
fp = fopen(file, "w");
|
||||
Free(file);
|
||||
if (!fp)
|
||||
{
|
||||
ArrayFree(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fprintf(fp, "{}");
|
||||
fflush(fp);
|
||||
fclose(fp);
|
||||
|
||||
ret = DbLockFromArr(db, args);
|
||||
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
DbDelete(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
char *file;
|
||||
char *hash;
|
||||
int ret = 1;
|
||||
DbRef *ref;
|
||||
|
||||
if (!db)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
hash = DbHashKey(args);
|
||||
file = DbFileName(db, args);
|
||||
|
||||
ref = HashMapGet(db->cache, hash);
|
||||
if (ref)
|
||||
{
|
||||
pthread_mutex_lock(&ref->lock);
|
||||
|
||||
HashMapDelete(db->cache, hash);
|
||||
JsonFree(ref->json);
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
db->cacheSize -= ref->size;
|
||||
|
||||
if (ref->next)
|
||||
{
|
||||
ref->next->prev = ref->prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->mostRecent = ref->prev;
|
||||
}
|
||||
|
||||
if (ref->prev)
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ref->lock);
|
||||
pthread_mutex_destroy(&ref->lock);
|
||||
Free(ref);
|
||||
}
|
||||
|
||||
Free(hash);
|
||||
|
||||
if (UtilLastModified(file))
|
||||
{
|
||||
ret = remove(file) == 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
ArrayFree(args);
|
||||
Free(file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbLock(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
DbRef *ret;
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = DbLockFromArr(db, args);
|
||||
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
DbUnlock(Db * db, DbRef * ref)
|
||||
{
|
||||
if (!db || !ref)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
rewind(ref->fp);
|
||||
if (ftruncate(fileno(ref->fp), 0) < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
JsonEncode(ref->json, ref->fp);
|
||||
|
||||
fflush(ref->fp);
|
||||
fclose(ref->fp);
|
||||
|
||||
if (db->cache)
|
||||
{
|
||||
db->cacheSize -= ref->size;
|
||||
ref->size = DbComputeSize(ref->json);
|
||||
db->cacheSize += ref->size;
|
||||
|
||||
/* If this ref has grown significantly since we last computed
|
||||
* its size, it may have filled the cache and require some
|
||||
* items to be evicted. */
|
||||
DbCacheEvict(db);
|
||||
pthread_mutex_unlock(&ref->lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonFree(ref->json);
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
pthread_mutex_unlock(&ref->lock);
|
||||
pthread_mutex_destroy(&ref->lock);
|
||||
|
||||
Free(ref);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
DbExists(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
char *file;
|
||||
int ret;
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Though it's not explicitly required, we lock the
|
||||
* database before checking that an object exists to
|
||||
* prevent any potential race conditions.
|
||||
*/
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
file = DbFileName(db, args);
|
||||
ret = UtilLastModified(file);
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
Free(file);
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Array *
|
||||
DbList(Db * db, size_t nArgs,...)
|
||||
{
|
||||
Array *result;
|
||||
Array *path;
|
||||
DIR *files;
|
||||
struct dirent *file;
|
||||
char *dir;
|
||||
va_list ap;
|
||||
|
||||
if (!db || !nArgs)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = ArrayCreate();
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
path = ArrayFromVarArgs(nArgs, ap);
|
||||
dir = DbDirName(db, path, 0);
|
||||
|
||||
files = opendir(dir);
|
||||
if (!files)
|
||||
{
|
||||
ArrayFree(path);
|
||||
ArrayFree(result);
|
||||
Free(dir);
|
||||
return NULL;
|
||||
}
|
||||
while ((file = readdir(files)))
|
||||
{
|
||||
size_t namlen = strlen(file->d_name);
|
||||
|
||||
if (namlen > 5)
|
||||
{
|
||||
int nameOffset = namlen - 5;
|
||||
|
||||
if (strcmp(file->d_name + nameOffset, ".json") == 0)
|
||||
{
|
||||
file->d_name[nameOffset] = '\0';
|
||||
ArrayAdd(result, StrDuplicate(file->d_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(files);
|
||||
|
||||
ArrayFree(path);
|
||||
Free(dir);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
DbListFree(Array * arr)
|
||||
{
|
||||
StringArrayFree(arr);
|
||||
}
|
||||
|
||||
HashMap *
|
||||
DbJson(DbRef * ref)
|
||||
{
|
||||
return ref ? ref->json : 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
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
@ -21,15 +22,14 @@
|
|||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Filter.h>
|
||||
|
||||
#ifndef TELODENDRIA_RAND_H
|
||||
#define TELODENDRIA_RAND_H
|
||||
#include <stddef.h>
|
||||
#include <Schema/Filter.h>
|
||||
|
||||
extern int
|
||||
RandInt(unsigned int);
|
||||
|
||||
extern void
|
||||
RandIntN(int *, size_t, unsigned int);
|
||||
|
||||
#endif /* TELODENDRIA_RAND_H */
|
||||
HashMap *
|
||||
FilterApply(Filter * filter, HashMap * event)
|
||||
{
|
||||
(void) filter;
|
||||
(void) event;
|
||||
return NULL;
|
||||
}
|
384
src/HashMap.c
384
src/HashMap.c
|
@ -1,384 +0,0 @@
|
|||
/*
|
||||
* 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 limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HashMap.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct HashMapBucket
|
||||
{
|
||||
unsigned long hash;
|
||||
char *key;
|
||||
void *value;
|
||||
} HashMapBucket;
|
||||
|
||||
struct HashMap
|
||||
{
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
HashMapBucket **entries;
|
||||
|
||||
unsigned long (*hashFunc) (const char *);
|
||||
|
||||
float maxLoad;
|
||||
size_t iterator;
|
||||
};
|
||||
|
||||
static unsigned long
|
||||
HashMapHashKey(const char *key)
|
||||
{
|
||||
unsigned long hash = 2166136261u;
|
||||
size_t i = 0;
|
||||
|
||||
while (key[i])
|
||||
{
|
||||
hash ^= (unsigned char) key[i];
|
||||
hash *= 16777619;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int
|
||||
HashMapGrow(HashMap * map)
|
||||
{
|
||||
size_t oldCapacity;
|
||||
size_t i;
|
||||
HashMapBucket **newEntries;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
oldCapacity = map->capacity;
|
||||
map->capacity *= 2;
|
||||
|
||||
newEntries = Malloc(map->capacity * sizeof(HashMapBucket *));
|
||||
if (!newEntries)
|
||||
{
|
||||
map->capacity /= 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(newEntries, 0, map->capacity * sizeof(HashMapBucket *));
|
||||
|
||||
for (i = 0; i < oldCapacity; i++)
|
||||
{
|
||||
/* If there is a value here, and it isn't a tombstone */
|
||||
if (map->entries[i] && map->entries[i]->hash)
|
||||
{
|
||||
/* Copy it to the new entries array */
|
||||
size_t index = map->entries[i]->hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (newEntries[index])
|
||||
{
|
||||
if (!newEntries[index]->hash)
|
||||
{
|
||||
Free(newEntries[index]);
|
||||
newEntries[index] = map->entries[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newEntries[index] = map->entries[i];
|
||||
break;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Either NULL or a tombstone */
|
||||
Free(map->entries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Free(map->entries);
|
||||
map->entries = newEntries;
|
||||
return 1;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HashMapCreate(void)
|
||||
{
|
||||
HashMap *map = Malloc(sizeof(HashMap));
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
map->maxLoad = 0.75;
|
||||
map->count = 0;
|
||||
map->capacity = 16;
|
||||
map->iterator = 0;
|
||||
map->hashFunc = HashMapHashKey;
|
||||
|
||||
map->entries = Malloc(map->capacity * sizeof(HashMapBucket *));
|
||||
if (!map->entries)
|
||||
{
|
||||
Free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(map->entries, 0, map->capacity * sizeof(HashMapBucket *));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapDelete(HashMap * map, const char *key)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = map->hashFunc(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
bucket->hash = 0;
|
||||
return bucket->value;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapFree(HashMap * map)
|
||||
{
|
||||
if (map)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < map->capacity; i++)
|
||||
{
|
||||
if (map->entries[i])
|
||||
{
|
||||
Free(map->entries[i]->key);
|
||||
Free(map->entries[i]);
|
||||
}
|
||||
}
|
||||
Free(map->entries);
|
||||
Free(map);
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapGet(HashMap * map, const char *key)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = map->hashFunc(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
return bucket->value;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
HashMapIterate(HashMap * map, char **key, void **value)
|
||||
{
|
||||
if (!map)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (map->iterator >= map->capacity)
|
||||
{
|
||||
map->iterator = 0;
|
||||
*key = NULL;
|
||||
*value = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (map->iterator < map->capacity)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[map->iterator];
|
||||
|
||||
map->iterator++;
|
||||
|
||||
if (bucket && bucket->hash)
|
||||
{
|
||||
*key = bucket->key;
|
||||
*value = bucket->value;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
map->iterator = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapMaxLoadSet(HashMap * map, float load)
|
||||
{
|
||||
if (!map || (load > 1.0 || load <= 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->maxLoad = load;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapFunctionSet(HashMap * map, unsigned long (*hashFunc) (const char *))
|
||||
{
|
||||
if (!map || !hashFunc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->hashFunc = hashFunc;
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapSet(HashMap * map, char *key, void *value)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
key = StrDuplicate(key);
|
||||
|
||||
if (!map || !key || !value)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (map->count + 1 > map->capacity * map->maxLoad)
|
||||
{
|
||||
HashMapGrow(map);
|
||||
}
|
||||
|
||||
hash = map->hashFunc(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
bucket = Malloc(sizeof(HashMapBucket));
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bucket->hash = hash;
|
||||
bucket->key = key;
|
||||
bucket->value = value;
|
||||
map->entries[index] = bucket;
|
||||
map->count++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bucket->hash)
|
||||
{
|
||||
bucket->hash = hash;
|
||||
Free(bucket->key);
|
||||
bucket->key = key;
|
||||
bucket->value = value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
void *oldValue = bucket->value;
|
||||
|
||||
Free(bucket->key);
|
||||
bucket->key = key;
|
||||
|
||||
bucket->value = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapIterateFree(char *key, void *value)
|
||||
{
|
||||
if (key)
|
||||
{
|
||||
Free(key);
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
Free(value);
|
||||
}
|
||||
}
|
100
src/Html.c
100
src/Html.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
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
@ -28,7 +29,7 @@
|
|||
#include <Telodendria.h>
|
||||
|
||||
void
|
||||
HtmlBegin(FILE * stream, char *title)
|
||||
HtmlBegin(Stream * stream, char *title)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
|
@ -37,87 +38,36 @@ HtmlBegin(FILE * stream, char *title)
|
|||
return;
|
||||
}
|
||||
|
||||
fprintf(stream,
|
||||
"<!DOCTYPE html>"
|
||||
"<html>"
|
||||
"<head>"
|
||||
"<meta charset=\"utf-8\">"
|
||||
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
||||
"<title>%s | Telodendria</title>"
|
||||
,title
|
||||
);
|
||||
HtmlBeginStyle(stream);
|
||||
fprintf(stream,
|
||||
":root {"
|
||||
" color-scheme: dark;"
|
||||
" --accent: #7b8333;"
|
||||
"}"
|
||||
"body {"
|
||||
" margin: auto;"
|
||||
" width: 100%%;"
|
||||
" max-width: 8.5in;"
|
||||
" padding: 0.25in;"
|
||||
" background-color: #0d1117;"
|
||||
" color: white;"
|
||||
"}"
|
||||
"a {"
|
||||
" color: var(--accent);"
|
||||
" text-decoration: none;"
|
||||
"}"
|
||||
"h1 {"
|
||||
" text-align: center;"
|
||||
"}"
|
||||
".logo {"
|
||||
" color: var(--accent);"
|
||||
" text-align: center;"
|
||||
" font-weight: bold;"
|
||||
"}"
|
||||
);
|
||||
|
||||
fprintf(stream,
|
||||
".form {"
|
||||
" margin: auto;"
|
||||
" width: 100%%;"
|
||||
" max-width: 400px;"
|
||||
" border-radius: 10px;"
|
||||
" border: 1px var(--accent) solid;"
|
||||
" padding: 10px;"
|
||||
"}"
|
||||
"form {"
|
||||
" display: block;"
|
||||
"}"
|
||||
"form > input, label {"
|
||||
" width: 95%%;"
|
||||
" height: 25px;"
|
||||
" display: block;"
|
||||
" margin-bottom: 5px;"
|
||||
" margin-left: auto;"
|
||||
" margin-right: auto;"
|
||||
"}"
|
||||
);
|
||||
HtmlEndStyle(stream);
|
||||
|
||||
fprintf(stream,
|
||||
"</head>"
|
||||
"<body>"
|
||||
"<pre class=\"logo\">"
|
||||
StreamPrintf(stream,
|
||||
"<!DOCTYPE html>"
|
||||
"<html>"
|
||||
"<head>"
|
||||
"<meta charset=\"utf-8\">"
|
||||
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
||||
"<title>%s | Telodendria</title>"
|
||||
"<link rel=\"stylesheet\" href=\"/_matrix/static/telodendria.css\">"
|
||||
"<script src=\"/_matrix/static/telodendria.js\"></script>"
|
||||
"</head>"
|
||||
"<body>"
|
||||
"<pre class=\"logo\">"
|
||||
,title
|
||||
);
|
||||
|
||||
for (i = 0; i < TELODENDRIA_LOGO_HEIGHT; i++)
|
||||
{
|
||||
fprintf(stream, "%s\n", TelodendriaLogo[i]);
|
||||
StreamPrintf(stream, "%s\n", TelodendriaLogo[i]);
|
||||
}
|
||||
|
||||
fprintf(stream,
|
||||
"</pre>"
|
||||
"<h1>%s</h1>"
|
||||
,title);
|
||||
StreamPrintf(stream,
|
||||
"</pre>"
|
||||
"<h1>%s</h1>"
|
||||
,title);
|
||||
}
|
||||
|
||||
void
|
||||
HtmlEnd(FILE * stream)
|
||||
HtmlEnd(Stream * stream)
|
||||
{
|
||||
fprintf(stream,
|
||||
"</body>"
|
||||
"</html>");
|
||||
StreamPuts(stream,
|
||||
"</body>"
|
||||
"</html>");
|
||||
}
|
||||
|
|
534
src/Http.c
534
src/Http.c
|
@ -1,534 +0,0 @@
|
|||
/*
|
||||
* 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 limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Http.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <HashMap.h>
|
||||
|
||||
#ifndef TELODENDRIA_STRING_CHUNK
|
||||
#define TELODENDRIA_STRING_CHUNK 64
|
||||
#endif
|
||||
|
||||
const char *
|
||||
HttpRequestMethodToString(const HttpRequestMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case HTTP_GET:
|
||||
return "GET";
|
||||
case HTTP_HEAD:
|
||||
return "HEAD";
|
||||
case HTTP_POST:
|
||||
return "POST";
|
||||
case HTTP_PUT:
|
||||
return "PUT";
|
||||
case HTTP_DELETE:
|
||||
return "DELETE";
|
||||
case HTTP_CONNECT:
|
||||
return "CONNECT";
|
||||
case HTTP_OPTIONS:
|
||||
return "OPTIONS";
|
||||
case HTTP_TRACE:
|
||||
return "TRACE";
|
||||
case HTTP_PATCH:
|
||||
return "PATCH";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestMethod
|
||||
HttpRequestMethodFromString(const char *str)
|
||||
{
|
||||
if (strcmp(str, "GET") == 0)
|
||||
{
|
||||
return HTTP_GET;
|
||||
}
|
||||
|
||||
if (strcmp(str, "HEAD") == 0)
|
||||
{
|
||||
return HTTP_HEAD;
|
||||
}
|
||||
|
||||
if (strcmp(str, "POST") == 0)
|
||||
{
|
||||
return HTTP_POST;
|
||||
}
|
||||
|
||||
if (strcmp(str, "PUT") == 0)
|
||||
{
|
||||
return HTTP_PUT;
|
||||
}
|
||||
|
||||
if (strcmp(str, "DELETE") == 0)
|
||||
{
|
||||
return HTTP_DELETE;
|
||||
}
|
||||
|
||||
if (strcmp(str, "CONNECT") == 0)
|
||||
{
|
||||
return HTTP_CONNECT;
|
||||
}
|
||||
|
||||
if (strcmp(str, "OPTIONS") == 0)
|
||||
{
|
||||
return HTTP_OPTIONS;
|
||||
}
|
||||
|
||||
if (strcmp(str, "TRACE") == 0)
|
||||
{
|
||||
return HTTP_TRACE;
|
||||
}
|
||||
|
||||
if (strcmp(str, "PATCH") == 0)
|
||||
{
|
||||
return HTTP_PATCH;
|
||||
}
|
||||
|
||||
return HTTP_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
const char *
|
||||
HttpStatusToString(const HttpStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case HTTP_CONTINUE:
|
||||
return "Continue";
|
||||
case HTTP_SWITCHING_PROTOCOLS:
|
||||
return "Switching Protocols";
|
||||
case HTTP_EARLY_HINTS:
|
||||
return "Early Hints";
|
||||
case HTTP_OK:
|
||||
return "Ok";
|
||||
case HTTP_CREATED:
|
||||
return "Created";
|
||||
case HTTP_ACCEPTED:
|
||||
return "Accepted";
|
||||
case HTTP_NON_AUTHORITATIVE_INFORMATION:
|
||||
return "Non-Authoritative Information";
|
||||
case HTTP_NO_CONTENT:
|
||||
return "No Content";
|
||||
case HTTP_RESET_CONTENT:
|
||||
return "Reset Content";
|
||||
case HTTP_PARTIAL_CONTENT:
|
||||
return "Partial Content";
|
||||
case HTTP_MULTIPLE_CHOICES:
|
||||
return "Multiple Choices";
|
||||
case HTTP_MOVED_PERMANENTLY:
|
||||
return "Moved Permanently";
|
||||
case HTTP_FOUND:
|
||||
return "Found";
|
||||
case HTTP_SEE_OTHER:
|
||||
return "See Other";
|
||||
case HTTP_NOT_MODIFIED:
|
||||
return "Not Modified";
|
||||
case HTTP_TEMPORARY_REDIRECT:
|
||||
return "Temporary Redirect";
|
||||
case HTTP_PERMANENT_REDIRECT:
|
||||
return "Permanent Redirect";
|
||||
case HTTP_BAD_REQUEST:
|
||||
return "Bad Request";
|
||||
case HTTP_UNAUTHORIZED:
|
||||
return "Unauthorized";
|
||||
case HTTP_FORBIDDEN:
|
||||
return "Forbidden";
|
||||
case HTTP_NOT_FOUND:
|
||||
return "Not Found";
|
||||
case HTTP_METHOD_NOT_ALLOWED:
|
||||
return "Method Not Allowed";
|
||||
case HTTP_NOT_ACCEPTABLE:
|
||||
return "Not Acceptable";
|
||||
case HTTP_PROXY_AUTH_REQUIRED:
|
||||
return "Proxy Authentication Required";
|
||||
case HTTP_REQUEST_TIMEOUT:
|
||||
return "Request Timeout";
|
||||
case HTTP_CONFLICT:
|
||||
return "Conflict";
|
||||
case HTTP_GONE:
|
||||
return "Gone";
|
||||
case HTTP_LENGTH_REQUIRED:
|
||||
return "Length Required";
|
||||
case HTTP_PRECONDITION_FAILED:
|
||||
return "Precondition Failed";
|
||||
case HTTP_PAYLOAD_TOO_LARGE:
|
||||
return "Payload Too Large";
|
||||
case HTTP_URI_TOO_LONG:
|
||||
return "URI Too Long";
|
||||
case HTTP_UNSUPPORTED_MEDIA_TYPE:
|
||||
return "Unsupported Media Type";
|
||||
case HTTP_RANGE_NOT_SATISFIABLE:
|
||||
return "Range Not Satisfiable";
|
||||
case HTTP_EXPECTATION_FAILED:
|
||||
return "Expectation Failed";
|
||||
case HTTP_TEAPOT:
|
||||
return "I'm a Teapot";
|
||||
case HTTP_UPGRADE_REQUIRED:
|
||||
return "Upgrade Required";
|
||||
case HTTP_PRECONDITION_REQUIRED:
|
||||
return "Precondition Required";
|
||||
case HTTP_TOO_MANY_REQUESTS:
|
||||
return "Too Many Requests";
|
||||
case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
|
||||
return "Request Header Fields Too Large";
|
||||
case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
|
||||
return "Unavailable For Legal Reasons";
|
||||
case HTTP_INTERNAL_SERVER_ERROR:
|
||||
return "Internal Server Error";
|
||||
case HTTP_NOT_IMPLEMENTED:
|
||||
return "Not Implemented";
|
||||
case HTTP_BAD_GATEWAY:
|
||||
return "Bad Gateway";
|
||||
case HTTP_SERVICE_UNAVAILABLE:
|
||||
return "Service Unavailable";
|
||||
case HTTP_GATEWAY_TIMEOUT:
|
||||
return "Gateway Timeout";
|
||||
case HTTP_VERSION_NOT_SUPPORTED:
|
||||
return "Version Not Supported";
|
||||
case HTTP_VARIANT_ALSO_NEGOTIATES:
|
||||
return "Variant Also Negotiates";
|
||||
case HTTP_NOT_EXTENDED:
|
||||
return "Not Extended";
|
||||
case HTTP_NETWORK_AUTH_REQUIRED:
|
||||
return "Network Authentication Required";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
HttpUrlEncode(char *str)
|
||||
{
|
||||
size_t size;
|
||||
size_t len;
|
||||
char *encoded;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = TELODENDRIA_STRING_CHUNK;
|
||||
len = 0;
|
||||
encoded = Malloc(size);
|
||||
if (!encoded)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*str)
|
||||
{
|
||||
char c = *str;
|
||||
|
||||
if (len >= size - 4)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
size += TELODENDRIA_STRING_CHUNK;
|
||||
tmp = Realloc(encoded, size);
|
||||
if (!tmp)
|
||||
{
|
||||
Free(encoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encoded = tmp;
|
||||
}
|
||||
|
||||
/* Control characters and extended characters */
|
||||
if (c <= 0x1F || c >= 0x7F)
|
||||
{
|
||||
goto percentEncode;
|
||||
}
|
||||
|
||||
/* Reserved and unsafe characters */
|
||||
switch (c)
|
||||
{
|
||||
case '$':
|
||||
case '&':
|
||||
case '+':
|
||||
case ',':
|
||||
case '/':
|
||||
case ':':
|
||||
case ';':
|
||||
case '=':
|
||||
case '?':
|
||||
case '@':
|
||||
case ' ':
|
||||
case '"':
|
||||
case '<':
|
||||
case '>':
|
||||
case '#':
|
||||
case '%':
|
||||
case '{':
|
||||
case '}':
|
||||
case '|':
|
||||
case '\\':
|
||||
case '^':
|
||||
case '~':
|
||||
case '[':
|
||||
case ']':
|
||||
case '`':
|
||||
goto percentEncode;
|
||||
break;
|
||||
default:
|
||||
encoded[len] = c;
|
||||
len++;
|
||||
str++;
|
||||
continue;
|
||||
}
|
||||
|
||||
percentEncode:
|
||||
encoded[len] = '%';
|
||||
len++;
|
||||
snprintf(encoded + len, 3, "%2X", c);
|
||||
len += 2;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
encoded[len] = '\0';
|
||||
return encoded;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpUrlDecode(char *str)
|
||||
{
|
||||
size_t i;
|
||||
size_t inputLen;
|
||||
char *decoded;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
inputLen = strlen(str);
|
||||
decoded = Malloc(inputLen + 1);
|
||||
|
||||
if (!decoded)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*str)
|
||||
{
|
||||
char c = *str;
|
||||
|
||||
if (c == '%')
|
||||
{
|
||||
unsigned int d;
|
||||
|
||||
str++;
|
||||
|
||||
if (sscanf(str, "%2X", &d) != 1)
|
||||
{
|
||||
/* Decoding error */
|
||||
Free(decoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!d)
|
||||
{
|
||||
/* Null character given, don't put that in the string. */
|
||||
continue;
|
||||
}
|
||||
|
||||
c = (char) d;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
decoded[i] = c;
|
||||
i++;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
decoded[i] = '\0';
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpParamDecode(char *in)
|
||||
{
|
||||
HashMap *params;
|
||||
|
||||
if (!in)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
params = HashMapCreate();
|
||||
if (!params)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*in)
|
||||
{
|
||||
char *buf;
|
||||
size_t allocated;
|
||||
size_t len;
|
||||
|
||||
char *decKey;
|
||||
char *decVal;
|
||||
|
||||
/* Read in key */
|
||||
|
||||
allocated = TELODENDRIA_STRING_CHUNK;
|
||||
buf = Malloc(allocated);
|
||||
len = 0;
|
||||
|
||||
while (*in && *in != '=')
|
||||
{
|
||||
if (len >= allocated - 1)
|
||||
{
|
||||
allocated += TELODENDRIA_STRING_CHUNK;
|
||||
buf = Realloc(buf, allocated);
|
||||
}
|
||||
|
||||
buf[len] = *in;
|
||||
len++;
|
||||
in++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
/* Sanity check */
|
||||
if (*in != '=')
|
||||
{
|
||||
/* Malformed param */
|
||||
Free(buf);
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
in++;
|
||||
|
||||
/* Decode key */
|
||||
decKey = HttpUrlDecode(buf);
|
||||
Free(buf);
|
||||
|
||||
if (!decKey)
|
||||
{
|
||||
/* Decoding error */
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read in value */
|
||||
allocated = TELODENDRIA_STRING_CHUNK;
|
||||
buf = Malloc(allocated);
|
||||
len = 0;
|
||||
|
||||
while (*in && *in != '&')
|
||||
{
|
||||
if (len >= allocated - 1)
|
||||
{
|
||||
allocated += TELODENDRIA_STRING_CHUNK;
|
||||
buf = Realloc(buf, allocated);
|
||||
}
|
||||
|
||||
buf[len] = *in;
|
||||
len++;
|
||||
in++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
/* Decode value */
|
||||
decVal = HttpUrlDecode(buf);
|
||||
Free(buf);
|
||||
|
||||
if (!decVal)
|
||||
{
|
||||
/* Decoding error */
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf = HashMapSet(params, decKey, decVal);
|
||||
if (buf)
|
||||
{
|
||||
Free(buf);
|
||||
}
|
||||
Free(decKey);
|
||||
|
||||
if (*in == '&')
|
||||
{
|
||||
in++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpParamEncode(HashMap * params)
|
||||
{
|
||||
char *key;
|
||||
char *val;
|
||||
char *out = NULL;
|
||||
|
||||
if (!params || !out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (HashMapIterate(params, &key, (void *) &val))
|
||||
{
|
||||
char *encKey;
|
||||
char *encVal;
|
||||
|
||||
encKey = HttpUrlEncode(key);
|
||||
encVal = HttpUrlEncode(val);
|
||||
|
||||
if (!encKey || !encVal)
|
||||
{
|
||||
/* Memory error */
|
||||
Free(encKey);
|
||||
Free(encVal);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO */
|
||||
|
||||
Free(encKey);
|
||||
Free(encVal);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
764
src/HttpServer.c
764
src/HttpServer.c
|
@ -1,764 +0,0 @@
|
|||
/*
|
||||
* 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 limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HttpServer.h>
|
||||
#include <Memory.h>
|
||||
#include <Queue.h>
|
||||
#include <Array.h>
|
||||
#include <Util.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
static const char ENABLE = 1;
|
||||
|
||||
struct HttpServer
|
||||
{
|
||||
int sd;
|
||||
unsigned int nThreads;
|
||||
unsigned int maxConnections;
|
||||
pthread_t socketThread;
|
||||
|
||||
volatile unsigned int stop:1;
|
||||
volatile unsigned int isRunning:1;
|
||||
|
||||
HttpHandler *requestHandler;
|
||||
void *handlerArgs;
|
||||
|
||||
Queue *connQueue;
|
||||
pthread_mutex_t connQueueMutex;
|
||||
|
||||
Array *threadPool;
|
||||
};
|
||||
|
||||
struct HttpServerContext
|
||||
{
|
||||
HashMap *requestHeaders;
|
||||
HttpRequestMethod requestMethod;
|
||||
char *requestPath;
|
||||
HashMap *requestParams;
|
||||
|
||||
HashMap *responseHeaders;
|
||||
HttpStatus responseStatus;
|
||||
|
||||
FILE *stream;
|
||||
};
|
||||
|
||||
typedef struct HttpServerWorkerThreadArgs
|
||||
{
|
||||
HttpServer *server;
|
||||
int id;
|
||||
pthread_t thread;
|
||||
} HttpServerWorkerThreadArgs;
|
||||
|
||||
static HttpServerContext *
|
||||
HttpServerContextCreate(HttpRequestMethod requestMethod,
|
||||
char *requestPath, HashMap * requestParams, FILE * stream)
|
||||
{
|
||||
HttpServerContext *c;
|
||||
|
||||
c = Malloc(sizeof(HttpServerContext));
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->requestHeaders = HashMapCreate();
|
||||
if (!c->requestHeaders)
|
||||
{
|
||||
Free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->responseHeaders = HashMapCreate();
|
||||
if (!c->responseHeaders)
|
||||
{
|
||||
Free(c->requestHeaders);
|
||||
Free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->requestMethod = requestMethod;
|
||||
c->requestPath = requestPath;
|
||||
c->requestParams = requestParams;
|
||||
c->stream = stream;
|
||||
c->responseStatus = HTTP_OK;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
HttpServerContextFree(HttpServerContext * c)
|
||||
{
|
||||
char *key;
|
||||
void *val;
|
||||
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (HashMapIterate(c->requestHeaders, &key, &val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
HashMapFree(c->requestHeaders);
|
||||
|
||||
while (HashMapIterate(c->responseHeaders, &key, &val))
|
||||
{
|
||||
/*
|
||||
* These are generated by code. As such, they may be either
|
||||
* on the heap, or on the stack, depending on how they were
|
||||
* added.
|
||||
*
|
||||
* Basically, if the memory API knows about a pointer, then
|
||||
* it can be freed. If it doesn't know about a pointer, skip
|
||||
* freeing it because it's probably a stack pointer.
|
||||
*/
|
||||
|
||||
if (MemoryInfoGet(val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
}
|
||||
|
||||
HashMapFree(c->responseHeaders);
|
||||
|
||||
while (HashMapIterate(c->requestParams, &key, &val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
|
||||
HashMapFree(c->requestParams);
|
||||
|
||||
Free(c->requestPath);
|
||||
fclose(c->stream);
|
||||
|
||||
Free(c);
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpRequestHeaders(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestHeaders;
|
||||
}
|
||||
|
||||
HttpRequestMethod
|
||||
HttpRequestMethodGet(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return HTTP_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
return c->requestMethod;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpRequestPath(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestPath;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpRequestParams(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestParams;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpResponseHeader(HttpServerContext * c, char *key, char *val)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return HashMapSet(c->responseHeaders, key, val);
|
||||
}
|
||||
|
||||
void
|
||||
HttpResponseStatus(HttpServerContext * c, HttpStatus status)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
c->responseStatus = status;
|
||||
}
|
||||
|
||||
FILE *
|
||||
HttpStream(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->stream;
|
||||
}
|
||||
|
||||
void
|
||||
HttpSendHeaders(HttpServerContext * c)
|
||||
{
|
||||
FILE *fp = c->stream;
|
||||
|
||||
char *key;
|
||||
char *val;
|
||||
|
||||
fprintf(fp, "HTTP/1.0 %d %s\n", c->responseStatus, HttpStatusToString(c->responseStatus));
|
||||
|
||||
while (HashMapIterate(c->responseHeaders, &key, (void **) &val))
|
||||
{
|
||||
fprintf(fp, "%s: %s\n", key, val);
|
||||
}
|
||||
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
static FILE *
|
||||
DequeueConnection(HttpServer * server)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
if (!server)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&server->connQueueMutex);
|
||||
fp = QueuePop(server->connQueue);
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
HttpServer *
|
||||
HttpServerCreate(unsigned short port, unsigned int nThreads, unsigned int maxConnections,
|
||||
HttpHandler * requestHandler, void *handlerArgs)
|
||||
{
|
||||
HttpServer *server;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if (!requestHandler)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
server = Malloc(sizeof(HttpServer));
|
||||
if (!server)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
memset(server, 0, sizeof(HttpServer));
|
||||
|
||||
server->threadPool = ArrayCreate();
|
||||
if (!server->threadPool)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->connQueue = QueueCreate(maxConnections);
|
||||
if (!server->connQueue)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&server->connQueueMutex, NULL) != 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->sd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (server->sd < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (fcntl(server->sd, F_SETFL, O_NONBLOCK) == -1)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(int)) < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifdef SO_REUSEPORT
|
||||
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEPORT, &ENABLE, sizeof(int)) < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(&sa, 0, sizeof(struct sockaddr_in));
|
||||
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
if (bind(server->sd, (struct sockaddr *) & sa, sizeof(sa)) < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (listen(server->sd, maxConnections) < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->nThreads = nThreads;
|
||||
server->maxConnections = maxConnections;
|
||||
server->requestHandler = requestHandler;
|
||||
server->handlerArgs = handlerArgs;
|
||||
server->stop = 0;
|
||||
server->isRunning = 0;
|
||||
|
||||
return server;
|
||||
|
||||
error:
|
||||
if (server)
|
||||
{
|
||||
if (server->connQueue)
|
||||
{
|
||||
QueueFree(server->connQueue);
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&server->connQueueMutex);
|
||||
|
||||
if (server->threadPool)
|
||||
{
|
||||
ArrayFree(server->threadPool);
|
||||
}
|
||||
|
||||
if (server->sd)
|
||||
{
|
||||
close(server->sd);
|
||||
}
|
||||
|
||||
Free(server);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HttpServerFree(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
close(server->sd);
|
||||
QueueFree(server->connQueue);
|
||||
pthread_mutex_destroy(&server->connQueueMutex);
|
||||
ArrayFree(server->threadPool);
|
||||
Free(server);
|
||||
}
|
||||
|
||||
static void *
|
||||
HttpServerWorkerThread(void *args)
|
||||
{
|
||||
HttpServerWorkerThreadArgs *wArgs = (HttpServerWorkerThreadArgs *) args;
|
||||
HttpServer *server = wArgs->server;
|
||||
|
||||
while (!server->stop)
|
||||
{
|
||||
FILE *fp;
|
||||
HttpServerContext *context;
|
||||
|
||||
char *line = NULL;
|
||||
size_t lineSize = 0;
|
||||
ssize_t lineLen = 0;
|
||||
|
||||
char *requestMethodPtr;
|
||||
char *pathPtr;
|
||||
char *requestPath;
|
||||
char *requestProtocol;
|
||||
|
||||
HashMap *requestParams;
|
||||
ssize_t requestPathLen;
|
||||
|
||||
ssize_t i = 0;
|
||||
HttpRequestMethod requestMethod;
|
||||
|
||||
long firstRead;
|
||||
|
||||
fp = DequeueConnection(server);
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
/* Block for 1 millisecond before continuing so we don't
|
||||
* murder the CPU if the queue is empty. */
|
||||
UtilSleepMillis(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the first line of the request.
|
||||
*
|
||||
* Every once in a while, we're too fast for the client. When this
|
||||
* happens, UtilGetLine() sets errno to EAGAIN. If we get
|
||||
* EAGAIN, then clear the error on the stream and try again
|
||||
* after 1ms. This is typically more than enough time for the
|
||||
* client to send data. */
|
||||
firstRead = UtilServerTs();
|
||||
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) == -1
|
||||
&& errno == EAGAIN)
|
||||
{
|
||||
clearerr(fp);
|
||||
|
||||
/* If the server is stopped, or it's been a while, just
|
||||
* give up. */
|
||||
if (server->stop || (UtilServerTs() - firstRead) > 1000 * 30)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
UtilSleepMillis(5);
|
||||
}
|
||||
|
||||
if (lineLen == -1)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
requestMethodPtr = line;
|
||||
for (i = 0; i < lineLen; i++)
|
||||
{
|
||||
if (line[i] == ' ')
|
||||
{
|
||||
line[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == lineLen)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
requestMethod = HttpRequestMethodFromString(requestMethodPtr);
|
||||
if (requestMethod == HTTP_METHOD_UNKNOWN)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
pathPtr = line + i + 1;
|
||||
|
||||
for (i = 0; i < (line + lineLen) - pathPtr; i++)
|
||||
{
|
||||
if (pathPtr[i] == ' ')
|
||||
{
|
||||
pathPtr[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requestPathLen = i;
|
||||
requestPath = Malloc(((requestPathLen + 1) * sizeof(char)));
|
||||
strcpy(requestPath, pathPtr);
|
||||
|
||||
requestProtocol = &pathPtr[i + 1];
|
||||
line[lineLen - 2] = '\0'; /* Get rid of \r and \n */
|
||||
|
||||
if (strcmp(requestProtocol, "HTTP/1.1") != 0 && strcmp(requestProtocol, "HTTP/1.0") != 0)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
/* Find request params */
|
||||
for (i = 0; i < requestPathLen; i++)
|
||||
{
|
||||
if (requestPath[i] == '?')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requestPath[i] = '\0';
|
||||
requestParams = (i == requestPathLen) ? NULL : HttpParamDecode(requestPath + i + 1);
|
||||
|
||||
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
|
||||
if (!context)
|
||||
{
|
||||
goto internal_error;
|
||||
}
|
||||
|
||||
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
|
||||
{
|
||||
char *headerKey;
|
||||
char *headerValue;
|
||||
char *headerPtr;
|
||||
|
||||
if (strcmp(line, "\r\n") == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < lineLen; i++)
|
||||
{
|
||||
if (line[i] == ':')
|
||||
{
|
||||
line[i] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
line[i] = tolower((unsigned char) line[i]);
|
||||
}
|
||||
|
||||
headerKey = Malloc((i + 1) * sizeof(char));
|
||||
if (!headerKey)
|
||||
{
|
||||
goto internal_error;
|
||||
}
|
||||
|
||||
strcpy(headerKey, line);
|
||||
|
||||
headerPtr = line + i + 1;
|
||||
|
||||
while (isspace((unsigned char) *headerPtr))
|
||||
{
|
||||
headerPtr++;
|
||||
}
|
||||
|
||||
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
|
||||
{
|
||||
if (!isspace((unsigned char) line[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
line[i] = '\0';
|
||||
}
|
||||
|
||||
headerValue = Malloc(strlen(headerPtr) + 1);
|
||||
if (!headerValue)
|
||||
{
|
||||
goto internal_error;
|
||||
}
|
||||
|
||||
strcpy(headerValue, headerPtr);
|
||||
|
||||
HashMapSet(context->requestHeaders, headerKey, headerValue);
|
||||
Free(headerKey);
|
||||
}
|
||||
|
||||
server->requestHandler(context, server->handlerArgs);
|
||||
|
||||
HttpServerContextFree(context);
|
||||
fp = NULL; /* The above call will close this
|
||||
* FILE */
|
||||
goto finish;
|
||||
|
||||
internal_error:
|
||||
fprintf(fp, "HTTP/1.0 500 Internal Server Error\n");
|
||||
fprintf(fp, "Connection: close\n");
|
||||
goto finish;
|
||||
|
||||
bad_request:
|
||||
fprintf(fp, "HTTP/1.0 400 Bad Request\n");
|
||||
fprintf(fp, "Connection: close\n");
|
||||
goto finish;
|
||||
|
||||
finish:
|
||||
Free(line);
|
||||
if (fp)
|
||||
{
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *
|
||||
HttpServerEventThread(void *args)
|
||||
{
|
||||
HttpServer *server = (HttpServer *) args;
|
||||
struct pollfd pollFds[1];
|
||||
FILE *fp;
|
||||
size_t i;
|
||||
|
||||
server->isRunning = 1;
|
||||
server->stop = 0;
|
||||
|
||||
pollFds[0].fd = server->sd;
|
||||
pollFds[0].events = POLLIN;
|
||||
|
||||
for (i = 0; i < server->nThreads; i++)
|
||||
{
|
||||
HttpServerWorkerThreadArgs *workerThread = Malloc(sizeof(HttpServerWorkerThreadArgs));
|
||||
|
||||
if (!workerThread)
|
||||
{
|
||||
/* TODO: Make the event thread return an error to the main
|
||||
* thread */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
workerThread->server = server;
|
||||
workerThread->id = i;
|
||||
|
||||
if (pthread_create(&workerThread->thread, NULL, HttpServerWorkerThread, workerThread) != 0)
|
||||
{
|
||||
/* TODO: Make the event thread return an error to the main
|
||||
* thread */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ArrayAdd(server->threadPool, workerThread);
|
||||
}
|
||||
|
||||
while (!server->stop)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
int connFd;
|
||||
int pollResult;
|
||||
|
||||
|
||||
pollResult = poll(pollFds, 1, 500);
|
||||
|
||||
if (pollResult < 0)
|
||||
{
|
||||
/* The poll either timed out, or was interrupted. */
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&server->connQueueMutex);
|
||||
|
||||
/* Don't even accept connections if the queue is full. */
|
||||
if (!QueueFull(server->connQueue))
|
||||
{
|
||||
connFd = accept(server->sd, (struct sockaddr *) & addr, &addrLen);
|
||||
|
||||
if (connFd < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
fp = fdopen(connFd, "r+");
|
||||
if (!fp)
|
||||
{
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
close(connFd);
|
||||
continue;
|
||||
}
|
||||
|
||||
QueuePush(server->connQueue, fp);
|
||||
}
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
}
|
||||
|
||||
for (i = 0; i < server->nThreads; i++)
|
||||
{
|
||||
HttpServerWorkerThreadArgs *workerThread = ArrayGet(server->threadPool, i);
|
||||
|
||||
pthread_join(workerThread->thread, NULL);
|
||||
Free(workerThread);
|
||||
}
|
||||
|
||||
while ((fp = DequeueConnection(server)))
|
||||
{
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
server->isRunning = 0;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
HttpServerStart(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (server->isRunning)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pthread_create(&server->socketThread, NULL, HttpServerEventThread, server) != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
HttpServerJoin(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_join(server->socketThread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
HttpServerStop(HttpServer * server)
|
||||
{
|
||||
if (!server)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
server->stop = 1;
|
||||
}
|
1186
src/Json.c
1186
src/Json.c
File diff suppressed because it is too large
Load Diff
362
src/Log.c
362
src/Log.c
|
@ -1,362 +0,0 @@
|
|||
/*
|
||||
* 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 limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Log.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define LOG_TSBUFFER 64
|
||||
|
||||
struct LogConfig
|
||||
{
|
||||
int level;
|
||||
size_t indent;
|
||||
FILE *out;
|
||||
int flags;
|
||||
char *tsFmt;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
};
|
||||
|
||||
LogConfig *
|
||||
LogConfigCreate(void)
|
||||
{
|
||||
LogConfig *config;
|
||||
|
||||
config = Malloc(sizeof(LogConfig));
|
||||
|
||||
if (!config)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(config, 0, sizeof(LogConfig));
|
||||
|
||||
LogConfigLevelSet(config, LOG_INFO);
|
||||
LogConfigIndentSet(config, 0);
|
||||
LogConfigOutputSet(config, NULL); /* Will set to stdout */
|
||||
LogConfigFlagSet(config, LOG_FLAG_COLOR);
|
||||
LogConfigTimeStampFormatSet(config, "%y-%m-%d %H:%M:%S");
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigFlagClear(LogConfig * config, int flags)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
config->flags &= ~flags;
|
||||
}
|
||||
|
||||
static int
|
||||
LogConfigFlagGet(LogConfig * config, int flags)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return config->flags & flags;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigFlagSet(LogConfig * config, int flags)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
config->flags |= flags;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigFree(LogConfig * config)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fclose(config->out);
|
||||
Free(config);
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigIndent(LogConfig * config)
|
||||
{
|
||||
if (config)
|
||||
{
|
||||
config->indent += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigIndentSet(LogConfig * config, size_t indent)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
config->indent = indent;
|
||||
}
|
||||
|
||||
int
|
||||
LogConfigLevelGet(LogConfig * config)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return config->level;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigLevelSet(LogConfig * config, int level)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_ERR:
|
||||
case LOG_WARNING:
|
||||
case LOG_INFO:
|
||||
case LOG_DEBUG:
|
||||
config->level = level;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigOutputSet(LogConfig * config, FILE * out)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (out)
|
||||
{
|
||||
config->out = out;
|
||||
}
|
||||
else
|
||||
{
|
||||
config->out = stdout;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigTimeStampFormatSet(LogConfig * config, char *tsFmt)
|
||||
{
|
||||
if (config)
|
||||
{
|
||||
config->tsFmt = tsFmt;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigUnindent(LogConfig * config)
|
||||
{
|
||||
if (config && config->indent >= 2)
|
||||
{
|
||||
config->indent -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Log(LogConfig * config, int level, const char *msg,...)
|
||||
{
|
||||
size_t i;
|
||||
int doColor;
|
||||
char indicator;
|
||||
va_list argp;
|
||||
|
||||
/*
|
||||
* Only proceed if we have a config and its log level is set to a
|
||||
* value that permits us to log. This is as close as we can get
|
||||
* to a no-op function if we aren't logging anything, without doing
|
||||
* some crazy macro magic.
|
||||
*/
|
||||
if (!config || level > config->level)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Misconfiguration */
|
||||
if (!config->out)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&config->lock);
|
||||
|
||||
if (LogConfigFlagGet(config, LOG_FLAG_SYSLOG))
|
||||
{
|
||||
/* No further print logic is needed; syslog will handle it all
|
||||
* for us. */
|
||||
va_start(argp, msg);
|
||||
vsyslog(level, msg, argp);
|
||||
va_end(argp);
|
||||
pthread_mutex_unlock(&config->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
|
||||
&& isatty(fileno(config->out));
|
||||
|
||||
if (doColor)
|
||||
{
|
||||
char *ansi;
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_EMERG:
|
||||
case LOG_ALERT:
|
||||
case LOG_CRIT:
|
||||
case LOG_ERR:
|
||||
/* Bold Red */
|
||||
ansi = "\033[1;31m";
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
/* Bold Yellow */
|
||||
ansi = "\033[1;33m";
|
||||
break;
|
||||
case LOG_NOTICE:
|
||||
/* Bold Magenta */
|
||||
ansi = "\033[1;35m";
|
||||
break;
|
||||
case LOG_INFO:
|
||||
/* Bold Green */
|
||||
ansi = "\033[1;32m";
|
||||
break;
|
||||
case LOG_DEBUG:
|
||||
/* Bold Blue */
|
||||
ansi = "\033[1;34m";
|
||||
break;
|
||||
default:
|
||||
ansi = "";
|
||||
break;
|
||||
}
|
||||
|
||||
fputs(ansi, config->out);
|
||||
}
|
||||
|
||||
fputc('[', config->out);
|
||||
|
||||
if (config->tsFmt)
|
||||
{
|
||||
time_t timer = time(NULL);
|
||||
struct tm *timeInfo = localtime(&timer);
|
||||
char tsBuffer[LOG_TSBUFFER];
|
||||
|
||||
int tsLength = strftime(tsBuffer, LOG_TSBUFFER, config->tsFmt,
|
||||
timeInfo);
|
||||
|
||||
if (tsLength)
|
||||
{
|
||||
fputs(tsBuffer, config->out);
|
||||
if (!isspace((unsigned char) tsBuffer[tsLength - 1]))
|
||||
{
|
||||
fputc(' ', config->out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_EMERG:
|
||||
indicator = '#';
|
||||
break;
|
||||
case LOG_ALERT:
|
||||
indicator = '@';
|
||||
break;
|
||||
case LOG_CRIT:
|
||||
indicator = 'X';
|
||||
break;
|
||||
case LOG_ERR:
|
||||
indicator = 'x';
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
indicator = '!';
|
||||
break;
|
||||
case LOG_NOTICE:
|
||||
indicator = '~';
|
||||
break;
|
||||
case LOG_INFO:
|
||||
indicator = '>';
|
||||
break;
|
||||
case LOG_DEBUG:
|
||||
indicator = '*';
|
||||
break;
|
||||
default:
|
||||
indicator = '?';
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(config->out, "%c]", indicator);
|
||||
|
||||
if (doColor)
|
||||
{
|
||||
/* ANSI Reset */
|
||||
fputs("\033[0m", config->out);
|
||||
}
|
||||
|
||||
fputc(' ', config->out);
|
||||
for (i = 0; i < config->indent; i++)
|
||||
{
|
||||
fputc(' ', config->out);
|
||||
}
|
||||
|
||||
va_start(argp, msg);
|
||||
vfprintf(config->out, msg, argp);
|
||||
fputc('\n', config->out);
|
||||
va_end(argp);
|
||||
|
||||
/* If we are debugging, there might be something that's going to
|
||||
* segfault the program coming up, so flush the output stream
|
||||
* immediately. */
|
||||
if (config->level == LOG_DEBUG)
|
||||
{
|
||||
fflush(config->out);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&config->lock);
|
||||
}
|
|
@ -0,0 +1,648 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#include <Cytoplasm/Args.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HttpServer.h>
|
||||
#include <Cytoplasm/Db.h>
|
||||
#include <Cytoplasm/Cron.h>
|
||||
#include <Cytoplasm/Util.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <Telodendria.h>
|
||||
#include <Matrix.h>
|
||||
#include <User.h>
|
||||
#include <RegToken.h>
|
||||
#include <Routes.h>
|
||||
#include <Uia.h>
|
||||
#include <Config.h>
|
||||
|
||||
|
||||
static Array *httpServers;
|
||||
static volatile int restart;
|
||||
|
||||
static void
|
||||
SignalHandler(int signal)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
switch (signal)
|
||||
{
|
||||
case SIGPIPE:
|
||||
return;
|
||||
case SIGUSR1:
|
||||
restart = 1;
|
||||
/* Fall through */
|
||||
case SIGTERM:
|
||||
case SIGINT:
|
||||
if (!httpServers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ArraySize(httpServers); i++)
|
||||
{
|
||||
HttpServer *server = ArrayGet(httpServers, i);
|
||||
|
||||
HttpServerStop(server);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum ArgFlag
|
||||
{
|
||||
ARG_VERSION = (1 << 0),
|
||||
ARG_VERBOSE = (1 << 2)
|
||||
} ArgFlag;
|
||||
|
||||
int
|
||||
Main(Array * args)
|
||||
{
|
||||
int exit;
|
||||
|
||||
/* Arg parsing */
|
||||
ArgParseState arg;
|
||||
int opt;
|
||||
int flags;
|
||||
char *dbPath;
|
||||
|
||||
/* Program configuration */
|
||||
Config tConfig;
|
||||
Stream *logFile;
|
||||
Stream *pidFile;
|
||||
|
||||
char *pidPath;
|
||||
|
||||
/* User validation */
|
||||
struct passwd *userInfo;
|
||||
struct group *groupInfo;
|
||||
|
||||
/* HTTP server management */
|
||||
size_t i;
|
||||
HttpServer *server;
|
||||
|
||||
/* Signal handling */
|
||||
struct sigaction sigAction;
|
||||
|
||||
MatrixHttpHandlerArgs matrixArgs;
|
||||
Cron *cron;
|
||||
|
||||
char startDir[PATH_MAX];
|
||||
|
||||
char *token;
|
||||
|
||||
start:
|
||||
/* Global variables */
|
||||
httpServers = NULL;
|
||||
restart = 0;
|
||||
|
||||
/* Local variables */
|
||||
exit = EXIT_SUCCESS;
|
||||
flags = 0;
|
||||
dbPath = NULL;
|
||||
logFile = NULL;
|
||||
pidFile = NULL;
|
||||
pidPath = NULL;
|
||||
userInfo = NULL;
|
||||
groupInfo = NULL;
|
||||
cron = NULL;
|
||||
|
||||
token = NULL;
|
||||
|
||||
memset(&matrixArgs, 0, sizeof(matrixArgs));
|
||||
|
||||
if (!LogConfigGlobal())
|
||||
{
|
||||
printf("Fatal error: unable to allocate memory for logger.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
TelodendriaPrintHeader();
|
||||
|
||||
ArgParseStateInit(&arg);
|
||||
while ((opt = ArgParse(&arg, args, "d:Vv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 'd':
|
||||
dbPath = arg.optArg;
|
||||
break;
|
||||
case 'V':
|
||||
flags |= ARG_VERSION;
|
||||
break;
|
||||
case 'v':
|
||||
flags |= ARG_VERBOSE;
|
||||
break;
|
||||
case '?':
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & ARG_VERBOSE)
|
||||
{
|
||||
LogConfigLevelSet(LogConfigGlobal(), LOG_DEBUG);
|
||||
MemoryHook(TelodendriaMemoryHook, (void *) ARG_VERBOSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryHook(TelodendriaMemoryHook, NULL);
|
||||
}
|
||||
|
||||
if (flags & ARG_VERSION)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!dbPath)
|
||||
{
|
||||
Log(LOG_ERR, "No database directory specified.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!getcwd(startDir, PATH_MAX))
|
||||
{
|
||||
Log(LOG_ERR, "Unable to determine current working directory.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (chdir(dbPath) != 0)
|
||||
{
|
||||
Log(LOG_ERR, "Unable to change into data directory: %s.", strerror(errno));
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_DEBUG, "Changed working directory to: %s", dbPath);
|
||||
}
|
||||
|
||||
matrixArgs.db = DbOpen(".", 0);
|
||||
if (!matrixArgs.db)
|
||||
{
|
||||
Log(LOG_ERR, "Unable to open data directory as a database.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_DEBUG, "Opened database.");
|
||||
}
|
||||
|
||||
if (!ConfigExists(matrixArgs.db))
|
||||
{
|
||||
RegTokenInfo *info;
|
||||
|
||||
Log(LOG_NOTICE, "No configuration exists in the opened database.");
|
||||
Log(LOG_NOTICE, "A default configuration will be created, and a");
|
||||
Log(LOG_NOTICE, "new single-use registration token that grants all");
|
||||
Log(LOG_NOTICE, "privileges will be created so an admin user can");
|
||||
Log(LOG_NOTICE, "be created to configure this database using the");
|
||||
Log(LOG_NOTICE, "administrator API.");
|
||||
|
||||
if (!ConfigCreateDefault(matrixArgs.db))
|
||||
{
|
||||
Log(LOG_ERR, "Unable to create default configuration.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
token = StrRandom(32);
|
||||
info = RegTokenCreate(matrixArgs.db, token, NULL, /* expires */ 0, /* uses */ 1, USER_ALL);
|
||||
if (!info)
|
||||
{
|
||||
Free(token);
|
||||
Log(LOG_ERR, "Unable to create admin registration token.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
|
||||
/* Don't free token, because we need to print it when logging
|
||||
* is set up. */
|
||||
}
|
||||
|
||||
Log(LOG_NOTICE, "Loading configuration...");
|
||||
|
||||
ConfigLock(matrixArgs.db, &tConfig);
|
||||
if (!tConfig.ok)
|
||||
{
|
||||
Log(LOG_ERR, tConfig.err);
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!tConfig.log.timestampFormat || !StrEquals(tConfig.log.timestampFormat, "default"))
|
||||
{
|
||||
LogConfigTimeStampFormatSet(LogConfigGlobal(), tConfig.log.timestampFormat);
|
||||
}
|
||||
|
||||
if (tConfig.log.color)
|
||||
{
|
||||
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_COLOR);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogConfigFlagClear(LogConfigGlobal(), LOG_FLAG_COLOR);
|
||||
}
|
||||
|
||||
LogConfigLevelSet(
|
||||
LogConfigGlobal(),
|
||||
flags & ARG_VERBOSE ?
|
||||
LOG_DEBUG :
|
||||
ConfigLogLevelToSyslog(tConfig.log.level));
|
||||
|
||||
if (tConfig.log.output == CONFIG_LOG_OUTPUT_FILE)
|
||||
{
|
||||
logFile = StreamOpen("telodendria.log", "a");
|
||||
|
||||
if (!logFile)
|
||||
{
|
||||
Log(LOG_ERR, "Unable to open log file for appending.");
|
||||
exit = EXIT_FAILURE;
|
||||
tConfig.log.output = CONFIG_LOG_OUTPUT_STDOUT;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
Log(LOG_INFO, "Logging to the log file. Check there for all future messages.");
|
||||
LogConfigOutputSet(LogConfigGlobal(), logFile);
|
||||
}
|
||||
else if (tConfig.log.output == CONFIG_LOG_OUTPUT_STDOUT)
|
||||
{
|
||||
Log(LOG_DEBUG, "Already logging to standard output.");
|
||||
}
|
||||
else if (tConfig.log.output == CONFIG_LOG_OUTPUT_SYSLOG)
|
||||
{
|
||||
Log(LOG_INFO, "Logging to the syslog. Check there for all future messages.");
|
||||
LogConfigFlagSet(LogConfigGlobal(), LOG_FLAG_SYSLOG);
|
||||
|
||||
openlog("telodendria", LOG_PID | LOG_NDELAY, LOG_DAEMON);
|
||||
/* Always log everything, because the Log API will control what
|
||||
* messages get passed to the syslog */
|
||||
setlogmask(LOG_UPTO(LOG_DEBUG));
|
||||
}
|
||||
|
||||
/* If a token was created with a default config, print it to the
|
||||
* log */
|
||||
if (token)
|
||||
{
|
||||
Log(LOG_NOTICE, "Admin Registration token: %s", token);
|
||||
Free(token);
|
||||
}
|
||||
|
||||
if (tConfig.pid)
|
||||
{
|
||||
pidFile = StreamOpen(tConfig.pid, "w+");
|
||||
if (!pidFile)
|
||||
{
|
||||
char *msg = "Couldn't lock PID file at '%s'";
|
||||
Log(LOG_ERR, msg, tConfig.pid);
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
pidPath = StrDuplicate(tConfig.pid);
|
||||
StreamPrintf(pidFile, "%ld", (long) getpid());
|
||||
StreamClose(pidFile);
|
||||
}
|
||||
|
||||
Log(LOG_DEBUG, "Configuration:");
|
||||
LogConfigIndent(LogConfigGlobal());
|
||||
Log(LOG_DEBUG, "Server Name: %s", tConfig.serverName);
|
||||
Log(LOG_DEBUG, "Base URL: %s", tConfig.baseUrl);
|
||||
Log(LOG_DEBUG, "Identity Server: %s", tConfig.identityServer);
|
||||
Log(LOG_DEBUG, "Run As: %s:%s", tConfig.runAs.uid, tConfig.runAs.gid);
|
||||
Log(LOG_DEBUG, "Max Cache: %ld", tConfig.maxCache);
|
||||
Log(LOG_DEBUG, "Registration: %s", tConfig.registration ? "true" : "false");
|
||||
Log(LOG_DEBUG, "Federation: %s", tConfig.federation ? "true" : "false");
|
||||
LogConfigUnindent(LogConfigGlobal());
|
||||
|
||||
httpServers = ArrayCreate();
|
||||
if (!httpServers)
|
||||
{
|
||||
Log(LOG_ERR, "Error setting up HTTP server.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Bind servers before possibly dropping permissions. */
|
||||
for (i = 0; i < ArraySize(tConfig.listen); i++)
|
||||
{
|
||||
ConfigListener *serverCfg = ArrayGet(tConfig.listen, i);
|
||||
|
||||
HttpServerConfig args;
|
||||
|
||||
args.port = serverCfg->port;
|
||||
args.threads = serverCfg->maxConnections;
|
||||
args.maxConnections = serverCfg->maxConnections;
|
||||
args.tlsCert = serverCfg->tls.cert;
|
||||
args.tlsKey = serverCfg->tls.key;
|
||||
args.flags = args.tlsCert && args.tlsKey ? HTTP_FLAG_TLS : HTTP_FLAG_NONE;
|
||||
|
||||
Log(LOG_DEBUG, "HTTP listener: %lu", i);
|
||||
LogConfigIndent(LogConfigGlobal());
|
||||
Log(LOG_DEBUG, "Port: %hu", serverCfg->port);
|
||||
Log(LOG_DEBUG, "Threads: %u", serverCfg->threads);
|
||||
Log(LOG_DEBUG, "Max Connections: %u", serverCfg->maxConnections);
|
||||
Log(LOG_DEBUG, "Flags: %d", args.flags);
|
||||
Log(LOG_DEBUG, "TLS Cert: %s", serverCfg->tls.cert);
|
||||
Log(LOG_DEBUG, "TLS Key: %s", serverCfg->tls.key);
|
||||
LogConfigUnindent(LogConfigGlobal());
|
||||
|
||||
|
||||
args.handler = MatrixHttpHandler;
|
||||
args.handlerArgs = &matrixArgs;
|
||||
|
||||
if (args.flags & HTTP_FLAG_TLS)
|
||||
{
|
||||
if (!UtilLastModified(serverCfg->tls.cert))
|
||||
{
|
||||
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.cert);
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (UtilLastModified(serverCfg->tls.key))
|
||||
{
|
||||
Log(LOG_ERR, "%s: %s", strerror(errno), serverCfg->tls.key);
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
server = HttpServerCreate(&args);
|
||||
if (!server)
|
||||
{
|
||||
Log(LOG_ERR, "Unable to create HTTP server on port %d: %s",
|
||||
serverCfg->port, strerror(errno));
|
||||
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
ArrayAdd(httpServers, server);
|
||||
}
|
||||
|
||||
if (!ArraySize(httpServers))
|
||||
{
|
||||
Log(LOG_ERR, "No valid HTTP listeners specified in the configuration.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
Log(LOG_DEBUG, "Running as uid:gid: %d:%d.", getuid(), getgid());
|
||||
|
||||
if (tConfig.runAs.uid && tConfig.runAs.gid)
|
||||
{
|
||||
userInfo = getpwnam(tConfig.runAs.uid);
|
||||
groupInfo = getgrnam(tConfig.runAs.gid);
|
||||
|
||||
if (!userInfo || !groupInfo)
|
||||
{
|
||||
Log(LOG_ERR, "Unable to locate the user/group specified in the configuration.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_DEBUG, "Found user/group information using getpwnam() and getgrnam().");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_DEBUG, "No user/group info specified in the config.");
|
||||
}
|
||||
|
||||
if (getuid() == 0)
|
||||
{
|
||||
if (userInfo && groupInfo)
|
||||
{
|
||||
if (setgid(groupInfo->gr_gid) != 0 || setuid(userInfo->pw_uid) != 0)
|
||||
{
|
||||
Log(LOG_ERR, "Unable to set process uid/gid.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_DEBUG, "Set uid/gid to %s:%s.", tConfig.runAs.uid, tConfig.runAs.gid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_WARNING, "We are running as root, and we are not dropping to another user");
|
||||
Log(LOG_WARNING, "because none was specified in the configuration file.");
|
||||
Log(LOG_WARNING, "This is probably a security issue.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tConfig.runAs.uid && tConfig.runAs.gid)
|
||||
{
|
||||
if (getuid() != userInfo->pw_uid || getgid() != groupInfo->gr_gid)
|
||||
{
|
||||
Log(LOG_WARNING, "Not running as the uid/gid specified in the configuration.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_DEBUG, "Running as the uid/gid specified in the configuration.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!tConfig.maxCache)
|
||||
{
|
||||
Log(LOG_WARNING, "Database caching is disabled.");
|
||||
Log(LOG_WARNING, "If this is not what you intended, check the config file");
|
||||
Log(LOG_WARNING, "and ensure that maxCache is a valid number of bytes.");
|
||||
}
|
||||
|
||||
DbMaxCacheSet(matrixArgs.db, tConfig.maxCache);
|
||||
|
||||
ConfigUnlock(&tConfig);
|
||||
|
||||
cron = CronCreate(60 * 1000); /* 1-minute tick */
|
||||
if (!cron)
|
||||
{
|
||||
Log(LOG_ERR, "Unable to set up job scheduler.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
Log(LOG_DEBUG, "Registering jobs...");
|
||||
|
||||
CronEvery(cron, 30 * 60 * 1000, (JobFunc *) UiaCleanup, &matrixArgs);
|
||||
|
||||
Log(LOG_NOTICE, "Starting job scheduler...");
|
||||
CronStart(cron);
|
||||
|
||||
Log(LOG_NOTICE, "Building routing tree...");
|
||||
matrixArgs.router = RouterBuild();
|
||||
if (!matrixArgs.router)
|
||||
{
|
||||
Log(LOG_ERR, "Unable to build routing tree.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
Log(LOG_NOTICE, "Starting server...");
|
||||
|
||||
for (i = 0; i < ArraySize(httpServers); i++)
|
||||
{
|
||||
HttpServerConfig *serverCfg;
|
||||
|
||||
server = ArrayGet(httpServers, i);
|
||||
serverCfg = HttpServerConfigGet(server);
|
||||
|
||||
if (!HttpServerStart(server))
|
||||
{
|
||||
Log(LOG_ERR, "Unable to start HTTP server %lu on port %hu.", i, serverCfg->port);
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_DEBUG, "Started HTTP server %lu.", i);
|
||||
Log(LOG_INFO, "Listening on port: %hu", serverCfg->port);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sigAction.sa_handler = SignalHandler;
|
||||
sigfillset(&sigAction.sa_mask);
|
||||
sigAction.sa_flags = SA_RESTART;
|
||||
|
||||
#define SIGACTION(sig, act, oact) \
|
||||
if (sigaction(sig, act, oact) < 0) \
|
||||
{ \
|
||||
Log(LOG_ERR, "Unable to install signal handler: %s", #sig); \
|
||||
exit = EXIT_FAILURE; \
|
||||
goto finish; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
Log(LOG_DEBUG, "Installed signal handler: %s", #sig); \
|
||||
}
|
||||
|
||||
SIGACTION(SIGINT, &sigAction, NULL);
|
||||
SIGACTION(SIGTERM, &sigAction, NULL);
|
||||
SIGACTION(SIGPIPE, &sigAction, NULL);
|
||||
SIGACTION(SIGUSR1, &sigAction, NULL);
|
||||
|
||||
#undef SIGACTION
|
||||
|
||||
/* Block this thread until the servers are terminated by a signal
|
||||
* handler */
|
||||
for (i = 0; i < ArraySize(httpServers); i++)
|
||||
{
|
||||
server = ArrayGet(httpServers, i);
|
||||
HttpServerJoin(server);
|
||||
Log(LOG_DEBUG, "Joined HTTP server %lu.", i);
|
||||
}
|
||||
|
||||
finish:
|
||||
Log(LOG_NOTICE, "Shutting down...");
|
||||
if (httpServers)
|
||||
{
|
||||
for (i = 0; i < ArraySize(httpServers); i++)
|
||||
{
|
||||
Log(LOG_DEBUG, "Freeing HTTP server %lu...", i);
|
||||
server = ArrayGet(httpServers, i);
|
||||
HttpServerStop(server);
|
||||
HttpServerFree(server);
|
||||
Log(LOG_DEBUG, "Freed HTTP server %lu.", i);
|
||||
}
|
||||
ArrayFree(httpServers);
|
||||
httpServers = NULL;
|
||||
|
||||
Log(LOG_DEBUG, "Freed HTTP servers array.");
|
||||
}
|
||||
|
||||
if (cron)
|
||||
{
|
||||
Log(LOG_DEBUG, "Waiting on background jobs...");
|
||||
CronStop(cron);
|
||||
CronFree(cron);
|
||||
Log(LOG_DEBUG, "Stopped and freed job scheduler.");
|
||||
}
|
||||
|
||||
ConfigUnlock(&tConfig);
|
||||
Log(LOG_DEBUG, "Unlocked configuration.");
|
||||
|
||||
DbClose(matrixArgs.db);
|
||||
Log(LOG_DEBUG, "Closed database.");
|
||||
|
||||
HttpRouterFree(matrixArgs.router);
|
||||
Log(LOG_DEBUG, "Freed routing tree.");
|
||||
|
||||
if (pidPath)
|
||||
{
|
||||
remove(pidPath);
|
||||
Free(pidPath);
|
||||
}
|
||||
|
||||
/*
|
||||
* Uninstall the memory hook because it uses the Log
|
||||
* API, whose configuration is being freed now, so it
|
||||
* won't work anymore.
|
||||
*/
|
||||
MemoryHook(NULL, NULL);
|
||||
|
||||
StreamClose(logFile);
|
||||
|
||||
if (restart)
|
||||
{
|
||||
/*
|
||||
* Change back into starting directory so initial chdir()
|
||||
* call works.
|
||||
*/
|
||||
if (chdir(startDir) != 0)
|
||||
{
|
||||
/* TODO: Seems problematic, what do we do? */
|
||||
}
|
||||
goto start;
|
||||
}
|
||||
|
||||
return exit;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue