forked from Telodendria/Telodendria
Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
95342f7ad1 |
160 changed files with 10036 additions and 13038 deletions
1
.cvsignore
Normal file
1
.cvsignore
Normal file
|
@ -0,0 +1 @@
|
|||
build
|
2
.exrc
2
.exrc
|
@ -1,2 +0,0 @@
|
|||
set tabstop=4
|
||||
set expandtab
|
|
@ -1,49 +0,0 @@
|
|||
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.
|
|
@ -1,7 +0,0 @@
|
|||
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.
|
|
@ -1,18 +0,0 @@
|
|||
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
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
---
|
||||
|
||||
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.
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
name: Compile Telodendria
|
||||
run-name: Compile Telodendria on ${{ forgejo.actor }}
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'ma*'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
"Compile Telodendria":
|
||||
strategy:
|
||||
matrix:
|
||||
os: [alpine]
|
||||
arch: [aarch64]
|
||||
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Configure Telodendria
|
||||
run: ./configure
|
||||
- name: Configure & Build Cytoplasm
|
||||
run: make cytoplasm
|
||||
- name: Build Telodendria
|
||||
run: make
|
|
@ -1,44 +0,0 @@
|
|||
name: Release Telodendria
|
||||
run-name: Release Telodendria on ${{ forgejo.actor }}
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
"Release Telodendria":
|
||||
strategy:
|
||||
matrix:
|
||||
os: [alpine]
|
||||
arch: [aarch64]
|
||||
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Archive submodules
|
||||
run: git submodule foreach --recursive 'git archive --format tar --prefix=$displaypath/ -o submodule.tar HEAD'
|
||||
- name: Archive repository
|
||||
run: git archive --format tar -o release.tar HEAD
|
||||
- name: Produce release archive
|
||||
run: |
|
||||
TOPDIR=$(pwd) git submodule --quiet foreach --recursive 'cd $TOPDIR; tar --concatenate --file=release.tar $displaypath/submodule.tar; rm -fv $displaypath/submodule.tar'
|
||||
gzip release.tar
|
||||
mkdir release
|
||||
mv release.tar.gz release/Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||
path: release/Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||
- name: Publish release
|
||||
uses: actions/forgejo-release@v2
|
||||
with:
|
||||
tag: $GITHUB_REF_NAME
|
||||
title: "Telodendria $GITHUB_REF_NAME"
|
||||
release-dir: release/
|
||||
release-notes: "docs/CHANGELOG.md"
|
||||
direction: upload
|
||||
prerelease: true
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,2 +0,0 @@
|
|||
liberapay: "Telodendria"
|
||||
custom: "https://donate.stripe.com/8wM29AfF5bRJc48eUU"
|
18
.gitignore
vendored
18
.gitignore
vendored
|
@ -1,18 +0,0 @@
|
|||
# Telodendria .gitignore
|
||||
|
||||
build
|
||||
out
|
||||
data
|
||||
Makefile
|
||||
|
||||
*-leaked.txt
|
||||
.env
|
||||
*.patch
|
||||
*.orig
|
||||
*.log
|
||||
vgcore.*
|
||||
*.core
|
||||
contrib/.vagrant
|
||||
src/Schema
|
||||
src/include/Schema
|
||||
man/mandoc.db
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "Cytoplasm"]
|
||||
path = Cytoplasm
|
||||
url = https://git.telodendria.io/Telodendria/Cytoplasm.git
|
8
.idea/Telodendria.iml
Normal file
8
.idea/Telodendria.iml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="CPP_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Telodendria.iml" filepath="$PROJECT_DIR$/.idea/Telodendria.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
52
.idea/workspace.xml
Normal file
52
.idea/workspace.xml
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeSettings">
|
||||
<configurations>
|
||||
<configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" />
|
||||
</configurations>
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="fb1df1f2-2a47-495d-89b9-277de6049bd1" name="Changes" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ClangdSettings">
|
||||
<option name="formatViaClangd" value="false" />
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProjectId" id="2CD2pGJ1okjE4N8lOPrGKmwbXGw" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||
"WebServerToolWindowFactoryState": "false",
|
||||
"cf.first.check.clang-format": "false",
|
||||
"cidr.known.project.marker": "true"
|
||||
}
|
||||
}</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="fb1df1f2-2a47-495d-89b9-277de6049bd1" name="Changes" comment="" />
|
||||
<created>1658323016759</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1658323016759</updated>
|
||||
<workItem from="1658323018463" duration="25597000" />
|
||||
<workItem from="1658450495358" duration="6933000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,16 +0,0 @@
|
|||
N: Jordan Bancino
|
||||
E: jordan@bancino.net
|
||||
M: @jordan:bancino.net
|
||||
W: https://bancino.net
|
||||
D: Project Lead
|
||||
L: United States
|
||||
|
||||
N: LDA
|
||||
E: marie@doskel.net
|
||||
E: ldasta@tedomum.fr
|
||||
E: lda@freetards.xyz
|
||||
M: @lda:a.freetards.xyz
|
||||
M: @lda:pain.agency
|
||||
M: @fourier:ari.lt
|
||||
D: Developer
|
||||
L: France
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 4f316ff7b3a955b831ca4aefb8679ddf3396a7d0
|
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
|
136
README.md
136
README.md
|
@ -1,135 +1,7 @@
|
|||
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
|
||||
# [Telodendria](https://telodendria.io)
|
||||
|
||||
**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.
|
||||
**Telodendria** is an open source Matrix homeserver implementation written from scratch in ANSI C and designed to be lightweight and simple, yet functional.
|
||||
|
||||
!!!! **Note:** Telodendria still in development. See **Status** below.
|
||||
**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).
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
|
27
README.txt
Normal file
27
README.txt
Normal file
|
@ -0,0 +1,27 @@
|
|||
=======================================================
|
||||
|_ _|__| | ___ __| | ___ _ __ __| |_ __(_) __ _
|
||||
| |/ _ \ |/ _ \ / _` |/ _ \ '_ \ / _` | '__| |/ _` |
|
||||
| | __/ | (_) | (_| | __/ | | | (_| | | | | (_| |
|
||||
|_|\___|_|\___/ \__,_|\___|_| |_|\__,_|_| |_|\__,_|
|
||||
=======================================================
|
||||
Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
|
||||
This is the source code for Telodendria, a Matrix homeserver written
|
||||
in C. All of the documentation is available as man pages in the
|
||||
man/ directory, or online at https://telodendria.io
|
||||
|
||||
If information is missing from the documentation, please feel free
|
||||
to reach out to #telodendria-general:bancino.net on Matrix.
|
||||
|
||||
This file documents the directory structure of the source code
|
||||
repository.
|
||||
|
||||
Telodendria/
|
||||
contrib/ - Supplemental files, such as example configs.
|
||||
man/ - The official documentation as man pages.
|
||||
site/ - The official website.
|
||||
src/ - The C source code for Telodendria.
|
||||
include/ - Header files for the source code.
|
||||
Routes/ - Where Matrix API endpoints are implemented
|
||||
tests/ - Unit and integration tests will eventually go here.
|
||||
tools/ - Development environment and tools.
|
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_CONFIG_H",
|
||||
"header": "Schema\/Config.h",
|
||||
"include": [ "Cytoplasm\/Db.h", "Cytoplasm/HttpServer.h" ],
|
||||
|
||||
"types": {
|
||||
"ConfigTls": {
|
||||
"fields": {
|
||||
"cert": { "type": "string", "required": true },
|
||||
"key": { "type": "string", "required": true }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
|
||||
"ConfigListener": {
|
||||
"fields": {
|
||||
"port": { "type": "integer", "required": true },
|
||||
"threads": { "type": "integer", "required": false },
|
||||
"maxConnections": { "type": "integer", "required": false },
|
||||
"tls": { "type": "ConfigTls", "required": false }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"ConfigRunAs": {
|
||||
"fields": {
|
||||
"uid": { "type": "string", "required": false },
|
||||
"gid": { "type": "string", "required": true }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"ConfigLogOutput": {
|
||||
"fields": {
|
||||
"stdout": { "name": "CONFIG_LOG_OUTPUT_STDOUT" },
|
||||
"file": { "name": "CONFIG_LOG_OUTPUT_FILE" },
|
||||
"syslog": { "name": "CONFIG_LOG_OUTPUT_SYSLOG" }
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"ConfigLogLevel": {
|
||||
"fields": {
|
||||
"message": { "name": "CONFIG_LOG_LEVEL_MESSAGE" },
|
||||
"debug": { "name": "CONFIG_LOG_LEVEL_DEBUG" },
|
||||
"notice": { "name": "CONFIG_LOG_LEVEL_NOTICE" },
|
||||
"warning": { "name": "CONFIG_LOG_LEVEL_WARNING" },
|
||||
"error": { "name": "CONFIG_LOG_LEVEL_ERROR" }
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"ConfigLogConfig": {
|
||||
"fields": {
|
||||
"output": { "type": "ConfigLogOutput", "required": true },
|
||||
"level": { "type": "ConfigLogLevel", "required": false },
|
||||
"timestampFormat":{ "type": "string", "required": false },
|
||||
"color": { "type": "boolean", "required": false }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
|
||||
"Db *": { "type": "extern" },
|
||||
"DbRef *": { "type": "extern" },
|
||||
"char *": { "type": "extern" },
|
||||
|
||||
"Config": {
|
||||
"fields": {
|
||||
"db": { "type": "Db *", "ignore": true },
|
||||
"ref": { "type": "DbRef *", "ignore": true },
|
||||
|
||||
"ok": { "type": "boolean", "ignore": true },
|
||||
"err": { "type": "char *", "ignore": true },
|
||||
|
||||
"listen": { "type": "[ConfigListener]", "required": true },
|
||||
"runAs": { "type": "ConfigRunAs", "required": false },
|
||||
"log": { "type": "ConfigLogConfig", "required": true },
|
||||
|
||||
"serverName": { "type": "string", "required": true },
|
||||
"baseUrl": { "type": "string", "required": false },
|
||||
"identityServer": { "type": "string", "required": false },
|
||||
"pid": { "type": "string", "required": false },
|
||||
|
||||
"maxCache": { "type": "integer", "required": false },
|
||||
|
||||
"federation": { "type": "boolean", "required": true },
|
||||
"registration": { "type": "boolean", "required": true }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"header": "Schema\/UserDirectoryRequest.h",
|
||||
"types": {
|
||||
"UserDirectoryRequest": {
|
||||
"fields": {
|
||||
"search_term": { "type": "string" },
|
||||
"limit": { "type": "integer" }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_USERDIRECTORYREQUEST_H"
|
||||
}
|
141
TODO.txt
Normal file
141
TODO.txt
Normal file
|
@ -0,0 +1,141 @@
|
|||
Telodendria To-Do List
|
||||
|
||||
Key:
|
||||
|
||||
[ ] Not Started
|
||||
[x] Done
|
||||
[~] In Progress
|
||||
[!] Won't Fix
|
||||
|
||||
Phase 1: Getting off the ground
|
||||
|
||||
[x] Name this project
|
||||
[x] Set up a CVS repository
|
||||
[x] Make CVS repository public
|
||||
[x] Write a build script
|
||||
[x] Write a coding style guide
|
||||
[x] Add a license
|
||||
[x] Add support and issue reporting guide
|
||||
[x] Add table of contents to website
|
||||
|
||||
Phase 2: Building a foundation
|
||||
|
||||
[x] Implement an array
|
||||
[x] Implement a logging facility
|
||||
[x] Implement a hash map
|
||||
[x] Allow custom hash functions for each hash map
|
||||
[x] Combine library code files
|
||||
[x] Implement configuration file parsing using hash map
|
||||
[x] Base64 encoding/decoding with padded/unpadded support
|
||||
[x] Write a release script
|
||||
[x] UTF-8 encoder
|
||||
[x] Implement a JSON library using the hash map and array
|
||||
[x] Basic encoding from HashMap/Array/strings, etc.
|
||||
[x] Basic decoding to HashMap/Array/strings, etc.
|
||||
[x] Proper string encoding
|
||||
[x] Proper string decoding
|
||||
[x] Canonical JSON
|
||||
[x] Keys are sorted lexiconographically
|
||||
[x] Floats are not allowed (ignore any float values)
|
||||
[x] Encode as UTF-8 instead of using \u escapes
|
||||
[x] Decode encoded strings to UTF-8
|
||||
[x] Write a function that gets the current Unix timestamp in milliseconds
|
||||
[x] Parse the Telodendria config file
|
||||
[x] Add license header to all files
|
||||
[!] Add documentation for all public APIs (See below)
|
||||
[x] Implement a simple HTTP server
|
||||
[x] Implement param parser
|
||||
[x] URL encoder/decoder
|
||||
[~] Design server architecture
|
||||
[x] Route requests
|
||||
[x] Handle requests
|
||||
[~] Data abstraction layer
|
||||
[ ] Database upgrades/migration path
|
||||
[ ] Caching and cache control
|
||||
[x] Make memory info access O(1)
|
||||
[x] Error generation
|
||||
[x] Properly implement the command line options as stated in telodendria(8)
|
||||
[x] Remove "chroot" option, just chroot into the data dir, and make the log
|
||||
file live there as well.
|
||||
[x] Allow logging to the syslog
|
||||
[x] Fix memory leaks
|
||||
[ ] Fix bug where the socket stays open after quit.
|
||||
[ ] Possibly related to not closing the connections with fclose()?
|
||||
(see HttpServer.c, grep for fclose)
|
||||
[ ] Figure out how to write unit tests for array/hashmap/etc
|
||||
[ ] Add recipe to td script to upload patches to the Matrix room
|
||||
|
||||
Phase 3: Welcome to Matrix
|
||||
|
||||
[~] client-Server API
|
||||
[x] Error responses
|
||||
[x] CORS headers
|
||||
[x] /_matrix/client/versions
|
||||
[x] Well-known URIs
|
||||
[x] Make base-url optional in config
|
||||
[x] Make identity-server optional in config
|
||||
[ ] Client authentication
|
||||
[ ] Capabilities negotiation
|
||||
[ ] Server-Server API
|
||||
[ ] Application Service API
|
||||
[ ] Identity Service API
|
||||
[ ] Push Gateway API
|
||||
[ ] Room Versions
|
||||
|
||||
Phase 4:
|
||||
|
||||
[ ] Create an OpenBSD package and get it submitted to ports
|
||||
[ ] Add relayd examples to contrib/
|
||||
[ ] 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
|
||||
|
||||
Documentation
|
||||
|
||||
[x] Update rationale section
|
||||
[x] Update project description (no longer a CGI binary)
|
||||
[x] Clean up dark mode colors (tables, background, code snippets)
|
||||
[x] Add logo (possibly center title?)
|
||||
[x] Update code style to not include indent or line rules, but use indent(1) instead
|
||||
[x] fix typo "Subitting Patches" in Table of Contents
|
||||
[x] Make a note in Getting the Code that the password for the anoncvs account is just anoncvs
|
||||
[x] Add contributors list
|
||||
[x] Add list of make.sh recipes and what they do
|
||||
[x] Improve Google Lighthouse score on website
|
||||
[!] Image elements do not have explicit width and height
|
||||
[x] Background and foreground colors do not have sufficient contrast ratio (msg-error div)
|
||||
[x] Lists do not contain only <li> elements
|
||||
[!] Add other message divs for notes and warnings
|
||||
|
||||
[~] Convert documentation to man pages
|
||||
[x] Clean up dark mode in man page CSS (links)
|
||||
[x] Synopsis table should not be styled
|
||||
[x] Make available on MANPATH in tools/env.sh
|
||||
[x] Convert list of man pages into a table on home page
|
||||
[~] Internal API docs
|
||||
[x] Array
|
||||
[x] Base64
|
||||
[ ] CanonicalJson
|
||||
[ ] Config
|
||||
[ ] API (Config.3)
|
||||
[ ] File format (Config.5)
|
||||
[ ] Constants
|
||||
[x] HashMap
|
||||
[ ] Http
|
||||
[ ] HttpServer
|
||||
[ ] Json
|
||||
[ ] Log
|
||||
[ ] Matrix
|
||||
[ ] NonPosix
|
||||
[x] Queue
|
||||
[ ] Routes
|
||||
[ ] TelodendriaConfig
|
||||
[ ] Util
|
||||
[ ] Memory
|
||||
[ ] Db
|
||||
|
||||
[x] Add onboarding documentation
|
||||
[x] Building from source
|
||||
[!] Writing config file (see Config.5)
|
||||
|
62
Telodendria.css
Normal file
62
Telodendria.css
Normal file
|
@ -0,0 +1,62 @@
|
|||
body {
|
||||
margin: auto;
|
||||
max-width: 8.5in;
|
||||
padding: 0.25in;
|
||||
}
|
||||
.code {
|
||||
background-color: #eee;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
padding: 0.5em 1em 1.5em 1em;
|
||||
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
kbd {
|
||||
background-color: #eee;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b4b4b4;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset;
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
font-size: .85em;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
padding: 2px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
h1, h4, h5, h6 {
|
||||
border-bottom: 1px dashed gray;
|
||||
}
|
||||
h4, h5, h6 {
|
||||
width: fit-content;
|
||||
}
|
||||
a {
|
||||
color: #0969da;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
td, th {
|
||||
border: 1px solid #eee;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background-color: #eee;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #212121;
|
||||
color: white;
|
||||
}
|
||||
.code, tr:nth-child(even) {
|
||||
background-color: #333333;
|
||||
}
|
||||
}
|
541
Telodendria.html
Normal file
541
Telodendria.html
Normal file
|
@ -0,0 +1,541 @@
|
|||
<!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://bancino.net/pub/telodendria/telodendria.html">
|
||||
<meta property="og:description"
|
||||
content="Telodendria, a Matrix homeserver written in ANSI C.">
|
||||
|
||||
<link rel="stylesheet" href="Telodendria.css">
|
||||
|
||||
<title>Telodendria | A Matrix Homeserver written in ANSI C.</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="telodendria">Telodendria</h1>
|
||||
<p>
|
||||
<b>Telodendria:</b> The terminal branches of an axon.
|
||||
</p>
|
||||
<p>
|
||||
<b><i>Note:</i></b> <b>Telodendria</b> is under <i>heavy</i> development.
|
||||
Please see the <a href="#project-status">Project Status</a>.
|
||||
</p>
|
||||
<p>
|
||||
<b>Telodendria</b> is a Matrix homeserver implementation written from
|
||||
scratch in ANSI C. It is designed to be lightweight and simple, yet
|
||||
functional. <b>Telodendria</b> differentiates itself from other Matrix
|
||||
homeserver implementations because it:
|
||||
<ul>
|
||||
<li>
|
||||
Is written in C, a stable, low-level programming language with a long
|
||||
history, low build and runtime overhead, and wide compatibility.
|
||||
</li>
|
||||
<li>
|
||||
Is written with minimalism as a primary design goal. Whenever possible
|
||||
and practical, no third-party libraries are pulled in to the source
|
||||
code. Everything <b>Telodendria</b> needs is custom written. As a
|
||||
result, <b>Telodendria</b> depends only on a standard C compiler and
|
||||
library to be built, and only a web server with CGI capabilities to
|
||||
run.
|
||||
</li>
|
||||
<li>
|
||||
Uses a flat-file directory structure to store data instead of a
|
||||
database. This has a number of advantages:
|
||||
<ul>
|
||||
<li>It makes setup and maintenance much easier.</li>
|
||||
<li>
|
||||
It allows <b>Telodendria</b> to run on systems with fewer resources.
|
||||
</li>
|
||||
<li>
|
||||
It provides both runtime and data safety and stability. Since no
|
||||
database is running, there's fewer things that could go wrong because
|
||||
there's a lot less code running on the system.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Runs as a CGI application. <b>Telodendria</b> is delivered as a single
|
||||
small, highly-optimized binary that can be dropped in a web server's
|
||||
web root to be executed. This allows it to consume very few resources
|
||||
and be very easy to set up.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<b>Telodendria</b> is on Matrix! Check out the official Matrix rooms:
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Room</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>#telodendria-releases:bancino.net</code>
|
||||
</td>
|
||||
<td>
|
||||
Get notified of new releases.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>#telodendria-general:bancino.net</code>
|
||||
</td>
|
||||
<td>
|
||||
General discussion and support for <b>Telodendria</b>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>#telodendria-issues:bancino.net</code>
|
||||
</td>
|
||||
<td>
|
||||
Report issues with <b>Telodendria</b>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>#telodendria-patches:bancino.net</code>
|
||||
</td>
|
||||
<td>
|
||||
Submit code patches to the <b>Telodendria</b> project.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2 id="table-of-contents">Table of Contents</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#telodendria">Telodendria</a>
|
||||
<ul>
|
||||
<li><a href="#table-of-contents">Table of Contents</a></li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#install">Install Telodendria</a>
|
||||
<ul>
|
||||
<li><a href="#openbsd">OpenBSD</a></li>
|
||||
<li><a href="#building-from-source">Building From Source</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#configure">Configure Telodendria</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#project-status">Project Status</a>
|
||||
<ul>
|
||||
<li><a href="#phase-1">Phase 1: Getting Off The Ground</a></li>
|
||||
<li><a href="#phase-2">Phase 2: Building A Foundation</a></li>
|
||||
<li><a href="#phase-3">Phase 3: Welcome To Matrix</a></li>
|
||||
<li><a href="#phase-4">Phase 4: A Real Homeserver</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#rationale">Rationale</a></li>
|
||||
<li><a href="#project-goals">Project Goals</a></li>
|
||||
<li><a href="#getting-support">Getting Support</a></li>
|
||||
<li>
|
||||
<li>
|
||||
<a href="#documentation-status">Documentation Status</a>
|
||||
</li>
|
||||
<a href="#contributing">Contributing</a>
|
||||
<ul>
|
||||
<li><a href="#reporting-issues">Reporting Issues</a></li>
|
||||
<li>
|
||||
<a href="#Developing">Developing</a>
|
||||
<ul>
|
||||
<li><a href="#getting-the-code">Getting The Code</a></li>
|
||||
<li><a href="#code-style">Code Style</a></li>
|
||||
<li><a href="#submitting-patches">Subitting Patches</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#license">License</a></li>
|
||||
<li><a href="#change-log">Change Log</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="getting-started">Getting Started</h2>
|
||||
<h3 id="install">Install Telodendria</h3>
|
||||
<p>
|
||||
If your operating system has an official package or port of
|
||||
<b>Telodendria</b>, you should prefer to use that. 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 not have an official package, see below
|
||||
for instructions on building from source and use them to create one.
|
||||
</p>
|
||||
<h4 id="openbsd">OpenBSD</h4>
|
||||
<p>
|
||||
<b>Telodendria</b> is available in the ports tree and as a binary
|
||||
package. You can install it with the following command:
|
||||
</p>
|
||||
<div class="code">
|
||||
$ pkg_add telodendria
|
||||
</div>
|
||||
<h4 id="building-from-source">Building From Source</h4>
|
||||
<p>
|
||||
<b>Telodendria</b> is designed to be light enough that it can be built
|
||||
from source on just about any operating system. It only has the
|
||||
following requirements, all of which should be already available to
|
||||
you on a sufficiently complete operating system:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
A standards-compliant C compiler with the C standard library. Because
|
||||
<b>Telodendria</b> is written in ANSI C, it should compile on just about
|
||||
any compiler, but the following compilers are known to work:
|
||||
<ul>
|
||||
<li>GCC</li>
|
||||
<li>Clang</li>
|
||||
<li>
|
||||
Tiny C Compiler (<b>Note:</b> must edit <code>make.sh</code> and remove
|
||||
<code>-Wl,-static -Wl,-gc-sections</code> from <code>LDFLAGS</code>)
|
||||
</li>
|
||||
</ul>
|
||||
Other compilers should work as well, but you may have to play with the
|
||||
flags in <code>make.sh</code>.
|
||||
</li>
|
||||
<li>
|
||||
POSIX base utilities, including <code>find</code>, <code>stat</code>,
|
||||
<code>env</code>, and compliant <code>sh</code>-like shell.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="code">
|
||||
$ ./make.sh
|
||||
</div>
|
||||
<p>
|
||||
If everything went well, that will produce
|
||||
<code>telodendria.cgi</code>, which you can then place under your web
|
||||
root and configure your web server to execute. You'll need to make sure
|
||||
<code>/.well-known/matrix</code> and <code>/_matrix</code> and all the
|
||||
paths under them actually execute <code>telodendria.cgi</code>. See the
|
||||
provided OpenBSD <code>httpd.conf</code> for reference. Even if you
|
||||
aren't using OpenBSD's <code>httpd(8)</code>, you should find its
|
||||
configuration syntax simple enough to adequately demonstrate the proper
|
||||
configuration.
|
||||
</p>
|
||||
<h3 id="configure">Configure Telodendria</h3>
|
||||
<p>
|
||||
Once you get <b>Telodendria</b> built and hooked into your web server,
|
||||
you will have to write a configuration file for it. The configuration
|
||||
file is just JSON, and it should be called
|
||||
<code>Telodendria.json</code>.
|
||||
</p>
|
||||
<h2 id="project-status">Project Status</h2>
|
||||
<p>
|
||||
<b>Telodendria</b> is a very ambitious project. There's a lot that needs
|
||||
to happen yet before it is even remotely usable. At the moment, there's
|
||||
nothing that even remotely resembles a Matrix homeserver here; we're still
|
||||
getting off the ground and building a foundation.
|
||||
</p>
|
||||
<p>
|
||||
Just because there's nothing here yet doesn't mean you should go away
|
||||
though! We desparately need help, so you are more than welcome to help
|
||||
out if you want things to go quicker. Please see the
|
||||
<a href="#contributing">Contributing</a> section for details on how you
|
||||
can get involved.
|
||||
</p>
|
||||
<h3 id="phase-1">Phase 1: Getting Off The Ground</h3>
|
||||
<ul>
|
||||
<li><s>Name this project</s></li>
|
||||
<li><s>Set up a CVS repository</s></li>
|
||||
<li><s>Make CVS repository public</s></li>
|
||||
<li><s>Write a coding style guide</s></li>
|
||||
<li><s>Write a build script</s></li>
|
||||
<li><s>Add a license</s></li>
|
||||
<li><s>Add support and issue reporting guide</s></li>
|
||||
<li><s>Add table of contents to this document</s></li>
|
||||
</ul>
|
||||
<h3 id="phase-2">Phase 2: Building A Foundation</h3>
|
||||
<ul>
|
||||
<li><s>Implement an array</s></li>
|
||||
<li><s>Implement a logging facility</s></li>
|
||||
<li><s>Implement argument parsing (<code>-c file -Vh</code>)</s></li>
|
||||
<li><s>Implement a hash map</s></li>
|
||||
<li><s>Combine library code files</s></li>
|
||||
<li><s>Implement configuration file parsing using the hash map</s></li>
|
||||
<li>Implement a JSON library using the hash map and array</li>
|
||||
<li>Figure out how to write unit tests for array/hashmap/etc</li>
|
||||
<li>Implement a simple HTTP server</li>
|
||||
<li>
|
||||
Design the server architecture
|
||||
<ul>
|
||||
<li>Route requests</li>
|
||||
<li>Handle requests</li>
|
||||
<li>Data abstraction layer</li>
|
||||
<li>Error generation</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 id="phase-3">Phase 3: Welcome To Matrix</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Implement the Client-Server API
|
||||
</li>
|
||||
<li>
|
||||
Implement the Server-Server API
|
||||
</li>
|
||||
<li>
|
||||
Implement the other Matrix APIs
|
||||
</li>
|
||||
</ul>
|
||||
<h3 id="phase-4">Phase 4: A Real Homeserver</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Create an OpenBSD package and get it submitted to ports
|
||||
</li>
|
||||
<li>
|
||||
Create a command line tool to manage Telodendria
|
||||
<ul>
|
||||
<li>Configuration file generation</li>
|
||||
<li>User management</li>
|
||||
<li>Room management</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Migrate from Synapse. I run a Synapse homeserver right now, so somehow
|
||||
I have to get all my data into the Telodendria format.
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="documentation-status">Documentation Status</h2>
|
||||
<p>
|
||||
This documentation needs just a little work. Here's the things
|
||||
on my list for that:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Update Rationale section</li>
|
||||
<li>Update Project description (no longer a CGI binary)</li>
|
||||
<li>Update project code requirements (ANSI C, POSIX.1c)</li>
|
||||
</ul>
|
||||
<h2 id="rationale">Rationale</h2>
|
||||
<p>
|
||||
This section explains
|
||||
</p>
|
||||
<p>
|
||||
I want a lightweight Matrix homeserver designed for OpenBSD. I want a
|
||||
homeserver that can be developed in <code>vi(1)</code> and compiled
|
||||
with a C compiler. I want it to function entirely on a base OpenBSD
|
||||
install without having to install any extra packages whatsoever. I've
|
||||
found that the existing homeserver implementations are way
|
||||
over-engineered and written in such a way that many programs and
|
||||
libraries have to be pulled in to use them. I also want to learn how
|
||||
Matrix works, and I want to understand the code I'm running on my
|
||||
server.
|
||||
</p>
|
||||
<p>
|
||||
So I wrote Telodendria.
|
||||
</p>
|
||||
<p>
|
||||
Telodendria is written entirely in portable ANSI C. It depends on no
|
||||
third-party C libraries other than the standard C library. The only
|
||||
thing you need to run it is a web server that supports executing CGI
|
||||
programs, and a directory that data can be written to. Everything
|
||||
Telodendria needs to run itself is compiled into a single static
|
||||
binary, and the source code can be built anywhere, right out of the
|
||||
box.
|
||||
</p>
|
||||
<p>
|
||||
Telodendria doesn't use a database like all the other homeservers.
|
||||
Instead, it operates more like email: it uses a flat-file data
|
||||
structure similar to maildir to store data. The advantage of this is
|
||||
that it saves server maintainers from also having to maintain a
|
||||
database. It greatly simplifies the process of getting a Matrix
|
||||
homeserver up and running, and it makes it highly portable. It also is
|
||||
extremely easy to back up and restore with base tools; just
|
||||
<code>tar(1)</code> up the directory, and you're good to go.
|
||||
</p>
|
||||
<p>
|
||||
Telodendria is developed and tested on OpenBSD, but you'll find that it
|
||||
should run under any web server that supports CGI. I chose to write
|
||||
Telodendria as a CGI program because anyone running an existing Matrix
|
||||
server is likely running a web server acting as a reverse proxy in
|
||||
front of it anyway, so why not just hook the homeserver directly into
|
||||
the web server? That's one less daemon to run, which means memory and
|
||||
CPU savings. CGI also allows Telodendria to remain single-threaded.
|
||||
Each request that comes in is handled as its own process, and
|
||||
operations are entirely isolated.
|
||||
</p>
|
||||
<h2 id="project-goals">Project Goals</h2>
|
||||
<p>
|
||||
The goals of this project are as follows:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
To be a production-ready Matrix server capable of handling a lot of
|
||||
users. Telodendria should have good performance in many diverse
|
||||
environments.
|
||||
</li>
|
||||
<li>
|
||||
To have as few external build and run dependencies as possible. It
|
||||
should be possible to compile Telodendria on any operating system out
|
||||
of the box, and have it be totally statically linked, ready to run
|
||||
under a <code>chroot(8)</code>-ed web server. You'll even notice that
|
||||
the documentation is written in HTML directly, not Markdown, to remove
|
||||
the dependency on a Markdown parser and renderer.
|
||||
</li>
|
||||
<li>
|
||||
To be written in clean, elegant, and well-documented code. The goal is
|
||||
to build a Matrix homeserver from the ground up, not just because I
|
||||
don't the way existing homeservers are implemented, but also so I can
|
||||
learn how Matrix really works, and maybe even teach others along the
|
||||
way.
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="getting-support">Getting Support</h2>
|
||||
<p>
|
||||
<b>Telodendria</b> is designed to be fairly straightforward, but that
|
||||
doesn't mean there won't be hiccups along the way. If you are struggling
|
||||
to get <b>Telodendria</b> up and running, you're more than welcome to
|
||||
reach out for support. Just join the
|
||||
<code>#telodendria-general:bancino.net</code> Matrix channel. Before
|
||||
you do though, make sure you're running the latest version of
|
||||
<b>Telodendria</b> and you've thoroughly read through all the
|
||||
relevant documentation.
|
||||
</p>
|
||||
<h2 id="contributing">Contributing</h2>
|
||||
<p>
|
||||
<b>Telodendria</b> 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.
|
||||
</p>
|
||||
<h3 id="reporting-issues">Reporting Issues</h3>
|
||||
<p>
|
||||
If—after you've reached out to
|
||||
<code>#telodendria-general:bancino.net</code>—it has been
|
||||
determined that there is a problem with <b>Telodendria</b>, it should
|
||||
be reported to <code>#telodendria-issues:bancino.net</code>. There it
|
||||
can be discussed further. The issues channel serves as the official
|
||||
issue tracker of <b>Telodendria</b>; although issues may be copied
|
||||
into a <code>TODO</code> file in the CVS repository just so they
|
||||
don't get lost.
|
||||
</p>
|
||||
<h3 id="developing">Developing</h3>
|
||||
<p>
|
||||
The primary language used to write <b>Telodendria</b> code is ANSI C.
|
||||
Yes, that's the original C standard from 1989. The reason this standard
|
||||
is chosen, and the reason that it will not be changed, is because the
|
||||
original C is the most portable. Other languages you'll find in the
|
||||
<b>Telodendria</b> repository are shell scripts and HTML. If you have
|
||||
any experience at all with any of these languages, your contributions
|
||||
are valuable. Please follow the guidelines in this section to ensure
|
||||
the contribution workflow goes as smoothly as possible.
|
||||
</p>
|
||||
<h4 id="getting-the-code">Getting The Code</h4>
|
||||
<p>
|
||||
There are multiple ways to get the source code for <b>Telodendria</b>.
|
||||
You can download an official release tarball from
|
||||
<a href="https://bancino.net/pub/telodendria">here</a> if you would like,
|
||||
but the preferred way is to check out the source code from CVS. This
|
||||
makes generating patches a lot easier. If you do not have CVS, consult
|
||||
your operating system's package repository to install it. CVS was the
|
||||
chosen version control system for this project primarily because it is
|
||||
built into OpenBSD.
|
||||
</p>
|
||||
<div class="code">
|
||||
$ export CVSROOT=anoncvs@bancino.net:/cvs
|
||||
$ cvs checkout Telodendria
|
||||
$ cd Telodendria
|
||||
</div>
|
||||
<p>
|
||||
You should now have the latest <b>Telodendria</b> source code. Follow
|
||||
the <a href="#code-style">Code Style</a> as you make your changes.
|
||||
</p>
|
||||
<h4 id="code-style">Code Style</h4>
|
||||
<p>
|
||||
<b>Telodendria</b>'s code style is very unique. In general, these are
|
||||
the conventions used by the code base.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
All function, enumeration, structure, and header names are
|
||||
<code>CamelCase</code>. This is preferred to <code>snake_case</code>
|
||||
because it is more compact.
|
||||
</li>
|
||||
<li>
|
||||
<code>enum</code>s and <code>struct</code>s are always
|
||||
<code>typedef</code>-ed to their same name. The <code>typedef</code>
|
||||
occurs in the public API header, and the actual declaration occurs in
|
||||
the private implementation header.
|
||||
</li>
|
||||
<li>
|
||||
Indentation is done with spaces. This ensures that files look the same
|
||||
for everyone. It also makes line wrapping rules much easier because
|
||||
the indentations are the same size. Please configure your editor to
|
||||
make the <kbd>Tab</kbd> key insert spaces, and if it does automatic
|
||||
indentation, make sure it indents with spaces. If you cannot configure
|
||||
your editor to insert spaces, then you can try running the code files
|
||||
you were working on through <code>expand</code>. A unit of indentation
|
||||
is 4 spaces.
|
||||
</li>
|
||||
<li>
|
||||
Lines should not exceed 72 characters, including indentations. Some
|
||||
developers use <code>vi(1)</code> in an 80x24 terminal to write code.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
This guide may be subject to change. The source code is the absolute
|
||||
source of truth, so as long as you make your code look like the
|
||||
code surrounding it, you should be fine.
|
||||
</p>
|
||||
<h4 id="submitting-patches">Submitting Patches</h4>
|
||||
<p>
|
||||
Submitting patches is fairly easy to do if you've got the CVS sources
|
||||
checked out. Once you have made your changes, just run
|
||||
<code>cvs diff</code>:
|
||||
</p>
|
||||
<div class="code">
|
||||
$ cvs diff -uNp > your-changes.patch
|
||||
</div>
|
||||
<p>
|
||||
Then, send the resulting patches to
|
||||
<code>#telodendria-patches:bancino.net</code>, where they will be
|
||||
promptly reviewed by the community.
|
||||
</p>
|
||||
<h2 id="license">License</h2>
|
||||
<p>
|
||||
All of the code and documentation for <b>Telodendria</b> is licensed
|
||||
under the following terms and conditions:
|
||||
</p>
|
||||
<div class="code">
|
||||
Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial 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.
|
||||
</div>
|
||||
<h2 id="change-log">Change Log</h2>
|
||||
<p>
|
||||
At this time, Telodendria does not have any tagged releases because it
|
||||
is not yet functional as a Matrix homeserver. Please check out the <a
|
||||
href="#project-status">Project Status</a> to see where things are
|
||||
currently at.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
321
configure
vendored
321
configure
vendored
|
@ -1,321 +0,0 @@
|
|||
#!/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|Darwin)
|
||||
# These systems typically use Clang.
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=clang"
|
||||
;;
|
||||
*)
|
||||
# Use default compiler which is required to be present on
|
||||
# all POSIX-compliant systems.
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=c99"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Append any additional args specified by user
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} $@"
|
||||
|
||||
echo "Processing options..."
|
||||
echo "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"
|
||||
dir=$(dirname "$out")
|
||||
|
||||
echo "${TAB}mkdir -p \"$dir\""
|
||||
echo "${TAB}cp \"$src\" \"$out\""
|
||||
}
|
||||
|
||||
install_man() {
|
||||
src="${OUT}/man/man3/${BIN_NAME}-$(basename $1 .h).3"
|
||||
out="$2"
|
||||
dir=$(dirname "$out")
|
||||
|
||||
echo "${TAB}mkdir -p \"$dir\""
|
||||
echo "${TAB}cp \"$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}mkdir -p \$(PREFIX)/bin
|
||||
${TAB}cp ${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,6 +0,0 @@
|
|||
all:
|
||||
sh tools/bin/td
|
||||
|
||||
install:
|
||||
install build/telodendria $(PREFIX)/bin/telodendria
|
||||
find man -name 'telodendria*\.[1-8]' -exec install {} $(PREFIX)/{} \;
|
34
contrib/Vagrantfile
vendored
34
contrib/Vagrantfile
vendored
|
@ -1,34 +0,0 @@
|
|||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "generic/openbsd7"
|
||||
config.vm.network "forwarded_port", guest: 80, host: 80
|
||||
config.vm.network "forwarded_port", guest: 443, host: 443
|
||||
config.vm.network "forwarded_port", guest: 8008, host: 8008
|
||||
# NOTE: This address is not within the allowed ranges.
|
||||
# To allow this address, simply allow all ranges by specifying
|
||||
# this in /etc/vbox/networks.conf (if you use the Virtualbox provider, or change the configured address):
|
||||
# * 0.0.0.0/0 ::/0
|
||||
config.vm.network "private_network", ip: "172.17.0.101"
|
||||
# File watcher which syncs the project directory to /vagrant on the vm
|
||||
config.vm.synced_folder "../", "/vagrant"
|
||||
config.vm.provision "shell", inline: <<-EOF
|
||||
cp /vagrant/contrib/relayd.conf /etc/relayd.conf
|
||||
sed -i s/127.0.0.1/0.0.0.0/ /etc/relayd.conf
|
||||
mkdir -p -m 0700 /etc/ssl/private
|
||||
openssl req -x509 -newkey rsa:4096 \
|
||||
-days 365 -nodes \
|
||||
-subj '/CN=telodendria' \
|
||||
-keyout /etc/ssl/private/telodendria.key \
|
||||
-out /etc/ssl/telodendria.crt
|
||||
relayd -n
|
||||
rcctl enable relayd
|
||||
rcctl restart relayd
|
||||
cat /vagrant/tools/env.sh >> /home/vagrant/.bash_profile
|
||||
sed -i 's#$(pwd)#/vagrant#' /home/vagrant/.bash_profile
|
||||
sed -i 's#find tools/bin#find /vagrant/tools/bin#' /home/vagrant/.bash_profile
|
||||
mkdir /vagrant/data
|
||||
cp /vagrant/contrib/development.conf /vagrant/contrib/development.conf.bak
|
||||
sed -i 's/"localhost"/"vagrant"/' /vagrant/contrib/development.conf
|
||||
### If you changed the address in the config above you might want to change it here as well:
|
||||
sed -i s#http://localhost:8008#https://172.17.0.101:443# /vagrant/contrib/development.conf
|
||||
EOF
|
||||
end
|
|
@ -1,18 +1,18 @@
|
|||
{
|
||||
"log": {
|
||||
"output": "stdout",
|
||||
"color": true,
|
||||
"timestampFormat": "none",
|
||||
"level": "debug"
|
||||
},
|
||||
"listen": [
|
||||
{
|
||||
"port": 8008,
|
||||
"tls": false
|
||||
}
|
||||
],
|
||||
"registration": true,
|
||||
"serverName": "localhost",
|
||||
"baseUrl": "http:\/\/localhost:8008",
|
||||
"federation": true
|
||||
}
|
||||
#
|
||||
# Telodendria development configuration file.
|
||||
#
|
||||
|
||||
server-name "localhost";
|
||||
base-url "http://localhost:8008";
|
||||
id "jordan";
|
||||
data-dir "./data";
|
||||
federation "true";
|
||||
registration "true";
|
||||
log "stdout" {
|
||||
level "debug";
|
||||
timestampFormat "none";
|
||||
color "true";
|
||||
};
|
||||
threads "4";
|
||||
max-connections "32";
|
||||
max-cache "1k";
|
||||
|
|
9
contrib/httpd.conf
Normal file
9
contrib/httpd.conf
Normal file
|
@ -0,0 +1,9 @@
|
|||
#
|
||||
# httpd.conf: An OpenBSD httpd(8) configuration file for running
|
||||
# Telodendria. Note that this is a development configuration that
|
||||
# should be adapted using the httpd.conf(5) man page for production
|
||||
# use.
|
||||
#
|
||||
server "matrix" {
|
||||
listen on localhost port http
|
||||
}
|
|
@ -1,24 +1,32 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
#
|
||||
# Telodendria configuration file.
|
||||
#
|
||||
# The following man pages document the configuration:
|
||||
#
|
||||
# - telodendria.conf(5)
|
||||
# - Config(5)
|
||||
#
|
||||
# Alternatively, find the man pages online at the
|
||||
# following URL:
|
||||
#
|
||||
# https://telodendria.io/#documentation
|
||||
#
|
||||
|
||||
listen "8008";
|
||||
|
||||
server-name "example.com";
|
||||
base-url "https://matrix.example.com";
|
||||
identity-server "https://identity.example.com";
|
||||
|
||||
id "_telodendria" "_telodendria";
|
||||
data-dir "/var/telodendria";
|
||||
federation "true";
|
||||
registration "false";
|
||||
log "file" {
|
||||
level "warning";
|
||||
timestampFormat "default";
|
||||
};
|
||||
|
||||
threads "4";
|
||||
max-connections "32";
|
||||
max-cache "512M";
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
table <telodendria> { localhost }
|
||||
|
||||
http protocol httpproxy {
|
||||
pass request quick path "/_matrix/*" forward to <telodendria>
|
||||
pass request quick path "/.well-known/matrix/*" forward to <telodendria>
|
||||
|
||||
# You'll have to generate the following for this to work:
|
||||
# /etc/ssl/telodendria.crt /etc/ssl/private/telodendria.key
|
||||
tls keypair "telodendria"
|
||||
|
||||
block
|
||||
return error
|
||||
}
|
||||
|
||||
relay proxy {
|
||||
listen on 127.0.0.1 port https tls
|
||||
|
||||
protocol httpproxy
|
||||
|
||||
forward to <telodendria> port 8008
|
||||
}
|
||||
|
13
contrib/telodendria.conf
Normal file
13
contrib/telodendria.conf
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Telodendria configuration file
|
||||
|
||||
log "/var/log/telodendria.log" {
|
||||
level "message";
|
||||
timestampFormat "none";
|
||||
color "true";
|
||||
};
|
||||
|
||||
threads "4";
|
||||
|
||||
data-dir "/var/telodendria";
|
||||
|
||||
federation "true";
|
|
@ -1,291 +0,0 @@
|
|||
# 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!
|
|
@ -1,256 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
# 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)
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
# 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
|
|
@ -1,94 +0,0 @@
|
|||
# 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.
|
|
@ -1,92 +0,0 @@
|
|||
## 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.
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
# 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.
|
|
@ -1,17 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# 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.|
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
# 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.
|
|
@ -1,141 +0,0 @@
|
|||
# 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.|
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# 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.|
|
|
@ -1,106 +0,0 @@
|
|||
# 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.
|
|
@ -1,248 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
# 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.
|
|
@ -1,60 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# 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.
|
57
make.sh
Normal file
57
make.sh
Normal file
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
TELODENDRIA_VERSION="0.0.1"
|
||||
|
||||
HEADERS="-D_POSIX_C_SOURCE=199506L -DTELODENDRIA_VERSION=\"$TELODENDRIA_VERSION\""
|
||||
INCLUDES="-Isrc/include"
|
||||
|
||||
CC="${CC:-cc}"
|
||||
CFLAGS="-Wall -Werror -pedantic -std=c89 -O3 $HEADERS $INCLUDES"
|
||||
LDFLAGS="-static -flto -fdata-sections -ffunction-sections -s -Wl,-static -Wl,-gc-sections"
|
||||
PROG="telodendria"
|
||||
|
||||
mod_time() {
|
||||
if [ -n "$1" ] && [ -f "$1" ]; then
|
||||
case "$(uname)" in
|
||||
Linux)
|
||||
stat -c %Y "$1"
|
||||
;;
|
||||
*BSD)
|
||||
stat -f %m "$1"
|
||||
;;
|
||||
*)
|
||||
echo "0"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
mkdir -p build
|
||||
|
||||
do_rebuild=0
|
||||
objs=""
|
||||
for src in $(find src -name '*.c'); do
|
||||
obj=$(echo "$src" | sed -e 's/^src/build/' -e 's/\.c$/\.o/')
|
||||
objs="$objs $obj"
|
||||
|
||||
if [ $(mod_time "$src") -gt $(mod_time "$obj") ]; then
|
||||
echo "CC $obj"
|
||||
obj_dir=$(dirname "$obj")
|
||||
mkdir -p "$obj_dir"
|
||||
if ! $CC $CFLAGS -c -o "$obj" "$src"; then
|
||||
exit 1
|
||||
fi
|
||||
do_rebuild=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $do_rebuild -eq 1 ] || [ ! -f "build/$PROG" ]; then
|
||||
echo "LD build/$PROG"
|
||||
$CC $LDFLAGS -o "build/$PROG" $objs
|
||||
else
|
||||
echo "Up to date."
|
||||
fi
|
||||
|
||||
ls -lh "build/$PROG"
|
1
man/.cvsignore
Normal file
1
man/.cvsignore
Normal file
|
@ -0,0 +1 @@
|
|||
mandoc.db
|
|
@ -1,18 +0,0 @@
|
|||
.Dd $Mdocdate: May 6 2023 $
|
||||
.Dt HTTP-DEBUG-SERVER 1
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm http-debug-server
|
||||
.Nd A simple HTTP server that logs requests to the standard output.
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
spins up an HTTP server, listening on port 8008, in the exact same
|
||||
manner as Telodendria itself. Any request it receives is written to
|
||||
the standard output, and an empty JSON object is returned to the
|
||||
client.
|
||||
.Pp
|
||||
This command exists just to test the HTTP server API during
|
||||
development. It probably serves no other practical purpose.
|
||||
.Sh SEE ALSO
|
||||
.Xr HttpServer 3
|
111
man/man1/json.1
111
man/man1/json.1
|
@ -1,111 +0,0 @@
|
|||
.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.
|
|
@ -1,34 +0,0 @@
|
|||
.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
|
114
man/man3/Array.3
Normal file
114
man/man3/Array.3
Normal file
|
@ -0,0 +1,114 @@
|
|||
.Dd $Mdocdate: September 30 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 *)"
|
||||
.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.
|
||||
.Sh RETURN VALUES
|
||||
.Fn ArrayCreate
|
||||
returns 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
|
81
man/man3/Base64.3
Normal file
81
man/man3/Base64.3
Normal file
|
@ -0,0 +1,81 @@
|
|||
.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.
|
146
man/man3/HashMap.3
Normal file
146
man/man3/HashMap.3
Normal file
|
@ -0,0 +1,146 @@
|
|||
.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
|
100
man/man3/Queue.3
Normal file
100
man/man3/Queue.3
Normal file
|
@ -0,0 +1,100 @@
|
|||
.Dd $Mdocdate: October 10 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 "void"
|
||||
.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
|
205
man/man5/telodendria.conf.5
Normal file
205
man/man5/telodendria.conf.5
Normal file
|
@ -0,0 +1,205 @@
|
|||
.Dd $Mdocdate: October 24 2022 $
|
||||
.Dt TELODENDRIA.CONF 5
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm telodendria.conf
|
||||
.Nd Configuration file for Telodendria
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is the configuration file for the Telodendria homeserver,
|
||||
.Xr telodendria 8 .
|
||||
Telodendria is designed to be extremely configurable. As such,
|
||||
it has a fairly extensive configuration file. The configuration
|
||||
file can be passed to the Telodendria binary with the
|
||||
.Sy -f
|
||||
option, and is typically located at
|
||||
.Pa /etc/telodendria.conf
|
||||
.sp
|
||||
.Nm
|
||||
uses OpenBSD-style syntax, though it is a little more rigid in its
|
||||
parser. All values must be surrounded by quotes, and each directive
|
||||
must be ended with a semicolon.
|
||||
.Sh MACROS
|
||||
Macros can be defined that will later be expanded in context.
|
||||
Macro names must start with a letter, digit, or underscore, and may
|
||||
contain only those characters. Macros are not expanded inside quotes.
|
||||
.sp
|
||||
For example:
|
||||
.Bd -literal -offset indent
|
||||
macro1 = "value1";
|
||||
directive $macro1;
|
||||
.Ed
|
||||
.Sh GLOBAL OPTIONS
|
||||
Here are the settings that can be set globally:
|
||||
.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 server-name 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 base-url 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 identity-server Ar url
|
||||
The identity server that clients should use to perform identity lookups.
|
||||
.Pp
|
||||
.Ar url
|
||||
follows the same rules as
|
||||
.Ic base-url .
|
||||
.Pp
|
||||
This directive is optional. If it is not specified, it is automatically
|
||||
set to be the same as the base URL.
|
||||
.It Ic id Ar uid Ar gid
|
||||
The effective UNIX user and group to drop to after binding to the socket
|
||||
and changing the filesystem root. This only works if Telodendria is
|
||||
running as the root user, and is used as a security mechanism. If 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
|
||||
required, even if Telodendria is unable to switch to the given user and
|
||||
group. It should be used as a sanity check to make sure the permissions are
|
||||
working properly.
|
||||
.It Ic data-dir 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 stdout|file|syslog
|
||||
The log configuration. Telodendria uses its own logging facility, which can output
|
||||
logs to standard output, a file, or the syslog. If set to
|
||||
.Ar file ,
|
||||
Telodendria will log to
|
||||
.Pa telodendria.log
|
||||
inside the
|
||||
.Ic data-dir .
|
||||
.Pp
|
||||
A number of child directives can
|
||||
be added to this directive to customize the log output:
|
||||
.Bl -tag -width Ds
|
||||
.It Ic level Ar error|warning|task|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 task
|
||||
shows tasks, 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 max-connections 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 max-cache 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
|
252
man/man7/contributing.7
Normal file
252
man/man7/contributing.7
Normal file
|
@ -0,0 +1,252 @@
|
|||
.Dd $Mdocdate: September 30 2022 $
|
||||
.Dt CONTRIBUTING 7
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm contributing
|
||||
.Nd Guide to contributing to Telodendria
|
||||
.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, as noted in
|
||||
.Xr telodendria 7 ,
|
||||
so it can be discussed and reviewed by the community.
|
||||
.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.
|
134
man/man7/onboarding.7
Normal file
134
man/man7/onboarding.7
Normal file
|
@ -0,0 +1,134 @@
|
|||
.Dd $Mdocdate: October 11 2022 $
|
||||
.Dt ONBOARDING 7
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm onboarding
|
||||
.Nd Guide to getting Telodendria running on your system.
|
||||
.Sh DESCRIPTION
|
||||
This page aims to provide a high-level overview of how to
|
||||
get Telodendria running on your system from source. It assumes
|
||||
that you're running OpenBSD, or know how to translate OpenBSD
|
||||
commands into your system's commands, and that you are using an
|
||||
an official distribution tarball. If you are using your system's
|
||||
package manager to install Telodendria, you should refer to the
|
||||
package maintainer's installation instructions instead of these
|
||||
instructions.
|
||||
.Pp
|
||||
If you're a package maintainer, or would like to be, these
|
||||
instructions might also be helpful for you as well; you can
|
||||
use them to learn how the process is designed to work, so you
|
||||
can mimic it on your system.
|
||||
.Sh PREREQUISITES
|
||||
To build Telodendria, you just need a POSIX system and C compiler.
|
||||
If your system does not ship with a C compiler, please install
|
||||
one before continuing with these directions.
|
||||
.Pp
|
||||
The build script relies on a POSIX shell implementation, as well
|
||||
as the standard userspace utilities specified by POSIX.
|
||||
.Pp
|
||||
This should be obvious, but you'll also need a way to
|
||||
download files to your machine, or otherwise copy them into place.
|
||||
You'll also need
|
||||
.Xr tar 1 ,
|
||||
and
|
||||
.Xr gzip 1
|
||||
to extract the downloaded tarball.
|
||||
.Sh GETTING THE CODE
|
||||
You can download the source code for Telodendria by going to
|
||||
the official website and downloading a tarball from the table.
|
||||
Otherwise, you can download the tarball and supplemental files
|
||||
directly:
|
||||
.Bd -literal -offset indent
|
||||
$ export VERSION=x.x.x # Set version of Telodendria you want
|
||||
$ ftp https://telodendria.io/pub/v$VERSION/Telodendria-v$VERSION.tar.gz
|
||||
$ ftp https://telodendria.io/pub/v$VERSION/Telodendria-v$VERSION.tar.gz.{sha256,sig}
|
||||
.Ed
|
||||
.Pp
|
||||
Before you extract the tarball, it is a good idea to verify both its checksum
|
||||
and its signature. Use
|
||||
.Xr sha256 1
|
||||
to verify that the download was not corrupted in transit:
|
||||
.Bd -literal -offset indent
|
||||
$ sha256 -C Telodendria-v$VERSION.tar.gz.sha256 Telodendria-v$VERSION.tar.gz
|
||||
.Ed
|
||||
.Pp
|
||||
Use
|
||||
.Xr signify 1
|
||||
to verify that the download was legitimately published by the Telodendria
|
||||
project:
|
||||
.Bd -literal -offset indent
|
||||
$ signify -V -p telodendria-signify.pub -m Telodendria-v$VERSION.tar.gz
|
||||
.Ed
|
||||
.Pp
|
||||
You can obtain
|
||||
.Pa telodendria-signify.pub
|
||||
from the official website. While you may absolutely obtain the tarball
|
||||
and checksum files from mirrors or other sources, do not obtain the public
|
||||
key file from any other sources than the official website. Downloads from
|
||||
other sources should be verified by the official public key only:
|
||||
.Bd -literal -offset indent
|
||||
$ ftp https://telodendria.io/telodendria-signify.pub
|
||||
.Ed
|
||||
.Pp
|
||||
Finally, only when the tarball is verified, you can extract it:
|
||||
.Bd -literal -offset indent
|
||||
$ tar -xzf Telodendria-v$VERSION.tar.gz
|
||||
$ cd Telodendria-v$VERSION
|
||||
.Ed
|
||||
.Sh BUILDING AND INSTALLING
|
||||
In the source directory, just run the following commands:
|
||||
.Bd -literal -offset indent
|
||||
$ . tools/env.sh
|
||||
$ td build
|
||||
$ doas td install
|
||||
.Ed
|
||||
.Pp
|
||||
This will compile the code, and install the output binary, along
|
||||
with all of the documentation and configuration files, to the system.
|
||||
.Pp
|
||||
.Sy Packagers:
|
||||
It might be beneficial to take a look at the install recipe in the
|
||||
.Xr td 8
|
||||
script so you know what files go where. Adapt the general procedure
|
||||
for your operating system.
|
||||
.Sh CONFIGURATION
|
||||
A sufficiently complete configuration file should have been installed
|
||||
to
|
||||
.Pa /etc/telodendria.conf .
|
||||
Open it with your preferred editor, and go through each line, making
|
||||
sure everything is set appropriately for your setup. Consult the
|
||||
.Xr telodendria.conf 5
|
||||
and
|
||||
.Xr Config 5
|
||||
pages for a comprehensive list of all the available options and a
|
||||
description for each option, as well as the general syntax of the
|
||||
configuration file. You'll most certainly have to set your
|
||||
server's name and base URL. Optionally, specify an identity server,
|
||||
and whether or not you want to enable federation and registration.
|
||||
All other options should have sane defaults for most systems.
|
||||
.Sh STARTING TELODENDRIA
|
||||
Once you've written the configuration file, it should just be as
|
||||
easy as starting the
|
||||
.Dv telodendria
|
||||
daemon:
|
||||
.Bd -literal -offset indent
|
||||
$ rcctl enable telodendria
|
||||
$ rcctl start telodendria
|
||||
.Ed
|
||||
.Pp
|
||||
It might be a good idea to check the log file to make sure everything
|
||||
started okay and is running fine.
|
||||
.Pp
|
||||
At this point, it would be a very good idea to configure a reverse
|
||||
proxy. The
|
||||
.Pa contrib/
|
||||
directory of the source code has an example
|
||||
.Xr relayd.conf 5
|
||||
file which can be installed as-is into the typical location, or
|
||||
integrated into an existing configuration. At some point in the
|
||||
future, a reverse proxy may not be required for TLS, but it will
|
||||
always be recommended because it provides more control and security.
|
||||
.Sh SEE ALSO
|
||||
.Xr td 8 ,
|
||||
.Xr telodendria.conf 5 ,
|
||||
.Xr telodendria 8
|
249
man/man7/telodendria.7
Normal file
249
man/man7/telodendria.7
Normal file
|
@ -0,0 +1,249 @@
|
|||
.Dd $Mdocdate: October 10 2022 $
|
||||
.Dt TELODENDRIA 7
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm Telodendria
|
||||
.Nd The terminal branches of an axon.
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is an open source Matrix homeserver written entirely from scratch in ANSI C and
|
||||
designed to be lightweight and simple, yet functional.
|
||||
.Pp
|
||||
.Sy Note:
|
||||
.Nm
|
||||
is under
|
||||
.Sy heavy
|
||||
development. Please see
|
||||
.Sx PROJECT STATUS .
|
||||
.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-issues:bancino.net;Report bugs and issues.
|
||||
#telodendria-patches:bancino.net;Submit code patches to the project.
|
||||
.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 PROJECT STATUS
|
||||
Telodendria is a very ambitious project. There's a lot that needs to happen yet
|
||||
before it is usable. At the moment, there's nothing here that even remotely resembles
|
||||
a Matrix homeserver. I'm still getting off the ground and building a foundation.
|
||||
.Pp
|
||||
But just because there's nothing here yet doesn't mean you should go away! I could
|
||||
always use help, so you are more than welcome to help out if you want things to go
|
||||
quicker. Please see the
|
||||
.Xr contributing 7
|
||||
page for details on how you can get involved. The CVS repository has a file called
|
||||
.Pa TODO.txt ,
|
||||
which contains a checklist of what has been done so far, and what's on the radar
|
||||
to be done. You can take a look at it once you've checked out the source code.
|
||||
If you'd like to help out, feel free to grab an item on the list that's not done
|
||||
yet and start getting patches written. When you're done, enjoy the satisfaction
|
||||
of crossing the item off the list yourself.
|
||||
.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
|
||||
At this time,
|
||||
.Nm
|
||||
does not have any tagged releases because it is not yet functional
|
||||
as a Matrix homeserver. Please checkout out
|
||||
.Sx PROJECT STATUS
|
||||
to see where things are currently at. When
|
||||
.Nm
|
||||
is mature enough to have a change log, it will go in this section.
|
||||
.Sh AUTHORS
|
||||
.Nm
|
||||
was started by Jordan Bancino <@jordan:bancino.net>.
|
||||
Contributions to the code, website, documentation, or other
|
||||
components of
|
||||
.Nm
|
||||
were made by the following people:
|
||||
.Bl -bullet
|
||||
.It
|
||||
Jonah Evans <@jonah:bancino.net>
|
||||
.El
|
||||
.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, however, 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.
|
241
man/man8/td.8
Normal file
241
man/man8/td.8
Normal file
|
@ -0,0 +1,241 @@
|
|||
.Dd $Mdocdate: October 11 2022 $
|
||||
.Dt TD 8
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm td
|
||||
.Nd Telodendria build script
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op recipe
|
||||
.Sh DESCRIPTION
|
||||
Telodendria uses a custom build script called
|
||||
.Nm .
|
||||
The
|
||||
.Nm
|
||||
script is not only a build script, however. It does all kinds of
|
||||
cool things like format the source code, and generate patch files.
|
||||
.Nm
|
||||
is the only supported way to develop Telodendria.
|
||||
.sp
|
||||
I opted to write a custom build script instead of just writing a
|
||||
.Pa Makefile
|
||||
because I felt that there is really no way to make a truly portable
|
||||
.Pa Makefile
|
||||
that could do everything I wanted, with the flexibility I wanted. I
|
||||
was doing a lot of research on the differences between the GNU and BSD
|
||||
versions, and I felt it just wasn't worth trying to reconsile the two
|
||||
when I could write a small and relatively robust POSIX shell script that
|
||||
would run on both GNU and BSD systems without any problems. I also
|
||||
think that shell scripts are a lot easier to read than complex
|
||||
.Pa Makefiles.
|
||||
They're easier to follow because they're not so cryptic.
|
||||
.sp
|
||||
The
|
||||
.Nm
|
||||
script is fairly intuitive. It operates somewhat like
|
||||
.Xr make 1
|
||||
in that it has recipes that you specify on the command line. To start
|
||||
using it, just run the following command in the Telodendria source
|
||||
directory:
|
||||
.Bd -literal -offset indent
|
||||
$ . tools/env.sh
|
||||
.Ed
|
||||
.sp
|
||||
.Sy Note:
|
||||
You will have to run the above command every time you start a new
|
||||
terminal session, as nothing is persisted to your system. I believe in
|
||||
non-invasive, fully self-contained tooling, so it is up to you to hook the
|
||||
Telodendria tools into your environment as you see fit if you want them to
|
||||
persist.
|
||||
.sp
|
||||
If you're going to be submitting patches, you should also configure a
|
||||
.Pa .env
|
||||
file in the project directory root, which
|
||||
.Nm
|
||||
will include automatically for you. See
|
||||
.Em FILES
|
||||
and
|
||||
.Em ENVIRONMENT .
|
||||
.sp
|
||||
Telodendria is designed to be light enough that it can be built from source
|
||||
on just about any operating system. It only requires an ANSI C compiler and a
|
||||
standard POSIX environment. To build the Telodendria binary, run
|
||||
.Nm
|
||||
with no arguments, or with the
|
||||
.Pa build
|
||||
recipe. This will produce
|
||||
.Pa build/telodendria ,
|
||||
which you can then install to your system and run as a daemon.
|
||||
.sp
|
||||
A complete list of recipes is below. Note that you can run multiple recipes
|
||||
with a single invocation of
|
||||
.Nm ,
|
||||
but recipes are run unconditionally; that is, even if a recipe fails, all the
|
||||
following recipes are still executed.
|
||||
.Bl -tag
|
||||
.It build
|
||||
Build the source code and generate the output binary. This is the default recipe,
|
||||
which means it runs if no other recipes are specified. This recipe is incremental;
|
||||
it only rebuilds sources that have been modifed since the last build, making
|
||||
subsequent builds faster.
|
||||
.It run
|
||||
Run the build binary with the development configuration in the
|
||||
.Pa contrib/
|
||||
directory. This recipe is used for quick testing during development. It is
|
||||
.Sy not
|
||||
the recommended way to run Telodendria in a production environment; it should only
|
||||
be used for development.
|
||||
.It clean
|
||||
Remove the
|
||||
.Pa build/
|
||||
directory and any ephemeral files in the source tree, such as
|
||||
.Pa .orig
|
||||
files. The build recipe does not place anything outside of
|
||||
.Pa build/ ,
|
||||
so you can usually just delete that directory and get the same effect.
|
||||
.It 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 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.
|
57
man/man8/telodendria.8
Normal file
57
man/man8/telodendria.8
Normal file
|
@ -0,0 +1,57 @@
|
|||
.Dd $Mdocdate: September 30 2022 $
|
||||
.Dt TELODENDRIA 8
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm telodendria
|
||||
.Nd Matrix homeserver daemon
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl nVv
|
||||
.Op Fl f Ar file
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a Matrix homeserver written entirely from scratch in ANSI C.
|
||||
It is designed to be lightweight and simple, yet functional.
|
||||
.sp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl f Ar file
|
||||
Specify an alternate configuration file. The default is
|
||||
.Pa /etc/telodendria.conf .
|
||||
.It Fl n
|
||||
Configtest mode. Only check the configuration file for validity.
|
||||
.It Fl V
|
||||
Only print the version information header.
|
||||
.It Fl v
|
||||
Verbose mode. This overrides the configuration file and sets the
|
||||
log level to
|
||||
.Em LOG_DEBUG
|
||||
.El
|
||||
.Sh ENVIRONMENT
|
||||
.Nm
|
||||
does not read any environment variables. All configuration should
|
||||
be done via the configuration file.
|
||||
.Sh FILES
|
||||
Just the configuration file and the data directory; see
|
||||
.Xr telodendria.conf 5
|
||||
for more details.
|
||||
.El
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
exits with a non-0 exit code if the configuration file is invalid, or
|
||||
one or more required paths is inaccessible.
|
||||
.Nm
|
||||
will print an error to the log and then terminate abnormally.
|
||||
.Pp
|
||||
.Nm
|
||||
exits with a code of 0 if the configuration file is valid, all
|
||||
paths and files required are accessible, and the HTTP listener starts
|
||||
as intended. If
|
||||
.Nm
|
||||
is sent a signal that it catches after it begins servicing requests, it
|
||||
will still exit with a code of 0 after it safely shuts down, because
|
||||
the bootstrap process completed successfully, and by all accounts,
|
||||
it ran normally and exitted normally.
|
||||
.Sh SEE ALSO
|
||||
.Xr telodendria 7 ,
|
||||
.Xr telodendria.conf 5
|
189
site/index.html
Normal file
189
site/index.html
Normal file
|
@ -0,0 +1,189 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="author" content="Jordan Bancino">
|
||||
<meta name="description"
|
||||
content="Telodendria, a Matrix homeserver written in ANSI C.">
|
||||
|
||||
<meta property="og:title"
|
||||
content="Telodendria | A Matrix Homeserver written in ANSI C.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url"
|
||||
content="https://telodendria.io">
|
||||
<meta property="og:description"
|
||||
content="Telodendria, a Matrix homeserver written in ANSI C.">
|
||||
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="icon" href="assets/Telodendria-196x196.png">
|
||||
|
||||
<title>Telodendria | A Matrix Homeserver written in ANSI C.</title>
|
||||
</head>
|
||||
<body>
|
||||
<img id="logo" src="/assets/Telodendria-500x500.png" alt="Telodendria Logo"/>
|
||||
<h1 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.
|
||||
Please see the <a href="/man/man7/telodendria.7.html#PROJECT_STATUS">Project Status</a>.
|
||||
</div>
|
||||
<h2 id="documentation">Documentation</h2>
|
||||
<p>
|
||||
<b>Telodendria</b>'s documentation is distributed with the source
|
||||
code as <code>man</code> pages, which contain all of the information
|
||||
on what <b>Telodendria</b> is, what its goals are, how to build the source,
|
||||
configure it, as well as contribute to the project. The <code>man</code>
|
||||
pages are also available online for convenience:
|
||||
</p>
|
||||
<p>User Documentation:</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Man Page</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man7/telodendria.7.html">telodendria(7)</a></td>
|
||||
<td>
|
||||
<b>Start here.</b> This page contains the project introduction, and provides
|
||||
information about it, such as its status, how to contact the developers, and what
|
||||
the source code license is.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man7/onboarding.7.html">onboarding(7)</a></td>
|
||||
<td>
|
||||
Setup instructions for OpenBSD.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man8/telodendria.8.html">telodendria(8)</a></td>
|
||||
<td>
|
||||
Command line usage for <b>Telodendria</b> administrators.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man5/telodendria.conf.5.html">telodendria.conf(5)</a></td>
|
||||
<td>
|
||||
Configuration file options.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man7/contributing.7.html">contributing(7)</a></td>
|
||||
<td>
|
||||
Contributing guide.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Developer Documentation:</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Man Page</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man8/td.8.html">td(8)</a></td>
|
||||
<td>
|
||||
Build script and patch generation instructions.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man3/Array.3.html">Array(3)</a></td>
|
||||
<td>
|
||||
Dynamically-sized array API.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man3/Base64.3.html">Base64(3)</a></td>
|
||||
<td>
|
||||
Base64 implementation with Matrix's "unpadded base64" support.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man3/HashMap.3.html">HashMap(3)</a></td>
|
||||
<td>
|
||||
A simple hash map implementation.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="man/man3/Queue.3.html">Queue(3)</a></td>
|
||||
<td>
|
||||
Basic fixed-size circular queue implementation.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<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>
|
||||
<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. 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>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Download</th>
|
||||
<th>Checksum</th>
|
||||
<th>Signature</th>
|
||||
</tr>
|
||||
<!--
|
||||
<tr>
|
||||
<td>v0.0.0</td>
|
||||
<td>
|
||||
<a href="/pub/v0.0.0/Telodendria-v0.0.0.tar.gz">
|
||||
Telodendria-v0.0.0.tar.gz
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/pub/v0.0.0/Telodendria-v0.0.0.tar.gz.sha256">
|
||||
SHA256
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/pub/v0.0.0/Telodendria-v0.0.0.tar.gz.sig">
|
||||
Signify
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
-->
|
||||
<tr>
|
||||
<td colspan="4" style="text-align: center;">
|
||||
No downloads here yet. See the
|
||||
<a href="/man/man7/telodendria.7.html#PROJECT_STATUS">Project Status</a> for more
|
||||
information.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
You can find the change log in the documentation.
|
||||
</p>
|
||||
<h2 id="resources">Resources</h2>
|
||||
<ul>
|
||||
<li><a target="_blank" href="/pub">Old <b>Telodendria</b> Versions</a></li>
|
||||
<li><a target="_blank" href="/spec.matrix.org">Matrix Spec Mirror</a>
|
||||
(<a href="/matrix-spec-v1.3.tar.gz">Download matrix-spec-v1.3.tar.gz</a>)
|
||||
</li>
|
||||
<li><a target="_blank" href="/patches">Public Patch Directory</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
© 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
</body>
|
||||
</html>
|
605
site/style.css
Normal file
605
site/style.css
Normal file
|
@ -0,0 +1,605 @@
|
|||
:root {
|
||||
--border-radius: 10px;
|
||||
|
||||
--color-snippet: #f6f8fa;
|
||||
--color-link: #0969da;
|
||||
--color-bg: #ffffff;
|
||||
--color-text: #24292f;
|
||||
|
||||
--color-table-border: #d0d7de;
|
||||
--color-table-accent: #f6f8fa;
|
||||
|
||||
--color-error-bg: #f44336;
|
||||
--color-error: white;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-snippet: #161b22;
|
||||
--color-link: #58a6ff;
|
||||
--color-bg: #0d1117;
|
||||
--color-text: #c9d1d9;
|
||||
|
||||
--color-table-border: #30363d;
|
||||
--color-table-accent: #161b22;
|
||||
|
||||
--color-error-bg: #b71c1c;
|
||||
--color-error: white;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: var(--color-snippet);
|
||||
border-radius: var(--border-radius);
|
||||
display: block;
|
||||
padding: 0.5em 1em 1.5em 1em;
|
||||
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--color-snippet);
|
||||
border-radius: calc(var(--border-radius) / 2);
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: #eee;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--color-table-border);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset;
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
font-size: .85em;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
padding: 2px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1, h4, h5, h6 {
|
||||
border-bottom: 1px dashed var(--color-table-border);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px dashed var(--color-table-border);
|
||||
}
|
||||
|
||||
h4, h5, h6 {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-link);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid var(--color-table-border);
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: var(--color-table-accent);
|
||||
}
|
||||
|
||||
/* Thanks Jonah! */
|
||||
#logo {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 50vw;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* MAN PAGE STYLES
|
||||
***************************************/
|
||||
|
||||
dl {
|
||||
margin-top: 0em;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
dt {
|
||||
margin-top: 1em;
|
||||
}
|
||||
pre {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.permalink {
|
||||
border-bottom: thin dotted;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
* {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Header and footer lines. */
|
||||
|
||||
table.head {
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
td.head-vol {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td.head-rtitle {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.head td, .foot td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
table.foot {
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
font-size: smaller;
|
||||
border: none;
|
||||
}
|
||||
td.foot-os {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Sections and paragraphs. */
|
||||
|
||||
.manual-text {
|
||||
margin-left: 3.8em;
|
||||
}
|
||||
.Nd {
|
||||
}
|
||||
section.Sh {
|
||||
}
|
||||
h1.Sh {
|
||||
text-align: left;
|
||||
border-bottom: none;
|
||||
margin-top: 1.2em;
|
||||
margin-bottom: 0.6em;
|
||||
margin-left: -3.2em;
|
||||
}
|
||||
section.Ss {
|
||||
}
|
||||
h2.Ss {
|
||||
margin-top: 1.2em;
|
||||
margin-bottom: 0.6em;
|
||||
margin-left: -1.2em;
|
||||
font-size: 105%;
|
||||
}
|
||||
.Pp {
|
||||
margin: 0.6em 0em;
|
||||
}
|
||||
.Sx {
|
||||
}
|
||||
|
||||
.Xr {
|
||||
color: var(--color-text);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Xr:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 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 {
|
||||
border: none;
|
||||
border-style: none !important;
|
||||
}
|
||||
|
||||
.tbl td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.HP {
|
||||
margin-left: 3.8em;
|
||||
text-indent: -3.8em;
|
||||
}
|
||||
|
||||
/* Semantic markup for command line utilities. */
|
||||
|
||||
table.Nm,
|
||||
table.Nm td {
|
||||
border: none;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
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. */
|
||||
|
||||
h1.Sh,
|
||||
h2.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;
|
||||
}
|
||||
|
||||
/* Overrides to avoid excessive margins on small devices. */
|
||||
|
||||
@media (max-width: 37.5em) {
|
||||
.manual-text {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
h1.Sh,
|
||||
h2.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;
|
||||
}
|
||||
}
|
||||
|
2
site/telodendria-signify.pub
Normal file
2
site/telodendria-signify.pub
Normal file
|
@ -0,0 +1,2 @@
|
|||
untrusted comment: signify public key
|
||||
RWTPPnWvnpee8NlygSggQqk5V5oghl6Ikq99bZl5IRQwiRMLaJnq82mw
|
174
src/Array.c
Normal file
174
src/Array.c
Normal file
|
@ -0,0 +1,174 @@
|
|||
#include <Array.h>
|
||||
|
||||
#ifndef ARRAY_BLOCK
|
||||
#define ARRAY_BLOCK 16
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.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)
|
||||
{
|
||||
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;
|
||||
}
|
212
src/Base64.c
Normal file
212
src/Base64.c
Normal file
|
@ -0,0 +1,212 @@
|
|||
#include <Base64.h>
|
||||
|
||||
#include <stdlib.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,6 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
@ -24,39 +23,36 @@
|
|||
*/
|
||||
#include <CanonicalJson.h>
|
||||
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Array.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <HashMap.h>
|
||||
#include <Array.h>
|
||||
#include <Json.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int
|
||||
int
|
||||
CanonicalJsonKeyCompare(void *k1, void *k2)
|
||||
{
|
||||
return strcmp((char *) k1, (char *) k2);
|
||||
}
|
||||
|
||||
int
|
||||
CanonicalJsonEncodeValue(JsonValue * value, Stream * out)
|
||||
static void
|
||||
CanonicalJsonEncodeValue(JsonValue * value, FILE * 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:
|
||||
length += CanonicalJsonEncode(JsonValueAsObject(value), out);
|
||||
CanonicalJsonEncode(JsonValueAsObject(value), out);
|
||||
break;
|
||||
case JSON_ARRAY:
|
||||
arr = JsonValueAsArray(value);
|
||||
len = ArraySize(arr);
|
||||
|
||||
StreamPutc(out, '[');
|
||||
length++;
|
||||
fputc('[', out);
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
|
@ -68,44 +64,39 @@ CanonicalJsonEncodeValue(JsonValue * value, Stream * out)
|
|||
continue;
|
||||
}
|
||||
|
||||
length += CanonicalJsonEncodeValue(aVal, out);
|
||||
CanonicalJsonEncodeValue(aVal, out);
|
||||
if (i < len - 1)
|
||||
{
|
||||
StreamPutc(out, ',');
|
||||
length++;
|
||||
fputc(',', out);
|
||||
}
|
||||
}
|
||||
|
||||
StreamPutc(out, ']');
|
||||
length++;
|
||||
fputc(']', out);
|
||||
break;
|
||||
default:
|
||||
length += JsonEncodeValue(value, out, JSON_DEFAULT);
|
||||
JsonEncodeValue(value, out);
|
||||
break;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int
|
||||
CanonicalJsonEncode(HashMap * object, Stream * out)
|
||||
CanonicalJsonEncode(HashMap * object, FILE * out)
|
||||
{
|
||||
char *key;
|
||||
JsonValue *value;
|
||||
Array *keys;
|
||||
size_t i;
|
||||
size_t keyCount;
|
||||
int length;
|
||||
|
||||
if (!object)
|
||||
if (!object || !out)
|
||||
{
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
keys = ArrayCreate();
|
||||
if (!keys)
|
||||
{
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (HashMapIterate(object, &key, (void **) &value))
|
||||
|
@ -115,11 +106,7 @@ CanonicalJsonEncode(HashMap * object, Stream * out)
|
|||
|
||||
ArraySort(keys, CanonicalJsonKeyCompare);
|
||||
|
||||
/* The total number of bytes written */
|
||||
length = 0;
|
||||
|
||||
StreamPutc(out, '{');
|
||||
length++;
|
||||
fputc('{', out);
|
||||
|
||||
keyCount = ArraySize(keys);
|
||||
for (i = 0; i < keyCount; i++)
|
||||
|
@ -141,21 +128,18 @@ CanonicalJsonEncode(HashMap * object, Stream * out)
|
|||
continue;
|
||||
}
|
||||
|
||||
length += JsonEncodeString(key, out);
|
||||
StreamPutc(out, ':');
|
||||
length++;
|
||||
length += CanonicalJsonEncodeValue(value, out);
|
||||
JsonEncodeString(key, out);
|
||||
fputc(':', out);
|
||||
CanonicalJsonEncodeValue(value, out);
|
||||
|
||||
if (i < keyCount - 1)
|
||||
{
|
||||
StreamPutc(out, ',');
|
||||
length++;
|
||||
fputc(',', out);
|
||||
}
|
||||
}
|
||||
|
||||
StreamPutc(out, '}');
|
||||
length++;
|
||||
fputc('}', out);
|
||||
|
||||
ArrayFree(keys);
|
||||
return length;
|
||||
return 1;
|
||||
}
|
||||
|
|
608
src/Config.c
608
src/Config.c
|
@ -1,232 +1,452 @@
|
|||
/*
|
||||
* 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 <Config.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
|
||||
#ifndef CONFIG_BUFFER_BLOCK
|
||||
#define CONFIG_BUFFER_BLOCK 32
|
||||
#endif
|
||||
|
||||
void
|
||||
ConfigParse(HashMap * config, Config *tConfig)
|
||||
struct ConfigDirective {
|
||||
Array *values;
|
||||
HashMap *children;
|
||||
};
|
||||
|
||||
struct ConfigParseResult {
|
||||
unsigned int ok : 1;
|
||||
union {
|
||||
size_t lineNumber;
|
||||
HashMap *confMap;
|
||||
} data;
|
||||
};
|
||||
|
||||
typedef enum ConfigToken {
|
||||
TOKEN_UNKNOWN,
|
||||
TOKEN_NAME,
|
||||
TOKEN_MACRO_ASSIGNMENT,
|
||||
TOKEN_VALUE,
|
||||
TOKEN_SEMICOLON,
|
||||
TOKEN_BLOCK_OPEN,
|
||||
TOKEN_BLOCK_CLOSE,
|
||||
TOKEN_MACRO,
|
||||
TOKEN_EOF
|
||||
} ConfigToken;
|
||||
|
||||
typedef struct ConfigParserState {
|
||||
FILE *stream;
|
||||
unsigned int line;
|
||||
|
||||
char *token;
|
||||
size_t tokenSize;
|
||||
size_t tokenLen;
|
||||
ConfigToken tokenType;
|
||||
|
||||
HashMap *macroMap;
|
||||
|
||||
} ConfigParserState;
|
||||
|
||||
unsigned int
|
||||
ConfigParseResultOk(ConfigParseResult *result)
|
||||
{
|
||||
return result ? result->ok : 0;
|
||||
}
|
||||
|
||||
size_t
|
||||
ConfigParseResultLineNumber(ConfigParseResult *result)
|
||||
{
|
||||
return result && !result->ok ? result->data.lineNumber : 0;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
ConfigParseResultGet(ConfigParseResult *result)
|
||||
{
|
||||
return result && result->ok ? result->data.confMap : NULL;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigParseResultFree(ConfigParseResult *result)
|
||||
{
|
||||
/*
|
||||
* Note that if the parse was valid, the hash map
|
||||
* needs to be freed separately.
|
||||
*/
|
||||
free(result);
|
||||
}
|
||||
|
||||
Array *
|
||||
ConfigValuesGet(ConfigDirective *directive)
|
||||
{
|
||||
return directive ? directive->values : NULL;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
ConfigChildrenGet(ConfigDirective *directive)
|
||||
{
|
||||
return directive ? directive->children : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes a void pointer because it is only used with
|
||||
* HashMapIterate(), which requires a pointer to a function
|
||||
* that takes a void pointer.
|
||||
*/
|
||||
static void
|
||||
ConfigDirectiveFree(void *ptr)
|
||||
{
|
||||
ConfigDirective *directive = ptr;
|
||||
size_t i;
|
||||
|
||||
if (!config)
|
||||
if (!directive)
|
||||
{
|
||||
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))
|
||||
for (i = 0; i < ArraySize(directive->values); i++)
|
||||
{
|
||||
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;
|
||||
free(ArrayGet(directive->values, i));
|
||||
}
|
||||
|
||||
int
|
||||
ConfigExists(Db * db)
|
||||
{
|
||||
return DbExists(db, 1, "config");
|
||||
}
|
||||
ArrayFree(directive->values);
|
||||
ConfigFree(directive->children);
|
||||
|
||||
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;
|
||||
free(directive);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigLock(Db * db, Config *config)
|
||||
ConfigFree(HashMap *conf)
|
||||
{
|
||||
DbRef *ref = DbLock(db, 1, "config");
|
||||
|
||||
if (!ref)
|
||||
{
|
||||
config->ok = 0;
|
||||
config->err = "Couldn't lock configuration.";
|
||||
HashMapIterate(conf, ConfigDirectiveFree);
|
||||
HashMapFree(conf);
|
||||
}
|
||||
|
||||
ConfigParse(DbJson(ref), config);
|
||||
if (config->ok)
|
||||
static ConfigParserState *
|
||||
ConfigParserStateCreate(FILE * stream)
|
||||
{
|
||||
config->db = db;
|
||||
config->ref = ref;
|
||||
ConfigParserState *state = malloc(sizeof(ConfigParserState));
|
||||
if (!state)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state->macroMap = HashMapCreate();
|
||||
|
||||
if (!state->macroMap)
|
||||
{
|
||||
free(state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state->stream = stream;
|
||||
state->line = 1;
|
||||
state->token = NULL;
|
||||
state->tokenSize = 0;
|
||||
state->tokenLen = 0;
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigParserStateFree(ConfigParserState *state)
|
||||
{
|
||||
if (!state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
free(state->token);
|
||||
|
||||
HashMapIterate(state->macroMap, free);
|
||||
HashMapFree(state->macroMap);
|
||||
|
||||
free(state);
|
||||
}
|
||||
|
||||
static int
|
||||
ConfigIsNameChar(int c)
|
||||
{
|
||||
return isdigit(c) || isalpha(c) || (c == '-' || c == '_');
|
||||
}
|
||||
|
||||
static char
|
||||
ConfigConsumeWhitespace(ConfigParserState *state)
|
||||
{
|
||||
int c;
|
||||
while (isspace(c = fgetc(state->stream)))
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
state->line++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigConsumeLine(ConfigParserState *state)
|
||||
{
|
||||
while (fgetc(state->stream) != '\n');
|
||||
state->line++;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigTokenSeek(ConfigParserState *state)
|
||||
{
|
||||
int c;
|
||||
|
||||
/* If we already hit EOF, don't do anything */
|
||||
if (state->tokenType == TOKEN_EOF) {
|
||||
return;
|
||||
}
|
||||
while ((c = ConfigConsumeWhitespace(state)) == '#') {
|
||||
ConfigConsumeLine(state);
|
||||
}
|
||||
|
||||
/*
|
||||
* After all whitespace and comments are consumed, identify the
|
||||
* token by looking at the next character
|
||||
*/
|
||||
|
||||
if (feof(state->stream)) {
|
||||
state->tokenType = TOKEN_EOF;
|
||||
return;
|
||||
}
|
||||
if (ConfigIsNameChar(c)) {
|
||||
state->tokenLen = 0;
|
||||
|
||||
/* Read the key/macro into state->token */
|
||||
if (!state->token) {
|
||||
state->tokenSize = CONFIG_BUFFER_BLOCK;
|
||||
state->token = malloc(CONFIG_BUFFER_BLOCK);
|
||||
}
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
while (ConfigIsNameChar((c = fgetc(state->stream)))) {
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
if (state->tokenLen >= state->tokenSize) {
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ConfigUnlock(Config *config)
|
||||
{
|
||||
Db *db;
|
||||
DbRef *dbRef;
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
|
||||
if (!config->ok)
|
||||
{
|
||||
return 0;
|
||||
if (!isspace(c)) {
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
} else {
|
||||
state->tokenType = TOKEN_NAME;
|
||||
|
||||
if (c == '\n') {
|
||||
state->line++;
|
||||
}
|
||||
}
|
||||
|
||||
db = config->db;
|
||||
dbRef = config->ref;
|
||||
} else {
|
||||
switch (c) {
|
||||
case '=':
|
||||
state->tokenType = TOKEN_MACRO_ASSIGNMENT;
|
||||
break;
|
||||
case '"':
|
||||
state->tokenLen = 0;
|
||||
state->tokenType = TOKEN_VALUE;
|
||||
|
||||
ConfigFree(config);
|
||||
config->ok = 0;
|
||||
/* read the value into state->curtok */
|
||||
while ((c = fgetc(state->stream)) != '"') {
|
||||
if (c == '\n') {
|
||||
state->line++;
|
||||
}
|
||||
/*
|
||||
* End of the stream reached without finding
|
||||
* a closing quote
|
||||
*/
|
||||
if (feof(state->stream)) {
|
||||
state->tokenType = TOKEN_EOF;
|
||||
break;
|
||||
}
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
return DbUnlock(db, dbRef);
|
||||
if (state->tokenLen >= state->tokenSize) {
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
int
|
||||
ConfigLogLevelToSyslog(ConfigLogLevel level)
|
||||
}
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
break;
|
||||
case ';':
|
||||
state->tokenType = TOKEN_SEMICOLON;
|
||||
break;
|
||||
case '{':
|
||||
state->tokenType = TOKEN_BLOCK_OPEN;
|
||||
break;
|
||||
case '}':
|
||||
state->tokenType = TOKEN_BLOCK_CLOSE;
|
||||
break;
|
||||
case '$':
|
||||
state->tokenLen = 0;
|
||||
/* read the macro name into state->curtok */
|
||||
while (ConfigIsNameChar(c = fgetc(state->stream))) {
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
if (state->tokenLen >= state->tokenSize) {
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
}
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
state->tokenType = TOKEN_MACRO;
|
||||
|
||||
ungetc(c, state->stream);
|
||||
break;
|
||||
default:
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resize curtok to only use the bytes it needs */
|
||||
if (state->tokenLen) {
|
||||
state->tokenSize = state->tokenLen;
|
||||
state->token = realloc(state->token, state->tokenSize);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
ConfigExpect(ConfigParserState *state, ConfigToken tokenType)
|
||||
{
|
||||
switch (level)
|
||||
return state->tokenType == tokenType;
|
||||
}
|
||||
|
||||
|
||||
static HashMap *
|
||||
ConfigParseBlock(ConfigParserState *state, int 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;
|
||||
HashMap *block = HashMapCreate();
|
||||
|
||||
ConfigTokenSeek(state);
|
||||
|
||||
while (ConfigExpect(state, TOKEN_NAME)) {
|
||||
char *name = malloc(state->tokenLen + 1);
|
||||
strcpy(name, state->token);
|
||||
|
||||
ConfigTokenSeek(state);
|
||||
if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) {
|
||||
ConfigDirective *directive;
|
||||
|
||||
directive = malloc(sizeof(ConfigDirective));
|
||||
directive->children = NULL;
|
||||
directive->values = ArrayCreate();
|
||||
|
||||
while (ConfigExpect(state, TOKEN_VALUE) ||
|
||||
ConfigExpect(state, TOKEN_MACRO)) {
|
||||
|
||||
char *dval;
|
||||
char *dvalCpy;
|
||||
|
||||
if (ConfigExpect(state, TOKEN_VALUE)) {
|
||||
dval = state->token;
|
||||
} else if (ConfigExpect(state, TOKEN_MACRO)) {
|
||||
dval = HashMapGet(state->macroMap, state->token);
|
||||
if (!dval) {
|
||||
goto error;
|
||||
}
|
||||
return LOG_INFO;
|
||||
} else {
|
||||
dval = NULL; /* Should never happen */
|
||||
}
|
||||
|
||||
/* dval is a pointer which is overwritten with the next token. */
|
||||
dvalCpy = malloc(strlen(dval) + 1);
|
||||
strcpy(dvalCpy, dval);
|
||||
|
||||
ArrayAdd(directive->values, dvalCpy);
|
||||
ConfigTokenSeek(state);
|
||||
}
|
||||
|
||||
if (ConfigExpect(state, TOKEN_BLOCK_OPEN)) {
|
||||
/* token_seek(state); */
|
||||
directive->children = ConfigParseBlock(state, level + 1);
|
||||
if (!directive->children) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Append this directive to the current block,
|
||||
* overwriting a directive at this level with the same name.
|
||||
*
|
||||
* Note that if a value already exists with this name, it is
|
||||
* returned by HashMapSet() and then immediately passed to
|
||||
* ConfigDirectiveFree(). If the value does not exist, then
|
||||
* NULL is sent to ConfigDirectiveFree(), making it a no-op.
|
||||
*/
|
||||
ConfigDirectiveFree(HashMapSet(block, name, directive));
|
||||
|
||||
} else if (ConfigExpect(state, TOKEN_MACRO_ASSIGNMENT)) {
|
||||
ConfigTokenSeek(state);
|
||||
if (ConfigExpect(state, TOKEN_VALUE)) {
|
||||
char * valueCopy = malloc(strlen(state->token) + 1);
|
||||
strcpy(valueCopy, state->token);
|
||||
free(HashMapSet(state->macroMap, name, state->token));
|
||||
ConfigTokenSeek(state);
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ConfigExpect(state, TOKEN_SEMICOLON)) {
|
||||
goto error;
|
||||
}
|
||||
ConfigTokenSeek(state);
|
||||
}
|
||||
|
||||
if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF)) {
|
||||
ConfigTokenSeek(state);
|
||||
return block;
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
/* Only free the very top level, because this will recurse */
|
||||
if (!level) {
|
||||
ConfigFree(block);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConfigParseResult *
|
||||
ConfigParse(FILE * stream)
|
||||
{
|
||||
ConfigParseResult *result;
|
||||
HashMap *conf;
|
||||
ConfigParserState *state;
|
||||
|
||||
result = malloc(sizeof(ConfigParseResult));
|
||||
state = ConfigParserStateCreate(stream);
|
||||
conf = ConfigParseBlock(state, 0);
|
||||
|
||||
if (!conf) {
|
||||
result->ok = 0;
|
||||
result->data.lineNumber = state->line;
|
||||
} else {
|
||||
result->ok = 1;
|
||||
result->data.confMap = conf;
|
||||
}
|
||||
|
||||
ConfigParserStateFree(state);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
@ -22,103 +21,120 @@
|
|||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Db.h>
|
||||
|
||||
#include <Room.h>
|
||||
#include <Memory.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Db.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <Schema/RoomCreateRequest.h>
|
||||
|
||||
struct Room
|
||||
struct Db
|
||||
{
|
||||
Db *db;
|
||||
DbRef *ref;
|
||||
const char *dir;
|
||||
size_t cacheSize;
|
||||
size_t maxCache;
|
||||
pthread_mutex_t lock;
|
||||
|
||||
char *id;
|
||||
int version;
|
||||
HashMap *cache;
|
||||
};
|
||||
|
||||
Room *
|
||||
RoomCreate(Db * db, RoomCreateRequest * req)
|
||||
struct DbRef
|
||||
{
|
||||
(void) db;
|
||||
(void) req;
|
||||
return NULL;
|
||||
}
|
||||
HashMap *json;
|
||||
pthread_mutex_t lock;
|
||||
|
||||
Room *
|
||||
RoomLock(Db * db, char *id)
|
||||
{
|
||||
DbRef *ref;
|
||||
Room *room;
|
||||
unsigned long ts;
|
||||
char *file;
|
||||
};
|
||||
|
||||
if (!db || !id)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ref = DbLock(db, 3, "rooms", id, "state");
|
||||
|
||||
if (!ref)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
room = Malloc(sizeof(Room));
|
||||
if (!room)
|
||||
{
|
||||
DbUnlock(db, ref);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
room->db = db;
|
||||
room->ref = ref;
|
||||
room->id = StrDuplicate(id);
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
int
|
||||
RoomUnlock(Room * room)
|
||||
Db *
|
||||
DbOpen(const char *dir, size_t cache)
|
||||
{
|
||||
Db *db;
|
||||
DbRef *ref;
|
||||
|
||||
if (!room)
|
||||
if (!dir)
|
||||
{
|
||||
return 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
db = room->db;
|
||||
ref = room->ref;
|
||||
|
||||
Free(room->id);
|
||||
Free(room);
|
||||
|
||||
return DbUnlock(db, ref);
|
||||
db = Malloc(sizeof(Db));
|
||||
if (!db)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *
|
||||
RoomIdGet(Room * room)
|
||||
db->dir = dir;
|
||||
db->maxCache = cache;
|
||||
|
||||
pthread_mutex_init(&db->lock, NULL);
|
||||
|
||||
if (db->maxCache)
|
||||
{
|
||||
return room ? room->id : NULL;
|
||||
db->cache = HashMapCreate();
|
||||
if (!db->cache)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
RoomVersionGet(Room * room)
|
||||
return db;
|
||||
}
|
||||
|
||||
void
|
||||
DbClose(Db * db)
|
||||
{
|
||||
return room ? room->version : 0;
|
||||
if (!db)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&db->lock);
|
||||
HashMapFree(db->cache);
|
||||
|
||||
Free(db);
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbCreate(Db * db, char *prefix, char *key)
|
||||
{
|
||||
if (!db || !prefix || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbLock(Db * db, char *prefix, char *key)
|
||||
{
|
||||
if (!db || !prefix || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
DbUnlock(Db * db, DbRef * ref)
|
||||
{
|
||||
if (!db || !ref)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
RoomStateGet(Room * room)
|
||||
DbJson(DbRef * ref)
|
||||
{
|
||||
if (!room)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return ref ? ref->json : NULL;
|
||||
}
|
300
src/HashMap.c
Normal file
300
src/HashMap.c
Normal file
|
@ -0,0 +1,300 @@
|
|||
#include <HashMap.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct HashMapBucket {
|
||||
uint32_t hash;
|
||||
void *value;
|
||||
} HashMapBucket;
|
||||
|
||||
struct HashMap {
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
HashMapBucket **entries;
|
||||
|
||||
float maxLoad;
|
||||
};
|
||||
|
||||
static uint32_t
|
||||
HashMapHashKey(const char *key)
|
||||
{
|
||||
uint32_t hash = 2166136261u;
|
||||
size_t i = 0;
|
||||
|
||||
while (key[i])
|
||||
{
|
||||
hash ^= (uint8_t) 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 = calloc(map->capacity, sizeof(HashMapBucket *));
|
||||
if (!newEntries)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
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->entries = calloc(map->capacity, sizeof(HashMapBucket *));
|
||||
if (!map->entries)
|
||||
{
|
||||
free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapDelete(HashMap *map, const char *key)
|
||||
{
|
||||
uint32_t hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = HashMapHashKey(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
bucket->hash = 0;
|
||||
return bucket->value;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapFree(HashMap *map)
|
||||
{
|
||||
if (map)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < map->capacity; i++)
|
||||
{
|
||||
if (map->entries[i])
|
||||
{
|
||||
free(map->entries[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(map);
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapGet(HashMap *map, const char *key)
|
||||
{
|
||||
uint32_t hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = HashMapHashKey(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;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapIterate(HashMap *map, void (*iteratorFunc)(void *))
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < map->capacity; i++)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[i];
|
||||
|
||||
if (bucket)
|
||||
{
|
||||
iteratorFunc(bucket->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HashMapMaxLoadSet(HashMap *map, float load)
|
||||
{
|
||||
if (!map)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->maxLoad = load;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
HashMapSet(HashMap *map, const char *key, void *value)
|
||||
{
|
||||
uint32_t hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key || !value)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (map->count + 1 > map->capacity * map->maxLoad)
|
||||
{
|
||||
HashMapGrow(map);
|
||||
}
|
||||
|
||||
hash = HashMapHashKey(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
bucket = malloc(sizeof(HashMapBucket));
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bucket->hash = hash;
|
||||
bucket->value = value;
|
||||
map->entries[index] = bucket;
|
||||
map->count++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bucket->hash)
|
||||
{
|
||||
bucket->hash = hash;
|
||||
bucket->value = value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
void *oldValue = bucket->value;
|
||||
bucket->value = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
73
src/Html.c
73
src/Html.c
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Html.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <Telodendria.h>
|
||||
|
||||
void
|
||||
HtmlBegin(Stream * stream, char *title)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!stream)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StreamPrintf(stream,
|
||||
"<!DOCTYPE html>"
|
||||
"<html>"
|
||||
"<head>"
|
||||
"<meta charset=\"utf-8\">"
|
||||
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
||||
"<title>%s | Telodendria</title>"
|
||||
"<link rel=\"stylesheet\" href=\"/_matrix/static/telodendria.css\">"
|
||||
"<script src=\"/_matrix/static/telodendria.js\"></script>"
|
||||
"</head>"
|
||||
"<body>"
|
||||
"<pre class=\"logo\">"
|
||||
,title
|
||||
);
|
||||
|
||||
for (i = 0; i < TELODENDRIA_LOGO_HEIGHT; i++)
|
||||
{
|
||||
StreamPrintf(stream, "%s\n", TelodendriaLogo[i]);
|
||||
}
|
||||
|
||||
StreamPrintf(stream,
|
||||
"</pre>"
|
||||
"<h1>%s</h1>"
|
||||
,title);
|
||||
}
|
||||
|
||||
void
|
||||
HtmlEnd(Stream * stream)
|
||||
{
|
||||
StreamPuts(stream,
|
||||
"</body>"
|
||||
"</html>");
|
||||
}
|
526
src/Http.c
Normal file
526
src/Http.c
Normal file
|
@ -0,0 +1,526 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Http.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Constants.h>
|
||||
#include <HashMap.h>
|
||||
|
||||
const char *
|
||||
HttpRequestMethodToString(const HttpRequestMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case HTTP_GET:
|
||||
return "GET";
|
||||
case HTTP_HEAD:
|
||||
return "HEAD";
|
||||
case HTTP_POST:
|
||||
return "POST";
|
||||
case HTTP_PUT:
|
||||
return "PUT";
|
||||
case HTTP_DELETE:
|
||||
return "DELETE";
|
||||
case HTTP_CONNECT:
|
||||
return "CONNECT";
|
||||
case HTTP_OPTIONS:
|
||||
return "OPTIONS";
|
||||
case HTTP_TRACE:
|
||||
return "TRACE";
|
||||
case HTTP_PATCH:
|
||||
return "PATCH";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestMethod
|
||||
HttpRequestMethodFromString(const char *str)
|
||||
{
|
||||
if (strcmp(str, "GET") == 0)
|
||||
{
|
||||
return HTTP_GET;
|
||||
}
|
||||
|
||||
if (strcmp(str, "HEAD") == 0)
|
||||
{
|
||||
return HTTP_HEAD;
|
||||
}
|
||||
|
||||
if (strcmp(str, "POST") == 0)
|
||||
{
|
||||
return HTTP_POST;
|
||||
}
|
||||
|
||||
if (strcmp(str, "PUT") == 0)
|
||||
{
|
||||
return HTTP_PUT;
|
||||
}
|
||||
|
||||
if (strcmp(str, "DELETE") == 0)
|
||||
{
|
||||
return HTTP_DELETE;
|
||||
}
|
||||
|
||||
if (strcmp(str, "CONNECT") == 0)
|
||||
{
|
||||
return HTTP_CONNECT;
|
||||
}
|
||||
|
||||
if (strcmp(str, "OPTIONS") == 0)
|
||||
{
|
||||
return HTTP_OPTIONS;
|
||||
}
|
||||
|
||||
if (strcmp(str, "TRACE") == 0)
|
||||
{
|
||||
return HTTP_TRACE;
|
||||
}
|
||||
|
||||
if (strcmp(str, "PATCH") == 0)
|
||||
{
|
||||
return HTTP_PATCH;
|
||||
}
|
||||
|
||||
return HTTP_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
const char *
|
||||
HttpStatusToString(const HttpStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case HTTP_CONTINUE:
|
||||
return "Continue";
|
||||
case HTTP_SWITCHING_PROTOCOLS:
|
||||
return "Switching Protocols";
|
||||
case HTTP_EARLY_HINTS:
|
||||
return "Early Hints";
|
||||
case HTTP_OK:
|
||||
return "Ok";
|
||||
case HTTP_CREATED:
|
||||
return "Created";
|
||||
case HTTP_ACCEPTED:
|
||||
return "Accepted";
|
||||
case HTTP_NON_AUTHORITATIVE_INFORMATION:
|
||||
return "Non-Authoritative Information";
|
||||
case HTTP_NO_CONTENT:
|
||||
return "No Content";
|
||||
case HTTP_RESET_CONTENT:
|
||||
return "Reset Content";
|
||||
case HTTP_PARTIAL_CONTENT:
|
||||
return "Partial Content";
|
||||
case HTTP_MULTIPLE_CHOICES:
|
||||
return "Multiple Choices";
|
||||
case HTTP_MOVED_PERMANENTLY:
|
||||
return "Moved Permanently";
|
||||
case HTTP_FOUND:
|
||||
return "Found";
|
||||
case HTTP_SEE_OTHER:
|
||||
return "See Other";
|
||||
case HTTP_NOT_MODIFIED:
|
||||
return "Not Modified";
|
||||
case HTTP_TEMPORARY_REDIRECT:
|
||||
return "Temporary Redirect";
|
||||
case HTTP_PERMANENT_REDIRECT:
|
||||
return "Permanent Redirect";
|
||||
case HTTP_BAD_REQUEST:
|
||||
return "Bad Request";
|
||||
case HTTP_UNAUTHORIZED:
|
||||
return "Unauthorized";
|
||||
case HTTP_FORBIDDEN:
|
||||
return "Forbidden";
|
||||
case HTTP_NOT_FOUND:
|
||||
return "Not Found";
|
||||
case HTTP_METHOD_NOT_ALLOWED:
|
||||
return "Method Not Allowed";
|
||||
case HTTP_NOT_ACCEPTABLE:
|
||||
return "Not Acceptable";
|
||||
case HTTP_PROXY_AUTH_REQUIRED:
|
||||
return "Proxy Authentication Required";
|
||||
case HTTP_REQUEST_TIMEOUT:
|
||||
return "Request Timeout";
|
||||
case HTTP_CONFLICT:
|
||||
return "Conflict";
|
||||
case HTTP_GONE:
|
||||
return "Gone";
|
||||
case HTTP_LENGTH_REQUIRED:
|
||||
return "Length Required";
|
||||
case HTTP_PRECONDITION_FAILED:
|
||||
return "Precondition Failed";
|
||||
case HTTP_PAYLOAD_TOO_LARGE:
|
||||
return "Payload Too Large";
|
||||
case HTTP_URI_TOO_LONG:
|
||||
return "URI Too Long";
|
||||
case HTTP_UNSUPPORTED_MEDIA_TYPE:
|
||||
return "Unsupported Media Type";
|
||||
case HTTP_RANGE_NOT_SATISFIABLE:
|
||||
return "Range Not Satisfiable";
|
||||
case HTTP_EXPECTATION_FAILED:
|
||||
return "Expectation Failed";
|
||||
case HTTP_TEAPOT:
|
||||
return "I'm a Teapot";
|
||||
case HTTP_UPGRADE_REQUIRED:
|
||||
return "Upgrade Required";
|
||||
case HTTP_PRECONDITION_REQUIRED:
|
||||
return "Precondition Required";
|
||||
case HTTP_TOO_MANY_REQUESTS:
|
||||
return "Too Many Requests";
|
||||
case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
|
||||
return "Request Header Fields Too Large";
|
||||
case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
|
||||
return "Unavailable For Legal Reasons";
|
||||
case HTTP_INTERNAL_SERVER_ERROR:
|
||||
return "Internal Server Error";
|
||||
case HTTP_NOT_IMPLEMENTED:
|
||||
return "Not Implemented";
|
||||
case HTTP_BAD_GATEWAY:
|
||||
return "Bad Gateway";
|
||||
case HTTP_SERVICE_UNAVAILABLE:
|
||||
return "Service Unavailable";
|
||||
case HTTP_GATEWAY_TIMEOUT:
|
||||
return "Gateway Timeout";
|
||||
case HTTP_VERSION_NOT_SUPPORTED:
|
||||
return "Version Not Supported";
|
||||
case HTTP_VARIANT_ALSO_NEGOTIATES:
|
||||
return "Variant Also Negotiates";
|
||||
case HTTP_NOT_EXTENDED:
|
||||
return "Not Extended";
|
||||
case HTTP_NETWORK_AUTH_REQUIRED:
|
||||
return "Network Authentication Required";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
HttpUrlEncode(char *str)
|
||||
{
|
||||
size_t size;
|
||||
size_t len;
|
||||
char *encoded;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = TELODENDRIA_STRING_CHUNK;
|
||||
len = 0;
|
||||
encoded = Malloc(size);
|
||||
if (!encoded)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*str)
|
||||
{
|
||||
char c = *str;
|
||||
|
||||
if (len >= size - 4)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
size += TELODENDRIA_STRING_CHUNK;
|
||||
tmp = Realloc(encoded, size);
|
||||
if (!tmp)
|
||||
{
|
||||
Free(encoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encoded = tmp;
|
||||
}
|
||||
|
||||
/* Control characters and extended characters */
|
||||
if (c <= 0x1F || c >= 0x7F)
|
||||
{
|
||||
goto percentEncode;
|
||||
}
|
||||
|
||||
/* Reserved and unsafe characters */
|
||||
switch (c)
|
||||
{
|
||||
case '$':
|
||||
case '&':
|
||||
case '+':
|
||||
case ',':
|
||||
case '/':
|
||||
case ':':
|
||||
case ';':
|
||||
case '=':
|
||||
case '?':
|
||||
case '@':
|
||||
case ' ':
|
||||
case '"':
|
||||
case '<':
|
||||
case '>':
|
||||
case '#':
|
||||
case '%':
|
||||
case '{':
|
||||
case '}':
|
||||
case '|':
|
||||
case '\\':
|
||||
case '^':
|
||||
case '~':
|
||||
case '[':
|
||||
case ']':
|
||||
case '`':
|
||||
goto percentEncode;
|
||||
break;
|
||||
default:
|
||||
encoded[len] = c;
|
||||
len++;
|
||||
str++;
|
||||
continue;
|
||||
}
|
||||
|
||||
percentEncode:
|
||||
encoded[len] = '%';
|
||||
len++;
|
||||
snprintf(encoded + len, 3, "%2X", c);
|
||||
len += 2;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
encoded[len] = '\0';
|
||||
return encoded;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpUrlDecode(char *str)
|
||||
{
|
||||
size_t i;
|
||||
size_t inputLen;
|
||||
char *decoded;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
inputLen = strlen(str);
|
||||
decoded = Malloc(inputLen + 1);
|
||||
|
||||
if (!decoded)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*str)
|
||||
{
|
||||
char c = *str;
|
||||
|
||||
if (c == '%')
|
||||
{
|
||||
unsigned int d;
|
||||
|
||||
str++;
|
||||
|
||||
if (sscanf(str, "%2X", &d) != 1)
|
||||
{
|
||||
/* Decoding error */
|
||||
Free(decoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!d)
|
||||
{
|
||||
/* Null character given, don't put that in the string. */
|
||||
continue;
|
||||
}
|
||||
|
||||
c = (char) d;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
decoded[i] = c;
|
||||
i++;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
decoded[i] = '\0';
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpParamDecode(char *in)
|
||||
{
|
||||
HashMap *params;
|
||||
|
||||
if (!in)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
params = HashMapCreate();
|
||||
if (!params)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*in)
|
||||
{
|
||||
char *buf;
|
||||
size_t allocated;
|
||||
size_t len;
|
||||
|
||||
char *decKey;
|
||||
char *decVal;
|
||||
|
||||
/* Read in key */
|
||||
|
||||
allocated = TELODENDRIA_STRING_CHUNK;
|
||||
buf = Malloc(allocated);
|
||||
len = 0;
|
||||
|
||||
while (*in && *in != '=')
|
||||
{
|
||||
if (len >= allocated - 1)
|
||||
{
|
||||
allocated += TELODENDRIA_STRING_CHUNK;
|
||||
buf = Realloc(buf, allocated);
|
||||
}
|
||||
|
||||
buf[len] = *in;
|
||||
len++;
|
||||
in++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
/* Sanity check */
|
||||
if (*in != '=')
|
||||
{
|
||||
/* Malformed param */
|
||||
Free(buf);
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
in++;
|
||||
|
||||
/* Decode key */
|
||||
decKey = HttpUrlDecode(buf);
|
||||
Free(buf);
|
||||
|
||||
if (!decKey)
|
||||
{
|
||||
/* Decoding error */
|
||||
Free(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read in value */
|
||||
allocated = TELODENDRIA_STRING_CHUNK;
|
||||
buf = Malloc(allocated);
|
||||
len = 0;
|
||||
|
||||
while (*in && *in != '&')
|
||||
{
|
||||
if (len >= allocated - 1)
|
||||
{
|
||||
allocated += TELODENDRIA_STRING_CHUNK;
|
||||
buf = Realloc(buf, allocated);
|
||||
}
|
||||
|
||||
buf[len] = *in;
|
||||
len++;
|
||||
in++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
/* Decode value */
|
||||
decVal = HttpUrlDecode(buf);
|
||||
Free(buf);
|
||||
|
||||
if (!decVal)
|
||||
{
|
||||
/* Decoding error */
|
||||
Free(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HashMapSet(params, decKey, decVal);
|
||||
|
||||
if (*in == '&')
|
||||
{
|
||||
in++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpParamEncode(HashMap * params)
|
||||
{
|
||||
char *key;
|
||||
char *val;
|
||||
char *out = NULL;
|
||||
|
||||
if (!params || !out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (HashMapIterate(params, &key, (void *) &val))
|
||||
{
|
||||
char *encKey;
|
||||
char *encVal;
|
||||
|
||||
encKey = HttpUrlEncode(key);
|
||||
encVal = HttpUrlEncode(val);
|
||||
|
||||
if (!encKey || !encVal)
|
||||
{
|
||||
/* Memory error */
|
||||
Free(encKey);
|
||||
Free(encVal);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO */
|
||||
|
||||
Free(encKey);
|
||||
Free(encVal);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
728
src/HttpServer.c
Normal file
728
src/HttpServer.c
Normal file
|
@ -0,0 +1,728 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <HttpServer.h>
|
||||
#include <Memory.h>
|
||||
#include <Queue.h>
|
||||
#include <Array.h>
|
||||
#include <Util.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
static HttpServerContext *
|
||||
HttpServerContextCreate(HttpRequestMethod requestMethod,
|
||||
char *requestPath, HashMap * requestParams, FILE * stream)
|
||||
{
|
||||
HttpServerContext *c;
|
||||
|
||||
c = Malloc(sizeof(HttpServerContext));
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->requestHeaders = HashMapCreate();
|
||||
if (!c->requestHeaders)
|
||||
{
|
||||
Free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->responseHeaders = HashMapCreate();
|
||||
if (!c->responseHeaders)
|
||||
{
|
||||
Free(c->requestHeaders);
|
||||
Free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->requestMethod = requestMethod;
|
||||
c->requestPath = requestPath;
|
||||
c->requestParams = requestParams;
|
||||
c->stream = stream;
|
||||
c->responseStatus = HTTP_OK;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
HttpServerContextFree(HttpServerContext * c)
|
||||
{
|
||||
char *key;
|
||||
void *val;
|
||||
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (HashMapIterate(c->requestHeaders, &key, &val))
|
||||
{
|
||||
/*
|
||||
* These are always parsed from the request, so they should
|
||||
* always be on the heap.
|
||||
*/
|
||||
Free(key);
|
||||
Free(val);
|
||||
}
|
||||
HashMapFree(c->requestHeaders);
|
||||
|
||||
while (HashMapIterate(c->responseHeaders, &key, &val))
|
||||
{
|
||||
/*
|
||||
* These are generated by code. As such, they may be either
|
||||
* on the heap, or on the stack, depending on how they were
|
||||
* added.
|
||||
*
|
||||
* Basically, if the memory API knows about a pointer, then
|
||||
* it can be freed. If it doesn't know about a pointer, skip
|
||||
* freeing it because it's probably a stack pointer.
|
||||
*/
|
||||
|
||||
if (MemoryInfoGet(key))
|
||||
{
|
||||
Free(key);
|
||||
}
|
||||
|
||||
if (MemoryInfoGet(val))
|
||||
{
|
||||
Free(val);
|
||||
}
|
||||
}
|
||||
|
||||
HashMapFree(c->responseHeaders);
|
||||
|
||||
while (HashMapIterate(c->requestParams, &key, &val))
|
||||
{
|
||||
Free(key);
|
||||
Free(val);
|
||||
}
|
||||
|
||||
HashMapFree(c->requestParams);
|
||||
|
||||
Free(c->requestPath);
|
||||
#if 0
|
||||
fclose(c->stream);
|
||||
#endif
|
||||
|
||||
Free(c);
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpRequestHeaders(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestHeaders;
|
||||
}
|
||||
|
||||
HttpRequestMethod
|
||||
HttpRequestMethodGet(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return HTTP_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
return c->requestMethod;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpRequestPath(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestPath;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpRequestParams(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->requestParams;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpResponseHeader(HttpServerContext * c, char *key, char *val)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return HashMapSet(c->responseHeaders, key, val);
|
||||
}
|
||||
|
||||
void
|
||||
HttpResponseStatus(HttpServerContext * c, HttpStatus status)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
c->responseStatus = status;
|
||||
}
|
||||
|
||||
FILE *
|
||||
HttpStream(HttpServerContext * c)
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c->stream;
|
||||
}
|
||||
|
||||
void
|
||||
HttpSendHeaders(HttpServerContext * c)
|
||||
{
|
||||
FILE *fp = c->stream;
|
||||
|
||||
char *key;
|
||||
char *val;
|
||||
|
||||
fprintf(fp, "HTTP/1.0 %d %s\n", c->responseStatus, HttpStatusToString(c->responseStatus));
|
||||
|
||||
while (HashMapIterate(c->responseHeaders, &key, (void **) &val))
|
||||
{
|
||||
fprintf(fp, "%s: %s\n", key, val);
|
||||
}
|
||||
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
static int
|
||||
QueueConnection(HttpServer * server, int fd)
|
||||
{
|
||||
FILE *fp;
|
||||
int result;
|
||||
|
||||
if (!server)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
fp = fdopen(fd, "r+");
|
||||
if (!fp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&server->connQueueMutex);
|
||||
result = QueuePush(server->connQueue, fp);
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static FILE *
|
||||
DequeueConnection(HttpServer * server)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
if (!server)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&server->connQueueMutex);
|
||||
fp = QueuePop(server->connQueue);
|
||||
pthread_mutex_unlock(&server->connQueueMutex);
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
HttpServer *
|
||||
HttpServerCreate(unsigned short port, unsigned int nThreads, unsigned int maxConnections,
|
||||
HttpHandler * requestHandler, void *handlerArgs)
|
||||
{
|
||||
HttpServer *server;
|
||||
struct sockaddr_in sa = {0};
|
||||
|
||||
if (!requestHandler)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
server = Malloc(sizeof(HttpServer));
|
||||
if (!server)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
memset(server, 0, sizeof(HttpServer));
|
||||
|
||||
server->threadPool = ArrayCreate();
|
||||
if (!server->threadPool)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->connQueue = QueueCreate(maxConnections);
|
||||
if (!server->connQueue)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&server->connQueueMutex, NULL) != 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->sd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (server->sd < 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (fcntl(server->sd, F_SETFL, O_NONBLOCK) == -1)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
HttpServer *server = (HttpServer *) args;
|
||||
|
||||
while (!server->stop)
|
||||
{
|
||||
FILE *fp = DequeueConnection(server);
|
||||
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;
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
/* Block for 1 millisecond before continuing so we don't
|
||||
* murder the CPU */
|
||||
UtilSleepMillis(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the first line of the request */
|
||||
lineLen = UtilGetLine(&line, &lineSize, fp);
|
||||
if (lineLen == -1)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
|
||||
requestMethodPtr = line;
|
||||
for (i = 0; i < lineLen; i++)
|
||||
{
|
||||
if (line[i] == ' ')
|
||||
{
|
||||
line[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == lineLen)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
requestMethod = HttpRequestMethodFromString(requestMethodPtr);
|
||||
if (requestMethod == HTTP_METHOD_UNKNOWN)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
pathPtr = line + i + 1;
|
||||
|
||||
for (i = 0; i < (line + lineLen) - pathPtr; i++)
|
||||
{
|
||||
if (pathPtr[i] == ' ')
|
||||
{
|
||||
pathPtr[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requestPathLen = i;
|
||||
requestPath = Malloc(((requestPathLen + 1) * sizeof(char)));
|
||||
strcpy(requestPath, pathPtr);
|
||||
|
||||
requestProtocol = &pathPtr[i + 1];
|
||||
line[lineLen - 2] = '\0'; /* Get rid of \r and \n */
|
||||
|
||||
if (strcmp(requestProtocol, "HTTP/1.1") != 0 && strcmp(requestProtocol, "HTTP/1.0") != 0)
|
||||
{
|
||||
goto bad_request;
|
||||
}
|
||||
|
||||
/* Find request params */
|
||||
for (i = 0; i < requestPathLen; i++)
|
||||
{
|
||||
if (requestPath[i] == '?')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requestPath[i] = '\0';
|
||||
requestParams = HttpParamDecode(requestPath + i);
|
||||
|
||||
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
|
||||
if (!context)
|
||||
{
|
||||
goto internal_error;
|
||||
}
|
||||
|
||||
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
|
||||
{
|
||||
char *headerKey;
|
||||
char *headerValue;
|
||||
char *headerPtr;
|
||||
ssize_t i;
|
||||
|
||||
if (strcmp(line, "\r\n") == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < lineLen; i++)
|
||||
{
|
||||
if (line[i] == ':')
|
||||
{
|
||||
line[i] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
line[i] = tolower(line[i]);
|
||||
}
|
||||
|
||||
headerKey = Malloc((i * sizeof(char)) + 1);
|
||||
if (!headerKey)
|
||||
{
|
||||
goto internal_error;
|
||||
}
|
||||
|
||||
strcpy(headerKey, line);
|
||||
|
||||
headerPtr = line + i + 1;
|
||||
|
||||
while (isspace((unsigned char) *headerPtr))
|
||||
{
|
||||
headerPtr++;
|
||||
}
|
||||
|
||||
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
|
||||
{
|
||||
if (!isspace((unsigned char) line[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
line[i] = '\0';
|
||||
}
|
||||
|
||||
headerValue = Malloc(strlen(headerPtr) + 1);
|
||||
if (!headerValue)
|
||||
{
|
||||
goto internal_error;
|
||||
}
|
||||
|
||||
strcpy(headerValue, headerPtr);
|
||||
|
||||
HashMapSet(context->requestHeaders, headerKey, headerValue);
|
||||
}
|
||||
|
||||
server->requestHandler(context, server->handlerArgs);
|
||||
|
||||
HttpServerContextFree(context);
|
||||
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 0
|
||||
fclose(fp);
|
||||
#endif
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
pthread_t *workerThread = Malloc(sizeof(pthread_t));
|
||||
|
||||
if (!workerThread)
|
||||
{
|
||||
/* TODO: Make the event thread return an error to the main
|
||||
* thread */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pthread_create(workerThread, NULL, HttpServerWorkerThread, server) != 0)
|
||||
{
|
||||
/* TODO: Make the event thread return an error to the main
|
||||
* thread */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ArrayAdd(server->threadPool, workerThread);
|
||||
}
|
||||
|
||||
while (!server->stop)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
int connFd;
|
||||
int pollResult;
|
||||
|
||||
pollResult = poll(pollFds, 1, 500);
|
||||
|
||||
if (pollResult < 0)
|
||||
{
|
||||
/* The poll either timed out, or was interrupted. */
|
||||
continue;
|
||||
}
|
||||
|
||||
connFd = accept(server->sd, (struct sockaddr *) & addr, &addrLen);
|
||||
|
||||
if (connFd < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QueueConnection(server, connFd);
|
||||
}
|
||||
|
||||
for (i = 0; i < server->nThreads; i++)
|
||||
{
|
||||
pthread_t *workerThread = ArrayGet(server->threadPool, i);
|
||||
|
||||
pthread_join(*workerThread, 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;
|
||||
}
|
1043
src/Json.c
Normal file
1043
src/Json.c
Normal file
File diff suppressed because it is too large
Load diff
310
src/Log.c
Normal file
310
src/Log.c
Normal file
|
@ -0,0 +1,310 @@
|
|||
#include <Log.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define LOG_TSBUFFER 64
|
||||
|
||||
struct LogConfig {
|
||||
LogLevel level;
|
||||
size_t indent;
|
||||
FILE *out;
|
||||
int flags;
|
||||
char *tsFmt;
|
||||
};
|
||||
|
||||
void
|
||||
Log(LogConfig *config, LogLevel level, const char *msg, ...)
|
||||
{
|
||||
int 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;
|
||||
}
|
||||
|
||||
for (i = 0; i < config->indent; i++)
|
||||
{
|
||||
fputc(' ', config->out);
|
||||
}
|
||||
|
||||
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
|
||||
&& isatty(fileno(config->out));
|
||||
|
||||
if (doColor)
|
||||
{
|
||||
char *ansi;
|
||||
switch (level)
|
||||
{
|
||||
case LOG_ERROR:
|
||||
/* Bold Red */
|
||||
ansi = "\033[1;31m";
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
/* Bold Yellow */
|
||||
ansi = "\033[1;33m";
|
||||
break;
|
||||
case LOG_TASK:
|
||||
/* Bold Magenta */
|
||||
ansi = "\033[1;35m";
|
||||
break;
|
||||
case LOG_MESSAGE:
|
||||
/* 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(tsBuffer[tsLength - 1]))
|
||||
{
|
||||
fputc(' ', config->out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_ERROR:
|
||||
indicator = 'x';
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
indicator = '!';
|
||||
break;
|
||||
case LOG_TASK:
|
||||
indicator = '~';
|
||||
break;
|
||||
case LOG_MESSAGE:
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
LogConfig *
|
||||
LogConfigCreate(void)
|
||||
{
|
||||
LogConfig *config;
|
||||
|
||||
config = calloc(1, sizeof(LogConfig));
|
||||
|
||||
if (!config)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LogConfigLevelSet(config, LOG_MESSAGE);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
free(config);
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigIndent(LogConfig *config)
|
||||
{
|
||||
if (config)
|
||||
{
|
||||
config->indent += 2;
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
LogConfigIndentGet(LogConfig *config)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return config->indent;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigIndentSet(LogConfig *config, size_t indent)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
config->indent = indent;
|
||||
}
|
||||
|
||||
LogLevel
|
||||
LogConfigLevelGet(LogConfig *config)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return config->level;
|
||||
}
|
||||
|
||||
void
|
||||
LogConfigLevelSet(LogConfig *config, LogLevel level)
|
||||
{
|
||||
if (!config)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LOG_ERROR:
|
||||
case LOG_WARNING:
|
||||
case LOG_MESSAGE:
|
||||
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;
|
||||
}
|
||||
}
|
648
src/Main.c
648
src/Main.c
|
@ -1,648 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
222
src/Matrix.c
222
src/Matrix.c
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
|
@ -26,43 +25,58 @@
|
|||
#include <Matrix.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/HttpServer.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Memory.h>
|
||||
#include <HttpServer.h>
|
||||
#include <Json.h>
|
||||
#include <Util.h>
|
||||
|
||||
#include <Cytoplasm/HttpRouter.h>
|
||||
#include <Routes.h>
|
||||
|
||||
void
|
||||
MatrixHttpHandler(HttpServerContext * context, void *argp)
|
||||
{
|
||||
MatrixHttpHandlerArgs *args = (MatrixHttpHandlerArgs *) argp;
|
||||
Stream *stream;
|
||||
HashMap *response = NULL;
|
||||
|
||||
LogConfig *lc = args->lc;
|
||||
|
||||
HashMap *requestHeaders = HttpRequestHeaders(context);
|
||||
FILE *stream;
|
||||
|
||||
char *key;
|
||||
char *val;
|
||||
|
||||
HashMap *response;
|
||||
|
||||
char *requestPath;
|
||||
RouteArgs routeArgs;
|
||||
Array *pathParts;
|
||||
char *pathPart;
|
||||
|
||||
requestPath = HttpRequestPath(context);
|
||||
stream = HttpServerStream(context);
|
||||
|
||||
Log(LOG_DEBUG, "%s %s",
|
||||
Log(lc, LOG_MESSAGE, "%s %s",
|
||||
HttpRequestMethodToString(HttpRequestMethodGet(context)),
|
||||
requestPath);
|
||||
|
||||
LogConfigIndent(lc);
|
||||
Log(lc, LOG_DEBUG, "Request headers:");
|
||||
|
||||
LogConfigIndent(lc);
|
||||
while (HashMapIterate(requestHeaders, &key, (void **) &val))
|
||||
{
|
||||
Log(lc, LOG_DEBUG, "%s: %s", key, val);
|
||||
}
|
||||
LogConfigUnindent(lc);
|
||||
|
||||
HttpResponseStatus(context, HTTP_OK);
|
||||
HttpResponseHeader(context, "Server", "Telodendria/" TELODENDRIA_VERSION);
|
||||
HttpResponseHeader(context, "Server", "Telodendria v" TELODENDRIA_VERSION);
|
||||
HttpResponseHeader(context, "Content-Type", "application/json");
|
||||
|
||||
/* CORS */
|
||||
HttpResponseHeader(context, "Access-Control-Allow-Origin", "*");
|
||||
HttpResponseHeader(context, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
HttpResponseHeader(context, "Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization");
|
||||
|
||||
HttpResponseHeader(context, "Connection", "close");
|
||||
|
||||
/*
|
||||
* Web Browser Clients: Servers MUST expect that clients will approach them
|
||||
* with OPTIONS requests... the server MUST NOT perform any logic defined
|
||||
|
@ -73,50 +87,71 @@ MatrixHttpHandler(HttpServerContext * context, void *argp)
|
|||
HttpResponseStatus(context, HTTP_NO_CONTENT);
|
||||
HttpSendHeaders(context);
|
||||
|
||||
return;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
routeArgs.matrixArgs = args;
|
||||
routeArgs.context = context;
|
||||
pathParts = ArrayCreate();
|
||||
key = requestPath;
|
||||
|
||||
if (!HttpRouterRoute(args->router, requestPath, &routeArgs, (void **) &response))
|
||||
while ((pathPart = strtok_r(key, "/", &key)))
|
||||
{
|
||||
HttpResponseHeader(context, "Content-Type", "application/json");
|
||||
HttpResponseStatus(context, HTTP_NOT_FOUND);
|
||||
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
||||
char *decoded = HttpUrlDecode(pathPart);
|
||||
|
||||
ArrayAdd(pathParts, decoded);
|
||||
}
|
||||
|
||||
pathPart = MATRIX_PATH_POP(pathParts);
|
||||
|
||||
if (MATRIX_PATH_EQUALS(pathPart, ".well-known"))
|
||||
{
|
||||
response = RouteWellKnown(args, context, pathParts);
|
||||
}
|
||||
else if (MATRIX_PATH_EQUALS(pathPart, "_matrix"))
|
||||
{
|
||||
response = RouteMatrix(args, context, pathParts);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpResponseStatus(context, HTTP_NOT_FOUND);
|
||||
response = MatrixErrorCreate(M_NOT_FOUND);
|
||||
}
|
||||
|
||||
Free(pathPart);
|
||||
|
||||
HttpSendHeaders(context);
|
||||
stream = HttpStream(context);
|
||||
|
||||
if (!response)
|
||||
{
|
||||
Log(lc, LOG_WARNING, "A route handler returned NULL.");
|
||||
HttpResponseStatus(context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN);
|
||||
}
|
||||
|
||||
JsonEncode(response, stream);
|
||||
fprintf(stream, "\n");
|
||||
|
||||
/*
|
||||
* If the route handler returned a JSON object, take care
|
||||
* of sending it here.
|
||||
*
|
||||
* Otherwise, if the route handler returned NULL, assume
|
||||
* that it sent its own headers and and body.
|
||||
* By this point, there should be no path parts remaining, but if
|
||||
* there are, free them up now.
|
||||
*/
|
||||
if (response)
|
||||
while ((pathPart = MATRIX_PATH_POP(pathParts)) != NULL)
|
||||
{
|
||||
char *contentLen = StrInt(JsonEncode(response, NULL, JSON_DEFAULT));
|
||||
|
||||
HttpResponseHeader(context, "Content-Type", "application/json");
|
||||
HttpResponseHeader(context, "Content-Length", contentLen);
|
||||
HttpSendHeaders(context);
|
||||
|
||||
Free(contentLen);
|
||||
|
||||
JsonEncode(response, stream, JSON_DEFAULT);
|
||||
JsonFree(response);
|
||||
StreamPrintf(stream, "\n");
|
||||
Free(pathPart);
|
||||
}
|
||||
|
||||
Log(LOG_INFO, "%s %s (%d %s)",
|
||||
HttpRequestMethodToString(HttpRequestMethodGet(context)),
|
||||
requestPath,
|
||||
HttpResponseStatusGet(context),
|
||||
HttpStatusToString(HttpResponseStatusGet(context)));
|
||||
ArrayFree(pathParts);
|
||||
JsonFree(response);
|
||||
|
||||
finish:
|
||||
stream = HttpStream(context);
|
||||
fclose(stream);
|
||||
|
||||
LogConfigUnindent(lc);
|
||||
}
|
||||
|
||||
HashMap *
|
||||
MatrixErrorCreate(MatrixError errorArg, char *msg)
|
||||
MatrixErrorCreate(MatrixError errorArg)
|
||||
{
|
||||
HashMap *errorObj;
|
||||
char *errcode;
|
||||
|
@ -236,10 +271,6 @@ MatrixErrorCreate(MatrixError errorArg, char *msg)
|
|||
errcode = "M_MISSING_PARAM";
|
||||
error = "A required parameter was missing from the request.";
|
||||
break;
|
||||
case M_INVALID_PARAM:
|
||||
errcode = "M_INVALID_PARAM";
|
||||
error = "A required parameter was invalid in some way.";
|
||||
break;
|
||||
case M_TOO_LARGE:
|
||||
errcode = "M_TOO_LARGE";
|
||||
error = "The request or entity was too large.";
|
||||
|
@ -262,101 +293,14 @@ MatrixErrorCreate(MatrixError errorArg, char *msg)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (msg)
|
||||
{
|
||||
error = msg;
|
||||
}
|
||||
|
||||
errorObj = HashMapCreate();
|
||||
if (!errorObj)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HashMapSet(errorObj, "errcode", JsonValueString(errcode));
|
||||
HashMapSet(errorObj, "error", JsonValueString(error));
|
||||
HashMapSet(errorObj, "errcode", JsonValueString(UtilStringDuplicate(errcode)));
|
||||
HashMapSet(errorObj, "error", JsonValueString(UtilStringDuplicate(error)));
|
||||
|
||||
return errorObj;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
MatrixGetAccessToken(HttpServerContext * context, char **accessToken)
|
||||
{
|
||||
HashMap *params;
|
||||
char *token;
|
||||
|
||||
params = HttpRequestHeaders(context);
|
||||
token = HashMapGet(params, "authorization");
|
||||
|
||||
if (token)
|
||||
{
|
||||
/* If the header was provided but it's not given correctly,
|
||||
* that's an error */
|
||||
if (strncmp(token, "Bearer ", 7) != 0)
|
||||
{
|
||||
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
||||
return MatrixErrorCreate(M_MISSING_TOKEN, NULL);
|
||||
}
|
||||
|
||||
/* Seek past "Bearer" */
|
||||
token += 7;
|
||||
|
||||
/* Seek past any spaces between "Bearer" and the token */
|
||||
while (*token && isspace((unsigned char) *token))
|
||||
{
|
||||
token++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Header was not provided, we must check for ?access_token */
|
||||
params = HttpRequestParams(context);
|
||||
token = HashMapGet(params, "access_token");
|
||||
|
||||
if (!token)
|
||||
{
|
||||
HttpResponseStatus(context, HTTP_UNAUTHORIZED);
|
||||
return MatrixErrorCreate(M_MISSING_TOKEN, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
*accessToken = token;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
MatrixRateLimit(HttpServerContext * context, Db * db)
|
||||
{
|
||||
/* TODO: Implement rate limiting */
|
||||
(void) context;
|
||||
(void) db;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
MatrixClientWellKnown(char *base, char *identity)
|
||||
{
|
||||
HashMap *response;
|
||||
HashMap *homeserver;
|
||||
|
||||
if (!base)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
response = HashMapCreate();
|
||||
homeserver = HashMapCreate();
|
||||
|
||||
HashMapSet(homeserver, "base_url", JsonValueString(base));
|
||||
HashMapSet(response, "m.homeserver", JsonValueObject(homeserver));
|
||||
|
||||
if (identity)
|
||||
{
|
||||
HashMap *identityServer = HashMapCreate();
|
||||
|
||||
HashMapSet(identityServer, "base_url", JsonValueString(identity));
|
||||
HashMapSet(response, "m.identity_server", JsonValueObject(identityServer));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
415
src/Memory.c
Normal file
415
src/Memory.c
Normal file
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Memory.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
struct MemoryInfo
|
||||
{
|
||||
size_t size;
|
||||
const char *file;
|
||||
int line;
|
||||
void *pointer;
|
||||
};
|
||||
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static void (*hook) (MemoryAction, MemoryInfo *, void *) = NULL;
|
||||
static void *hookArgs = NULL;
|
||||
|
||||
static MemoryInfo **allocations = NULL;
|
||||
static size_t allocationsSize = 0;
|
||||
static size_t allocationsLen = 0;
|
||||
|
||||
static size_t
|
||||
MemoryHash(void *p)
|
||||
{
|
||||
return (((size_t) p) >> 2 * 7) % allocationsSize;
|
||||
}
|
||||
|
||||
static int
|
||||
MemoryInsert(MemoryInfo * a)
|
||||
{
|
||||
size_t hash;
|
||||
|
||||
if (!allocations)
|
||||
{
|
||||
allocationsSize = 64;
|
||||
allocations = calloc(allocationsSize, sizeof(void *));
|
||||
if (!allocations)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((allocationsLen + 1) >= (0.75 * allocationsSize))
|
||||
{
|
||||
size_t i;
|
||||
size_t tmpAllocationsSize = allocationsSize;
|
||||
MemoryInfo **tmpAllocations;
|
||||
|
||||
allocationsSize *= 2;
|
||||
tmpAllocations = calloc(allocationsSize, sizeof(void *));
|
||||
|
||||
if (!tmpAllocations)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < tmpAllocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
hash = MemoryHash(allocations[i]->pointer);
|
||||
|
||||
while (tmpAllocations[hash])
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
}
|
||||
|
||||
tmpAllocations[hash] = allocations[i];
|
||||
}
|
||||
}
|
||||
|
||||
free(allocations);
|
||||
allocations = tmpAllocations;
|
||||
}
|
||||
|
||||
hash = MemoryHash(a->pointer);
|
||||
|
||||
while (allocations[hash])
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
}
|
||||
|
||||
allocations[hash] = a;
|
||||
allocationsLen++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
MemoryDelete(MemoryInfo * a)
|
||||
{
|
||||
size_t hash = MemoryHash(a->pointer);
|
||||
size_t count = 0;
|
||||
|
||||
while (count <= allocationsSize)
|
||||
{
|
||||
if (allocations[hash] && allocations[hash] == a)
|
||||
{
|
||||
allocations[hash] = NULL;
|
||||
allocationsLen--;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
MemoryAllocate(size_t size, const char *file, int line)
|
||||
{
|
||||
void *p;
|
||||
MemoryInfo *a;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
p = malloc(size);
|
||||
if (!p)
|
||||
{
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
a = malloc(sizeof(MemoryInfo));
|
||||
if (!a)
|
||||
{
|
||||
free(p);
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
a->size = size;
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
a->pointer = p;
|
||||
|
||||
if (!MemoryInsert(a))
|
||||
{
|
||||
free(a);
|
||||
free(p);
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (hook)
|
||||
{
|
||||
hook(MEMORY_ALLOCATE, a, hookArgs);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
return p;
|
||||
}
|
||||
|
||||
void *
|
||||
MemoryReallocate(void *p, size_t size, const char *file, int line)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
void *new = NULL;
|
||||
|
||||
if (!p)
|
||||
{
|
||||
return MemoryAllocate(size, file, line);
|
||||
}
|
||||
|
||||
a = MemoryInfoGet(p);
|
||||
if (a)
|
||||
{
|
||||
pthread_mutex_lock(&lock);
|
||||
new = realloc(a->pointer, size);
|
||||
if (new)
|
||||
{
|
||||
MemoryDelete(a);
|
||||
a->size = size;
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
|
||||
a->pointer = new;
|
||||
MemoryInsert(a);
|
||||
|
||||
if (hook)
|
||||
{
|
||||
hook(MEMORY_REALLOCATE, a, hookArgs);
|
||||
}
|
||||
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
else if (hook)
|
||||
{
|
||||
a = malloc(sizeof(MemoryInfo));
|
||||
if (a)
|
||||
{
|
||||
a->size = 0;
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
a->pointer = p;
|
||||
hook(MEMORY_BAD_POINTER, a, hookArgs);
|
||||
free(a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryFree(void *p, const char *file, int line)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
|
||||
if (!p)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
a = MemoryInfoGet(p);
|
||||
if (a)
|
||||
{
|
||||
pthread_mutex_lock(&lock);
|
||||
if (hook)
|
||||
{
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
hook(MEMORY_FREE, a, hookArgs);
|
||||
}
|
||||
MemoryDelete(a);
|
||||
free(a->pointer);
|
||||
free(a);
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
else if (hook)
|
||||
{
|
||||
a = malloc(sizeof(MemoryInfo));
|
||||
if (a)
|
||||
{
|
||||
a->file = file;
|
||||
a->line = line;
|
||||
a->size = 0;
|
||||
a->pointer = p;
|
||||
hook(MEMORY_BAD_POINTER, a, hookArgs);
|
||||
free(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
MemoryAllocated(void)
|
||||
{
|
||||
size_t i;
|
||||
size_t total = 0;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
for (i = 0; i < allocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
total += allocations[i]->size;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryFreeAll(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
for (i = 0; i < allocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
free(allocations[i]->pointer);
|
||||
free(allocations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(allocations);
|
||||
allocations = NULL;
|
||||
allocationsSize = 0;
|
||||
allocationsLen = 0;
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
MemoryInfo *
|
||||
MemoryInfoGet(void *p)
|
||||
{
|
||||
size_t hash, count;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
hash = MemoryHash(p);
|
||||
|
||||
count = 0;
|
||||
while (count <= allocationsSize)
|
||||
{
|
||||
if (!allocations[hash] || allocations[hash]->pointer != p)
|
||||
{
|
||||
hash = (hash + 1) % allocationsSize;
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_unlock(&lock);
|
||||
return allocations[hash];
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t
|
||||
MemoryInfoGetSize(MemoryInfo * a)
|
||||
{
|
||||
if (!a)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a->size;
|
||||
}
|
||||
|
||||
const char *
|
||||
MemoryInfoGetFile(MemoryInfo * a)
|
||||
{
|
||||
if (!a)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return a->file;
|
||||
}
|
||||
|
||||
int
|
||||
MemoryInfoGetLine(MemoryInfo * a)
|
||||
{
|
||||
if (!a)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return a->line;
|
||||
}
|
||||
|
||||
void *
|
||||
MemoryInfoGetPointer(MemoryInfo * a)
|
||||
{
|
||||
if (!a)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return a->pointer;
|
||||
}
|
||||
|
||||
void
|
||||
MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
for (i = 0; i < allocationsSize; i++)
|
||||
{
|
||||
if (allocations[i])
|
||||
{
|
||||
iterFunc(allocations[i], args);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
void
|
||||
MemoryHook(void (*memHook) (MemoryAction, MemoryInfo *, void *), void *args)
|
||||
{
|
||||
pthread_mutex_lock(&lock);
|
||||
hook = memHook;
|
||||
hookArgs = args;
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
516
src/Parser.c
516
src/Parser.c
|
@ -1,516 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <Parser.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
/* Iterate through a char **. */
|
||||
#define Iterate(s) (*(*s)++)
|
||||
|
||||
/* Parse an extended localpart */
|
||||
static bool
|
||||
ParseUserLocalpart(char **str, char **out)
|
||||
{
|
||||
char c;
|
||||
char *start;
|
||||
size_t length;
|
||||
|
||||
if (!str || !out)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/* An extended localpart contains every ASCII printable character,
|
||||
* except an ':'. */
|
||||
start = *str;
|
||||
while (isascii((c = Iterate(str))) && c != ':' && c)
|
||||
{
|
||||
/* Do nothing */
|
||||
}
|
||||
length = (size_t) (*str - start) - 1;
|
||||
if (length < 1)
|
||||
{
|
||||
*str = start;
|
||||
return false;
|
||||
}
|
||||
if (c == ':')
|
||||
{
|
||||
--(*str);
|
||||
}
|
||||
|
||||
*out = Malloc(length + 1);
|
||||
memcpy(*out, start, length);
|
||||
(*out)[length] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Parses an IPv4 address. */
|
||||
static int
|
||||
ParseIPv4(char **str, char **out)
|
||||
{
|
||||
/* Be *very* careful with this buffer */
|
||||
char buffer[4];
|
||||
char *start;
|
||||
size_t length;
|
||||
char c;
|
||||
|
||||
int digit = 0;
|
||||
int digits = 0;
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
start = *str;
|
||||
|
||||
/* An IPv4 address is made of 4 blocks between 1-3 digits, like so:
|
||||
* (1-3)*DIGIT.(1-3)*DIGIT.(1-3)*DIGIT.(1-3)*DIGIT */
|
||||
while ((isdigit(c = Iterate(str)) || c == '.') && c && digits < 4)
|
||||
{
|
||||
if (isdigit(c))
|
||||
{
|
||||
digit++;
|
||||
continue;
|
||||
}
|
||||
if (digit < 1 || digit > 3)
|
||||
{
|
||||
/* Current digit is too long for the spec! */
|
||||
*str = start;
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer, *str - digit - 1, digit);
|
||||
if (atoi(buffer) > 255)
|
||||
{
|
||||
/* Current digit is too large for the spec! */
|
||||
*str = start;
|
||||
return false;
|
||||
}
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
digit = 0;
|
||||
digits++; /* We have parsed a digit. */
|
||||
}
|
||||
if (c == '.' || digits != 3)
|
||||
{
|
||||
*str = start;
|
||||
return false;
|
||||
}
|
||||
length = (size_t) (*str - start) - 1;
|
||||
*out = Malloc(length + 1);
|
||||
memcpy(*out, start, length);
|
||||
(*str)--;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsIPv6Char(char c)
|
||||
{
|
||||
return (isxdigit(c) || c == ':' || c == '.');
|
||||
}
|
||||
|
||||
static bool
|
||||
ParseIPv6(char **str, char **out)
|
||||
{
|
||||
char *start;
|
||||
size_t length;
|
||||
char c;
|
||||
|
||||
int filled = 0;
|
||||
int digit = 0;
|
||||
int digits = 0;
|
||||
|
||||
start = *str;
|
||||
length = 0;
|
||||
|
||||
if (Iterate(str) != '[')
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
|
||||
while ((c = Iterate(str)) && IsIPv6Char(c) && digits < 8)
|
||||
{
|
||||
char *ipv4;
|
||||
if (isxdigit(c))
|
||||
{
|
||||
digit++;
|
||||
length++;
|
||||
continue;
|
||||
}
|
||||
if (c == ':')
|
||||
{
|
||||
if (**str == ':')
|
||||
{
|
||||
digit = 0;
|
||||
if (!filled)
|
||||
{
|
||||
filled = 1;
|
||||
length++;
|
||||
c = Iterate(str); /* Skip over the character */
|
||||
continue;
|
||||
}
|
||||
/* RFC3513 says the following:
|
||||
* > 'The "::" can only appear once in an address.' */
|
||||
*str = start;
|
||||
return false;
|
||||
}
|
||||
if (digit < 1 || digit > 4)
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
/* We do not have to check whenever the digit here is valid,
|
||||
* because it has to be. */
|
||||
digit = 0;
|
||||
digits++;
|
||||
|
||||
length++;
|
||||
continue;
|
||||
}
|
||||
/* The only remaining character being '.', we are probably dealing
|
||||
* with an IPv4 literal. */
|
||||
*str -= digit + 1;
|
||||
length -= digit + 1;
|
||||
if (ParseIPv4(str, &ipv4))
|
||||
{
|
||||
length += strlen(ipv4);
|
||||
Free(ipv4);
|
||||
c = Iterate(str);
|
||||
filled = 1;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
end:
|
||||
--(*str);
|
||||
if (Iterate(str) != ']')
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
|
||||
length = (size_t) (*str - start);
|
||||
if (length < 4 || length > 47)
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
*out = Malloc(length + 1);
|
||||
memset(*out, '\0', length + 1);
|
||||
memcpy(*out, start, length);
|
||||
|
||||
return true;
|
||||
fail:
|
||||
*str = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ParseHostname(char **str, char **out)
|
||||
{
|
||||
char *start;
|
||||
size_t length = 0;
|
||||
char c;
|
||||
|
||||
start = *str;
|
||||
while ((c = Iterate(str)) &&
|
||||
(isalnum(c) || c == '.' || c == '-') &&
|
||||
++length < 256)
|
||||
{
|
||||
/* Do nothing. */
|
||||
}
|
||||
if (length < 1 || length > 255)
|
||||
{
|
||||
*str = start;
|
||||
return false;
|
||||
}
|
||||
length = (size_t) (*str - start) - 1;
|
||||
*out = Malloc(length + 1);
|
||||
memcpy(*out, start, length);
|
||||
(*str)--;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ParseServerName(char **str, ServerPart *out)
|
||||
{
|
||||
char c;
|
||||
char *start;
|
||||
char *startPort;
|
||||
size_t chars = 0;
|
||||
|
||||
char *host = NULL;
|
||||
char *port = NULL;
|
||||
|
||||
if (!str || !out)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
start = *str;
|
||||
|
||||
if (!host)
|
||||
{
|
||||
/* If we can parse an IPv4 address, use that. */
|
||||
ParseIPv4(str, &host);
|
||||
}
|
||||
if (!host)
|
||||
{
|
||||
/* If we can parse an IPv6 address, use that. */
|
||||
ParseIPv6(str, &host);
|
||||
}
|
||||
if (!host)
|
||||
{
|
||||
/* If we can parse an hostname, use that. */
|
||||
ParseHostname(str, &host);
|
||||
}
|
||||
if (!host)
|
||||
{
|
||||
/* Can't parse a valid server name. */
|
||||
return false;
|
||||
}
|
||||
/* Now, there's only 2 options: a ':', or the end(everything else.) */
|
||||
if (**str != ':')
|
||||
{
|
||||
/* We're done. */
|
||||
out->hostname = host;
|
||||
out->port = NULL;
|
||||
return true;
|
||||
}
|
||||
/* TODO: Separate this out */
|
||||
startPort = ++(*str);
|
||||
while(isdigit(c = Iterate(str)) && c && ++chars < 5)
|
||||
{
|
||||
/* Do nothing. */
|
||||
}
|
||||
if (chars < 1 || chars > 5)
|
||||
{
|
||||
*str = start;
|
||||
Free(host);
|
||||
host = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
port = Malloc(chars + 1);
|
||||
memcpy(port, startPort, chars);
|
||||
port[chars] = '\0';
|
||||
if (atol(port) > 65535)
|
||||
{
|
||||
Free(port);
|
||||
Free(host);
|
||||
*str = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
out->hostname = host;
|
||||
out->port = port;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ParseServerPart(char *str, ServerPart *part)
|
||||
{
|
||||
/* This is a wrapper behind the internal ParseServerName. */
|
||||
if (!str || !part)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return ParseServerName(&str, part);
|
||||
}
|
||||
|
||||
void
|
||||
ServerPartFree(ServerPart part)
|
||||
{
|
||||
if (part.hostname)
|
||||
{
|
||||
Free(part.hostname);
|
||||
}
|
||||
if (part.port)
|
||||
{
|
||||
Free(part.port);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ParseCommonID(char *str, CommonID *id)
|
||||
{
|
||||
char sigil;
|
||||
|
||||
if (!str || !id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* There must at least be 2 chararacters: the sigil and a string.*/
|
||||
if (strlen(str) < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
sigil = *str++;
|
||||
/* Some sigils have the following restriction:
|
||||
* > MUST NOT exceed 255 bytes (including the # sigil and the domain).
|
||||
*/
|
||||
if ((sigil == '#' || sigil == '@') && strlen(str) > 255)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
id->sigil = sigil;
|
||||
id->local = NULL;
|
||||
id->server.hostname = NULL;
|
||||
id->server.port = NULL;
|
||||
|
||||
switch (sigil)
|
||||
{
|
||||
case '$':
|
||||
/* For event IDs, it depends on the version, so we're just
|
||||
* accepting it all. */
|
||||
if (!ParseUserLocalpart(&str, &id->local))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (*str == ':')
|
||||
{
|
||||
(*str)++;
|
||||
if (!ParseServerName(&str, &id->server))
|
||||
{
|
||||
Free(id->local);
|
||||
id->local = NULL;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case '!':
|
||||
case '#': /* It seems like the localpart should be the same as the
|
||||
user's: everything, except ':'. */
|
||||
case '@':
|
||||
if (!ParseUserLocalpart(&str, &id->local))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (*str++ != ':')
|
||||
{
|
||||
Free(id->local);
|
||||
id->local = NULL;
|
||||
return false;
|
||||
}
|
||||
if (!ParseServerName(&str, &id->server))
|
||||
{
|
||||
Free(id->local);
|
||||
id->local = NULL;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
CommonIDFree(CommonID id)
|
||||
{
|
||||
if (id.local)
|
||||
{
|
||||
Free(id.local);
|
||||
}
|
||||
ServerPartFree(id.server);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidCommonID(char *str, char sigil)
|
||||
{
|
||||
CommonID id;
|
||||
bool ret;
|
||||
|
||||
memset(&id, 0, sizeof(CommonID));
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = ParseCommonID(str, &id) && id.sigil == sigil;
|
||||
|
||||
CommonIDFree(id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *
|
||||
ParserRecomposeServerPart(ServerPart serverPart)
|
||||
{
|
||||
if (serverPart.hostname && serverPart.port)
|
||||
{
|
||||
return StrConcat(3, serverPart.hostname, ":", serverPart.port);
|
||||
}
|
||||
if (serverPart.hostname)
|
||||
{
|
||||
return StrDuplicate(serverPart.hostname);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *
|
||||
ParserRecomposeCommonID(CommonID id)
|
||||
{
|
||||
char *ret = Malloc(2 * sizeof(char));
|
||||
ret[0] = id.sigil;
|
||||
ret[1] = '\0';
|
||||
|
||||
if (id.local)
|
||||
{
|
||||
char *tmp = StrConcat(2, ret, id.local);
|
||||
Free(ret);
|
||||
|
||||
ret = tmp;
|
||||
}
|
||||
if (id.server.hostname)
|
||||
{
|
||||
char *server = ParserRecomposeServerPart(id.server);
|
||||
char *tmp = StrConcat(4, "@", ret, ":", server);
|
||||
Free(ret);
|
||||
Free(server);
|
||||
|
||||
ret = tmp;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
ParserServerNameEquals(ServerPart serverPart, char *str)
|
||||
{
|
||||
char *idServer;
|
||||
bool ret;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
idServer = ParserRecomposeServerPart(serverPart);
|
||||
|
||||
ret = StrEquals(idServer, str);
|
||||
Free(idServer);
|
||||
|
||||
return ret;
|
||||
}
|
176
src/Queue.c
Normal file
176
src/Queue.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Queue.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
struct Queue
|
||||
{
|
||||
void **items;
|
||||
size_t size;
|
||||
size_t front;
|
||||
size_t rear;
|
||||
};
|
||||
|
||||
Queue *
|
||||
QueueCreate(size_t size)
|
||||
{
|
||||
Queue *q;
|
||||
|
||||
if (!size)
|
||||
{
|
||||
/* Can't have a queue of length zero */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
q = Malloc(sizeof(Queue));
|
||||
if (!q)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
q->items = Malloc(size * sizeof(void *));
|
||||
if (!q->items)
|
||||
{
|
||||
Free(q);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
q->size = size;
|
||||
q->front = size + 1;
|
||||
q->rear = size + 1;
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
void
|
||||
QueueFree(Queue * q)
|
||||
{
|
||||
if (q)
|
||||
{
|
||||
Free(q->items);
|
||||
}
|
||||
|
||||
Free(q);
|
||||
}
|
||||
|
||||
int
|
||||
QueueFull(Queue * q)
|
||||
{
|
||||
if (!q)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((q->front == q->rear + 1) || (q->front == 0 && q->rear == q->size - 1));
|
||||
}
|
||||
|
||||
int
|
||||
QueueEmpty(Queue * q)
|
||||
{
|
||||
if (!q)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return q->front == q->size + 1;
|
||||
}
|
||||
|
||||
int
|
||||
QueuePush(Queue * q, void *element)
|
||||
{
|
||||
if (!q || !element)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (QueueFull(q))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (q->front == q->size + 1)
|
||||
{
|
||||
q->front = 0;
|
||||
}
|
||||
|
||||
if (q->rear == q->size + 1)
|
||||
{
|
||||
q->rear = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
q->rear = (q->rear + 1) % q->size;
|
||||
}
|
||||
|
||||
q->items[q->rear] = element;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *
|
||||
QueuePop(Queue * q)
|
||||
{
|
||||
void *element;
|
||||
|
||||
if (!q)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (QueueEmpty(q))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
element = q->items[q->front];
|
||||
|
||||
if (q->front == q->rear)
|
||||
{
|
||||
q->front = q->size + 1;
|
||||
q->rear = q->size + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
q->front = (q->front + 1) % q->size;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void *
|
||||
QueuePeek(Queue * q)
|
||||
{
|
||||
if (!q)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (QueueEmpty(q))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return q->items[q->front];
|
||||
}
|
241
src/RegToken.c
241
src/RegToken.c
|
@ -1,241 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <RegToken.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/Util.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
|
||||
#include <User.h>
|
||||
|
||||
int
|
||||
RegTokenValid(RegTokenInfo * token)
|
||||
{
|
||||
HashMap *tokenJson;
|
||||
int64_t uses, used;
|
||||
|
||||
uint64_t expiration;
|
||||
|
||||
if (!token || !RegTokenExists(token->db, token->name))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
tokenJson = DbJson(token->ref);
|
||||
uses = JsonValueAsInteger(HashMapGet(tokenJson, "uses"));
|
||||
used = JsonValueAsInteger(HashMapGet(tokenJson, "used"));
|
||||
expiration = JsonValueAsInteger(HashMapGet(tokenJson, "expires_on"));
|
||||
|
||||
return (!expiration || (UtilTsMillis() < expiration)) && (uses == -1 || used < uses);
|
||||
}
|
||||
void
|
||||
RegTokenUse(RegTokenInfo * token)
|
||||
{
|
||||
HashMap *tokenJson;
|
||||
|
||||
if (!token || !RegTokenExists(token->db, token->name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (token->uses >= 0 && token->used >= token->uses)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
token->used++;
|
||||
|
||||
/* Write the information to the hashmap */
|
||||
tokenJson = DbJson(token->ref);
|
||||
JsonValueFree(HashMapSet(tokenJson, "used", JsonValueInteger(token->used)));
|
||||
}
|
||||
|
||||
int
|
||||
RegTokenExists(Db * db, char *token)
|
||||
{
|
||||
if (!token || !db)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return DbExists(db, 3, "tokens", "registration", token);
|
||||
}
|
||||
|
||||
int
|
||||
RegTokenDelete(RegTokenInfo * token)
|
||||
{
|
||||
if (!token || !RegTokenClose(token))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!DbDelete(token->db, 3, "tokens", "registration", token->name))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
RegTokenInfoFree(token);
|
||||
Free(token);
|
||||
return 1;
|
||||
}
|
||||
|
||||
RegTokenInfo *
|
||||
RegTokenGetInfo(Db * db, char *token)
|
||||
{
|
||||
RegTokenInfo *ret;
|
||||
|
||||
DbRef *tokenRef;
|
||||
HashMap *tokenJson;
|
||||
|
||||
char *errp = NULL;
|
||||
|
||||
if (!RegTokenExists(db, token))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tokenRef = DbLock(db, 3, "tokens", "registration", token);
|
||||
if (!tokenRef)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
tokenJson = DbJson(tokenRef);
|
||||
ret = Malloc(sizeof(RegTokenInfo));
|
||||
|
||||
if (!RegTokenInfoFromJson(tokenJson, ret, &errp))
|
||||
{
|
||||
Log(LOG_ERR, "RegTokenGetInfo(): Database decoding error: %s", errp);
|
||||
RegTokenFree(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret->db = db;
|
||||
ret->ref = tokenRef;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
RegTokenFree(RegTokenInfo *tokeninfo)
|
||||
{
|
||||
if (tokeninfo)
|
||||
{
|
||||
RegTokenInfoFree(tokeninfo);
|
||||
Free(tokeninfo);
|
||||
}
|
||||
}
|
||||
int
|
||||
RegTokenClose(RegTokenInfo * tokeninfo)
|
||||
{
|
||||
HashMap *json;
|
||||
|
||||
if (!tokeninfo)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Write object to database. */
|
||||
json = RegTokenInfoToJson(tokeninfo);
|
||||
DbJsonSet(tokeninfo->ref, json); /* Copies json into internal structure. */
|
||||
JsonFree(json);
|
||||
|
||||
return DbUnlock(tokeninfo->db, tokeninfo->ref);
|
||||
}
|
||||
static int
|
||||
RegTokenVerify(char *token)
|
||||
{
|
||||
size_t i, size;
|
||||
char c;
|
||||
|
||||
if (!token)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
/* The spec says the following: "The token required for this
|
||||
* authentication [...] is an opaque string with maximum length of
|
||||
* 64 characters in the range [A-Za-z0-9._~-]." */
|
||||
if ((size = strlen(token)) > 64)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
c = token[i];
|
||||
if (!(isalnum(c) || c == '0' || c == '_' || c == '~' || c == '-'))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
RegTokenInfo *
|
||||
RegTokenCreate(Db * db, char *name, char *owner, uint64_t expires, int64_t uses, int privileges)
|
||||
{
|
||||
RegTokenInfo *ret;
|
||||
|
||||
uint64_t timestamp = UtilTsMillis();
|
||||
|
||||
if (!db || !name)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* -1 indicates infinite uses; zero and all positive values are a
|
||||
* valid number of uses; althought zero would be rather useless.
|
||||
* Anything less than -1 doesn't make sense. */
|
||||
if (uses < -1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Verify the token */
|
||||
if (!RegTokenVerify(name) || ((expires > 0) && (expires < timestamp)))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
ret = Malloc(sizeof(RegTokenInfo));
|
||||
/* Set the token's properties */
|
||||
ret->db = db;
|
||||
ret->ref = DbCreate(db, 3, "tokens", "registration", name);
|
||||
if (!ret->ref)
|
||||
{
|
||||
/* RegToken already exists or some weird fs error */
|
||||
Free(ret);
|
||||
return NULL;
|
||||
}
|
||||
ret->name = StrDuplicate(name);
|
||||
ret->created_by = StrDuplicate(owner);
|
||||
ret->used = 0;
|
||||
ret->uses = uses;
|
||||
ret->created_on = timestamp;
|
||||
ret->expires_on = expires;
|
||||
ret->grants = UserEncodePrivileges(privileges);
|
||||
|
||||
return ret;
|
||||
}
|
98
src/Routes.c
98
src/Routes.c
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Routes.h>
|
||||
|
||||
HttpRouter *
|
||||
RouterBuild(void)
|
||||
{
|
||||
HttpRouter *router = HttpRouterCreate();
|
||||
|
||||
if (!router)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define R(path, func) \
|
||||
if (!HttpRouterAdd(router, path, func)) \
|
||||
{ \
|
||||
Log(LOG_ERR, "Unable to add route: %s", path); \
|
||||
HttpRouterFree(router); \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
/* Matrix Specifification Routes */
|
||||
|
||||
R("/.well-known/matrix/(client|server)", RouteWellKnown);
|
||||
|
||||
R("/_matrix/client/versions", RouteVersions);
|
||||
|
||||
R("/_matrix/static", RouteStaticDefault);
|
||||
R("/_matrix/static/telodendria\\.(js|css)", RouteStaticResources);
|
||||
R("/_matrix/static/client/login", RouteStaticLogin);
|
||||
R("/_matrix/client/v3/auth/(.*)/fallback/web", RouteUiaFallback);
|
||||
|
||||
R("/_matrix/client/v3/capabilities", RouteCapabilities);
|
||||
R("/_matrix/client/v3/login", RouteLogin);
|
||||
R("/_matrix/client/v3/logout", RouteLogout);
|
||||
R("/_matrix/client/v3/logout/(all)", RouteLogout);
|
||||
R("/_matrix/client/v3/register", RouteRegister);
|
||||
R("/_matrix/client/v3/register/(available)", RouteRegister);
|
||||
R("/_matrix/client/v3/refresh", RouteRefresh);
|
||||
|
||||
R("/_matrix/client/v3/account/whoami", RouteWhoami);
|
||||
R("/_matrix/client/v3/account/password", RouteChangePwd);
|
||||
R("/_matrix/client/v3/account/deactivate", RouteDeactivate);
|
||||
|
||||
R("/_matrix/client/v1/register/m.login.registration_token/validity", RouteTokenValid);
|
||||
|
||||
R("/_matrix/client/v3/account/password/(email|msisdn)/requestToken", RouteRequestToken);
|
||||
R("/_matrix/client/v3/register/(email|msisdn)/requestToken", RouteRequestToken);
|
||||
|
||||
R("/_matrix/client/v3/profile/(.*)", RouteUserProfile);
|
||||
R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile);
|
||||
R("/_matrix/client/v3/user_directory/search", RouteUserDirectory);
|
||||
|
||||
R("/_matrix/client/v3/user/(.*)/filter", RouteFilter);
|
||||
R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter);
|
||||
|
||||
R("/_matrix/client/v3/createRoom", RouteCreateRoom);
|
||||
|
||||
R("/_matrix/client/v3/directory/room/(.*)", RouteAliasDirectory);
|
||||
R("/_matrix/client/v3/rooms/(.*)/aliases", RouteRoomAliases);
|
||||
|
||||
/* Telodendria Admin API Routes */
|
||||
|
||||
R("/_telodendria/admin/v1/(restart|shutdown|stats)", RouteProcControl);
|
||||
R("/_telodendria/admin/v1/config", RouteConfig);
|
||||
R("/_telodendria/admin/v1/privileges", RoutePrivileges);
|
||||
R("/_telodendria/admin/v1/privileges/(.*)", RoutePrivileges);
|
||||
R("/_telodendria/admin/v1/deactivate/(.*)", RouteAdminDeactivate);
|
||||
R("/_telodendria/admin/v1/tokens/(.*)", RouteAdminTokens);
|
||||
R("/_telodendria/admin/v1/tokens", RouteAdminTokens);
|
||||
|
||||
#undef R
|
||||
|
||||
return router;
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Routes.h>
|
||||
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <User.h>
|
||||
|
||||
ROUTE_IMPL(RouteAdminDeactivate, path, argp)
|
||||
{
|
||||
RouteArgs *args = argp;
|
||||
HashMap *request = NULL;
|
||||
HashMap *response = NULL;
|
||||
|
||||
JsonValue *val;
|
||||
char *reason = "Deactivated by admin";
|
||||
char *removedLocalpart = ArrayGet(path, 0);
|
||||
char *token;
|
||||
|
||||
Db *db = args->matrixArgs->db;
|
||||
|
||||
User *user = NULL;
|
||||
User *removed = NULL;
|
||||
|
||||
HttpRequestMethod method = HttpRequestMethodGet(args->context);
|
||||
|
||||
if ((method != HTTP_DELETE) && (method != HTTP_PUT))
|
||||
{
|
||||
char * msg = "Route only supports DELETE and PUT as for now.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
}
|
||||
|
||||
if (method == HTTP_DELETE)
|
||||
{
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
if (!request)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||
}
|
||||
val = HashMapGet(request, "reason");
|
||||
if (val && JsonValueType(val) == JSON_STRING)
|
||||
{
|
||||
reason = JsonValueAsString(val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response = MatrixGetAccessToken(args->context, &token);
|
||||
if (response)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
user = UserAuthenticate(db, token);
|
||||
removed = UserLock(db, removedLocalpart);
|
||||
if (!user || !removed)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!(UserGetPrivileges(user) & USER_DEACTIVATE))
|
||||
{
|
||||
char * msg = "User doesn't have the DEACTIVATE privilege.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (method == HTTP_DELETE)
|
||||
{
|
||||
UserDeactivate(removed, UserGetName(user), reason);
|
||||
response = HashMapCreate();
|
||||
|
||||
JsonSet(response, JsonValueString(removedLocalpart), 1, "user");
|
||||
JsonSet(response, JsonValueString(reason), 1, "reason");
|
||||
JsonSet(response, JsonValueString(UserGetName(user)), 1, "banned_by");
|
||||
}
|
||||
else
|
||||
{
|
||||
UserReactivate(removed);
|
||||
HttpResponseStatus(args->context, HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
finish:
|
||||
UserUnlock(user);
|
||||
UserUnlock(removed);
|
||||
JsonFree(request);
|
||||
return response;
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Routes.h>
|
||||
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
|
||||
#include <RegToken.h>
|
||||
#include <User.h>
|
||||
|
||||
ROUTE_IMPL(RouteAdminTokens, path, argp)
|
||||
{
|
||||
RouteArgs *args = argp;
|
||||
HashMap *request = NULL;
|
||||
HashMap *response = NULL;
|
||||
|
||||
char *token;
|
||||
char *msg;
|
||||
|
||||
Db *db = args->matrixArgs->db;
|
||||
|
||||
User *user = NULL;
|
||||
|
||||
HttpRequestMethod method = HttpRequestMethodGet(args->context);
|
||||
|
||||
Array *tokensarray;
|
||||
Array *tokens;
|
||||
|
||||
RegTokenInfo *info;
|
||||
|
||||
RegTokenInfo *req;
|
||||
|
||||
size_t i;
|
||||
|
||||
if (method != HTTP_GET && method != HTTP_POST && method != HTTP_DELETE)
|
||||
{
|
||||
msg = "Route only supports GET, POST, and DELETE";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
}
|
||||
|
||||
|
||||
response = MatrixGetAccessToken(args->context, &token);
|
||||
if (response)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
user = UserAuthenticate(db, token);
|
||||
if (!user)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!(UserGetPrivileges(user) & USER_ISSUE_TOKENS))
|
||||
{
|
||||
msg = "User doesn't have the ISSUE_TOKENS privilege.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case HTTP_GET:
|
||||
if (ArraySize(path) == 0)
|
||||
{
|
||||
tokensarray = ArrayCreate();
|
||||
|
||||
/* Get all registration tokens */
|
||||
tokens = DbList(db, 2, "tokens", "registration");
|
||||
|
||||
response = HashMapCreate();
|
||||
|
||||
for (i = 0; i < ArraySize(tokens); i++)
|
||||
{
|
||||
char *tokenname = ArrayGet(tokens, i);
|
||||
HashMap *jsoninfo;
|
||||
|
||||
info = RegTokenGetInfo(db, tokenname);
|
||||
jsoninfo = RegTokenInfoToJson(info);
|
||||
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
|
||||
ArrayAdd(tokensarray, JsonValueObject(jsoninfo));
|
||||
}
|
||||
|
||||
JsonSet(response, JsonValueArray(tokensarray), 1, "tokens");
|
||||
|
||||
DbListFree(tokens);
|
||||
break;
|
||||
}
|
||||
|
||||
info = RegTokenGetInfo(db, ArrayGet(path, 0));
|
||||
if (!info)
|
||||
{
|
||||
msg = "Token doesn't exist.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
response = RegTokenInfoToJson(info);
|
||||
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
break;
|
||||
case HTTP_POST:
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
if (!request)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
req = Malloc(sizeof(RegTokenInfo));
|
||||
memset(req, 0, sizeof(RegTokenInfo));
|
||||
|
||||
if (!RegTokenInfoFromJson(request, req, &msg))
|
||||
{
|
||||
RegTokenInfoFree(req);
|
||||
Free(req);
|
||||
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
|
||||
}
|
||||
|
||||
if (!req->name)
|
||||
{
|
||||
req->name = StrRandom(16);
|
||||
}
|
||||
|
||||
/* Create the actual token that will be stored. */
|
||||
info = RegTokenCreate(db, req->name, UserGetName(user),
|
||||
req->expires_on, req->uses,
|
||||
UserDecodePrivileges(req->grants));
|
||||
if (!info)
|
||||
{
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
RegTokenInfoFree(req);
|
||||
Free(req);
|
||||
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
msg = "Cannot create token.";
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
response = RegTokenInfoToJson(info);
|
||||
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
RegTokenInfoFree(req);
|
||||
Free(req);
|
||||
break;
|
||||
case HTTP_DELETE:
|
||||
if (ArraySize(path) == 0)
|
||||
{
|
||||
msg = "No registration token given to DELETE /tokens/[token].";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
info = RegTokenGetInfo(db, ArrayGet(path, 0));
|
||||
RegTokenDelete(info);
|
||||
|
||||
response = HashMapCreate();
|
||||
break;
|
||||
default:
|
||||
/* Should not be possible. */
|
||||
break;
|
||||
}
|
||||
finish:
|
||||
UserUnlock(user);
|
||||
JsonFree(request);
|
||||
return response;
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <Routes.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <Config.h>
|
||||
#include <Parser.h>
|
||||
#include <User.h>
|
||||
|
||||
ROUTE_IMPL(RouteAliasDirectory, path, argp)
|
||||
{
|
||||
RouteArgs *args = argp;
|
||||
char *alias = ArrayGet(path, 0);
|
||||
|
||||
HashMap *request = NULL;
|
||||
HashMap *response;
|
||||
|
||||
Db *db = args->matrixArgs->db;
|
||||
DbRef *ref = NULL;
|
||||
HashMap *aliases;
|
||||
HashMap *idObject;
|
||||
JsonValue *val;
|
||||
Array *arr;
|
||||
|
||||
char *token;
|
||||
char *msg;
|
||||
User *user = NULL;
|
||||
|
||||
CommonID aliasID;
|
||||
Config config;
|
||||
|
||||
aliasID.sigil = '\0';
|
||||
aliasID.local = NULL;
|
||||
aliasID.server.hostname = NULL;
|
||||
aliasID.server.port = NULL;
|
||||
|
||||
ConfigLock(db, &config);
|
||||
|
||||
if (!ParseCommonID(alias, &aliasID) || aliasID.sigil != '#')
|
||||
{
|
||||
msg = "Invalid room alias.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
ref = DbLock(db, 1, "aliases");
|
||||
if (!ref && !(ref = DbCreate(db, 1, "aliases")))
|
||||
{
|
||||
msg = "Unable to access alias database.",
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
aliases = DbJson(ref);
|
||||
|
||||
switch (HttpRequestMethodGet(args->context))
|
||||
{
|
||||
case HTTP_GET:
|
||||
val = JsonGet(aliases, 2, "alias", alias);
|
||||
if (val)
|
||||
{
|
||||
response = HashMapCreate();
|
||||
HashMapSet(response, "room_id", JsonValueDuplicate(HashMapGet(JsonValueAsObject(val), "id")));
|
||||
HashMapSet(response, "servers", JsonValueDuplicate(JsonGet(aliases, 3, "alias", alias, "servers")));
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = "There is no mapped room ID for this room alias.";
|
||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||
response = MatrixErrorCreate(M_NOT_FOUND, msg);
|
||||
}
|
||||
break;
|
||||
case HTTP_PUT:
|
||||
case HTTP_DELETE:
|
||||
response = MatrixGetAccessToken(args->context, &token);
|
||||
if (response)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
user = UserAuthenticate(db, token);
|
||||
if (!user)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (HttpRequestMethodGet(args->context) == HTTP_PUT)
|
||||
{
|
||||
HashMap *newAlias;
|
||||
char *id;
|
||||
char *serverPart;
|
||||
|
||||
serverPart = ParserRecomposeServerPart(aliasID.server);
|
||||
if (!StrEquals(serverPart, config.serverName))
|
||||
{
|
||||
msg = "Invalid server name.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
|
||||
Free(serverPart);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
Free(serverPart);
|
||||
|
||||
if (JsonGet(aliases, 2, "alias", alias))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_CONFLICT);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, "Room alias already exists.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
if (!request)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
id = JsonValueAsString(HashMapGet(request, "room_id"));
|
||||
if (!id)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, "Missing or invalid room_id.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!ValidCommonID(id, '!'))
|
||||
{
|
||||
msg = "Invalid room ID.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
newAlias = HashMapCreate();
|
||||
HashMapSet(newAlias, "createdBy", JsonValueString(UserGetName(user)));
|
||||
HashMapSet(newAlias, "id", JsonValueString(id));
|
||||
HashMapSet(newAlias, "servers", JsonValueArray(ArrayCreate()));
|
||||
|
||||
JsonSet(aliases, JsonValueObject(newAlias), 2, "alias", alias);
|
||||
|
||||
if (!(idObject = JsonValueAsObject(JsonGet(aliases, 2, "id", id))))
|
||||
{
|
||||
arr = ArrayCreate();
|
||||
idObject = HashMapCreate();
|
||||
HashMapSet(idObject, "aliases", JsonValueArray(arr));
|
||||
JsonSet(aliases, JsonValueObject(idObject), 2, "id", id);
|
||||
}
|
||||
val = HashMapGet(idObject, "aliases");
|
||||
arr = JsonValueAsArray(val);
|
||||
ArrayAdd(arr, JsonValueString(alias));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
HashMap *roomAlias;
|
||||
char *id;
|
||||
|
||||
if (!(val = JsonGet(aliases, 2, "alias", alias)))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||
response = MatrixErrorCreate(M_NOT_FOUND, "Room alias not found.");
|
||||
goto finish;
|
||||
}
|
||||
roomAlias = JsonValueAsObject(val);
|
||||
id = StrDuplicate(JsonValueAsString(HashMapGet(roomAlias, "id")));
|
||||
|
||||
if (!(UserGetPrivileges(user) & USER_ALIAS) && !StrEquals(UserGetName(user), JsonValueAsString(JsonGet(roomAlias, 1, "createdBy"))))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
||||
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
|
||||
Free(id);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
JsonValueFree(HashMapDelete(JsonValueAsObject(HashMapGet(aliases, "alias")), alias));
|
||||
|
||||
idObject = JsonValueAsObject(JsonGet(aliases, 2, "id", id));
|
||||
if (idObject)
|
||||
{
|
||||
size_t i;
|
||||
val = HashMapGet(idObject, "aliases");
|
||||
arr = JsonValueAsArray(val);
|
||||
for (i = 0; i < ArraySize(arr); i++)
|
||||
{
|
||||
if (StrEquals(JsonValueAsString(ArrayGet(arr, i)), alias))
|
||||
{
|
||||
JsonValueFree(ArrayDelete(arr, i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Free(id);
|
||||
}
|
||||
response = HashMapCreate();
|
||||
|
||||
break;
|
||||
default:
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, "Unknown request method.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
finish:
|
||||
CommonIDFree(aliasID);
|
||||
ConfigUnlock(&config);
|
||||
UserUnlock(user);
|
||||
DbUnlock(db, ref);
|
||||
JsonFree(request);
|
||||
return response;
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Routes.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
ROUTE_IMPL(RouteCapabilities, path, argp)
|
||||
{
|
||||
HashMap *response;
|
||||
HashMap *capabilities;
|
||||
HashMap *roomVersions;
|
||||
|
||||
(void) path;
|
||||
(void) argp;
|
||||
|
||||
response = HashMapCreate();
|
||||
capabilities = HashMapCreate();
|
||||
roomVersions = HashMapCreate();
|
||||
|
||||
JsonSet(capabilities, JsonValueBoolean(1), 2, "m.change_password", "enabled");
|
||||
JsonSet(capabilities, JsonValueBoolean(1), 2, "m.set_displayname", "enabled");
|
||||
JsonSet(capabilities, JsonValueBoolean(1), 2, "m.set_avatar_url", "enabled");
|
||||
JsonSet(capabilities, JsonValueBoolean(0), 2, "m.3pid_changes", "enabled");
|
||||
|
||||
/* TODO: When more room versions are implemented, add them to
|
||||
* roomVersions */
|
||||
HashMapSet(roomVersions, "1", JsonValueString("unstable"));
|
||||
|
||||
JsonSet(capabilities, JsonValueString("1"), 2, "m.room_versions", "default");
|
||||
JsonSet(capabilities, JsonValueObject(roomVersions), 2, "m.room_versions", "available");
|
||||
|
||||
HashMapSet(response, "capabilities", JsonValueObject(capabilities));
|
||||
return response;
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Routes.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Uia.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <User.h>
|
||||
|
||||
static Array *
|
||||
PasswordFlow(void)
|
||||
{
|
||||
Array *ret = ArrayCreate();
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
ArrayAdd(ret, UiaStageBuild("m.login.password", NULL));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ROUTE_IMPL(RouteChangePwd, path, argp)
|
||||
{
|
||||
RouteArgs *args = argp;
|
||||
Db *db = args->matrixArgs->db;
|
||||
|
||||
User *user = NULL;
|
||||
|
||||
HashMap *request = NULL;
|
||||
HashMap *response = NULL;
|
||||
|
||||
JsonValue *val = NULL;
|
||||
|
||||
Array *uiaFlows = NULL;
|
||||
|
||||
int uiaResult;
|
||||
int logoutDevices = 1;
|
||||
|
||||
char *token;
|
||||
char *newPassword;
|
||||
|
||||
char *msg;
|
||||
|
||||
Config config;
|
||||
|
||||
ConfigLock(db, &config);
|
||||
if (!config.ok)
|
||||
{
|
||||
Log(LOG_ERR, "%s", config.err);
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
return MatrixErrorCreate(M_UNKNOWN, config.err);
|
||||
}
|
||||
|
||||
(void) path;
|
||||
|
||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||
{
|
||||
msg = "Route only supports POST.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
response = MatrixGetAccessToken(args->context, &token);
|
||||
if (response)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
if (!request)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
uiaFlows = ArrayCreate();
|
||||
ArrayAdd(uiaFlows, PasswordFlow());
|
||||
uiaResult = UiaComplete(uiaFlows, args->context,
|
||||
db, request, &response,
|
||||
config);
|
||||
UiaFlowsFree(uiaFlows);
|
||||
|
||||
if (uiaResult < 0)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
goto finish;
|
||||
}
|
||||
else if (!uiaResult)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
newPassword = JsonValueAsString(HashMapGet(request, "new_password"));
|
||||
if (!newPassword)
|
||||
{
|
||||
msg = "'new_password' is unset or not a string.";
|
||||
JsonFree(request);
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "logout_devices");
|
||||
if (val)
|
||||
{
|
||||
logoutDevices = JsonValueAsBoolean(val);
|
||||
}
|
||||
|
||||
/* Let's authenticate the user */
|
||||
user = UserAuthenticate(db, token);
|
||||
|
||||
if (!user)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
UserSetPassword(user, newPassword);
|
||||
|
||||
/* We might want to logout all extra devices */
|
||||
if (logoutDevices)
|
||||
{
|
||||
/* Deletes all tokens except the passed token */
|
||||
UserDeleteTokens(user, token);
|
||||
}
|
||||
|
||||
response = HashMapCreate();
|
||||
|
||||
finish:
|
||||
ConfigUnlock(&config);
|
||||
UserUnlock(user);
|
||||
JsonFree(request);
|
||||
return response;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue