Finish converting all existing documentation. Next up is writing new docs.

This commit is contained in:
Jordan Bancino 2023-04-29 02:54:49 +00:00
parent 71fa96d10d
commit b70c3f0bed
16 changed files with 1035 additions and 242 deletions

View file

@ -32,10 +32,11 @@ Milestone: v0.3.0
[ ] http-debug-server
[ ] tp
[ ] send-patch
[ ] Log
[x] Log
[ ] TelodendriaConfig -> Config
[x] HashMap
[ ] HttpRouter
[x] Html
[ ] Str
[ ] HeaderParser
[ ] hdoc

View file

@ -568,7 +568,6 @@ HeaderParse(Stream * stream, HeaderExpr * expr)
{
/* Looks like we have an array. Slurp all the
* dimensions */
int block = 1;
int i = wordLen;
expr->data.global.name[i] = '[';

View file

@ -168,56 +168,6 @@ UtilSleepMillis(long ms)
return res;
}
size_t
UtilParseBytes(char *str)
{
size_t bytes = 0;
while (*str)
{
if (isdigit((unsigned char) *str))
{
bytes *= 10;
bytes += *str - '0';
}
else
{
switch (*str)
{
case 'K':
bytes *= 1024;
break;
case 'M':
bytes *= pow(1024, 2);
break;
case 'G':
bytes *= pow(1024, 3);
break;
case 'k':
bytes *= 1000;
break;
case 'm':
bytes *= pow(1000, 2);
break;
case 'g':
bytes *= pow(1000, 3);
break;
default:
return 0;
}
if (*(str + 1))
{
return 0;
}
}
str++;
}
return bytes;
}
ssize_t
UtilGetDelim(char **linePtr, size_t * n, int delim, Stream * stream)
{

View file

@ -25,10 +25,37 @@
#ifndef TELODENDRIA_CONFIG_H
#define TELODENDRIA_CONFIG_H
/***
* @Nm Config
* @Nd Parse the Telodendria configuration into a structure.
* @Dd April 28 2023
* @Xr Db Json HttpServer Log
*
* .Nm
* validates and maintains the Telodendria server's configuration data.
* This API builds on the database and JSON APIs to add parsing
* specific to Telodendria. It converts a configuration in its raw
* form into a structure that is much easier and more convenient to
* work with.
* .Pp
* Since very early on in Telodendria's development, the configuration
* file has existed inside of the database. This API also offers
* convenience methods for extracting the configuration out of the
* database.
* .Pp
* This documentation does not describe the actual format of the
* configuration file; for that, consult
* .Xr telodendria-config 7 .
*/
#include <HashMap.h>
#include <Array.h>
#include <Db.h>
/**
* Bit flags that can be set in the flags field of the configuration
* structure.
*/
typedef enum ConfigFlag
{
CONFIG_FEDERATION = (1 << 0),
@ -39,11 +66,29 @@ typedef enum ConfigFlag
CONFIG_LOG_SYSLOG = (1 << 5)
} ConfigFlag;
/**
* The configuration structure is not opaque like many of the other
* structures present in the other public APIs. This is intentional;
* defining functions for all of the fields would simply add too much
* unnecessary overhead.
*/
typedef struct Config
{
/*
* These are used internally and should not be touched outside of
* the functions defined in this API.
*/
Db *db;
DbRef *ref;
/*
* Whether or not the parsing was successful. If this boolean
* value is 0, then read the error message and assume that all
* other fields are invalid.
*/
int ok;
char *err;
char *serverName;
char *baseUrl;
char *identityServer;
@ -58,28 +103,61 @@ typedef struct Config
char *logTimestamp;
int logLevel;
/*
* An array of HttpServerConfig structures. Consult the HttpServer
* API.
*/
Array *servers;
int ok;
char *err;
} Config;
Config *
ConfigParse(HashMap *);
/**
* Parse a JSON object, extracting the necessary values, validating
* them, and adding them to the configuration structure for use by the
* caller. All values are copied, so the JSON object can be safely
* freed after this function returns.
* .Pp
* If an error occurs, this function will not return NULL, but it will
* set the ok flag to 0. The caller should always check the ok flag,
* and if there is an error, it should display the error to the user.
*/
Config * ConfigParse(HashMap *);
void
ConfigFree(Config *);
/**
* Free all the values inside of the given configuration structure,
* as well as the structure itself, such that it is completely invalid
* when this function returns.
*/
void ConfigFree(Config *);
extern int
ConfigExists(Db *);
/**
* Check whether or not the configuration exists in the database,
* returning a boolean value to indicate the status.
*/
extern int ConfigExists(Db *);
extern int
ConfigCreateDefault(Db *);
/**
* Create a sane default configuration in the specified database.
* This function returns a boolean value indicating whether or not it
* was successful.
*/
extern int ConfigCreateDefault(Db *);
extern Config *
ConfigLock(Db *);
/**
* Lock the configuration in the database using
* .Fn DbLock ,
* and then parse the object using
* .Fn ConfigParse .
* The return value of this function is the same as
* .Fn ConfigParse .
*/
extern Config * ConfigLock(Db *);
extern int
ConfigUnlock(Config *);
/**
* Unlock the specified configuration, returning it back to the
* database. This function also invalidates all memory associated with
* this config object, so values that should be retained after this is
* called should be duplicated as necessary.
*/
extern int ConfigUnlock(Config *);
#endif /* TELODENDRIA_CONFIG_H */

View file

@ -24,6 +24,37 @@
#ifndef TELODENDRIA_HTML_H
#define TELODENDRIA_HTML_H
/***
* @Nm Html
* @Nd Utility functions for generating static HTML pages.
* @Dd April 27 2023
*
* .Nm
* provides some simple macros and functions for generating HTML
* pages. These are very specific to Telodendria, as they automatically
* apply the color scheme and make assumptions about the stylesheets
* and scripts included.
* .Pp
* The following macros are available:
* .Bl -tag -width Ds
* .It HtmlBeginJs(stream)
* Begin JavaScript output. This sets up the opening script tags, and
* licenses the following JavaScript under the MIT license.
* .It HtmlEndJs(stream)
* End JavaScript output.
* .It HtmlBeginStyle(stream)
* Begin CSS output. This sets up the opening syle tags.
* .It HtmlEndStyle(stream)
* End CSS output.
* .It HtmlBeginForm(stream, id)
* Begin a new form with the specified ID. This sets up the opening
* form tags, which includes placing the form in a div with class
* 'form'.
* .It HtmlEndForm(stream)
* End HTML form output.
* .El
*/
#include <Stream.h>
#define HtmlBeginJs(stream) StreamPuts(stream, \
@ -46,10 +77,18 @@
"<p id=\"error-msg\"></p>" \
"</div>");
extern void
HtmlBegin(Stream *, char *);
/**
* Initialize an HTML page by writing the head and the beginning of the
* body. After this function is called, the page's main HTML can be
* written. This function takes the name of the page it is beginning.
* The name is placed in the title tags, and is used as the page
* header.
*/
extern void HtmlBegin(Stream *, char *);
extern void
HtmlEnd(Stream *);
/**
* Finish an HTML page by writing any necessary closing tags.
*/
extern void HtmlEnd(Stream *);
#endif /* TELODENDRIA_HTML_H */

View file

@ -109,25 +109,88 @@ extern void LogConfigUnindent(LogConfig *);
*/
extern void LogConfigIndentSet(LogConfig *, size_t);
extern void
LogConfigOutputSet(LogConfig *, Stream *);
/**
* Set the file stream that logging output should be written to. This
* defaults to standard output, but it can be set to standard error,
* or any other arbitrary stream. Passing a NULL value for the stream
* pointer sets the log output to the standard output. Note that the
* output stream is only used if
* .Va LOG_FLAG_SYSLOG
* is not set.
*/
extern void LogConfigOutputSet(LogConfig *, Stream *);
extern void
LogConfigFlagSet(LogConfig *, int);
/**
* Set a number of boolean options on a log configuration. This
* function uses bitwise operators, so multiple options can be set with
* a single function call using bitwise OR operators. The flags are
* defined as preprocessor macros, and are as follows:
* .Bl -tag -width Ds
* .It LOG_FLAG_COLOR
* When set, enable color-coded output on TTYs. Note that colors are
* implemented as ANSI escape sequences, and are not written to file
* streams that are not actually connected to a TTY, to prevent those
* sequences from being written to a file.
* .Xr isatty 3
* is checked before writing any terminal sequences.
* .It LOG_FLAG_SYSLOG
* When set, log output to the syslog using
* .Xr syslog 3 ,
* instead of logging to the file set by
* .Fn LogConfigOutputSet .
* This flag always overrides the stream set by that function,
* regardless of when it was set, even if it was set after this flag
* was set.
* .El
*/
extern void LogConfigFlagSet(LogConfig *, int);
extern void
LogConfigFlagClear(LogConfig *, int);
/**
* Clear a boolean flag from the specified log format. See above for
* the list of flags.
*/
extern void LogConfigFlagClear(LogConfig *, int);
extern void
LogConfigTimeStampFormatSet(LogConfig *, char *);
/**
* Set a custom timestamp to be prepended to each message if the
* output is not going to the system log. Consult your system's
* documentation for
* .Xr strftime 3 .
* A value of NULL disables the timestamp output before messages.
*/
extern void LogConfigTimeStampFormatSet(LogConfig *, char *);
extern void
Logv(LogConfig *, int, const char *, va_list);
/**
* This function does the actual logging of messages using a
* specified configuration. It takes the configuration, the log
* level, a format string, and then a list of arguments, all in that
* order. This function only logs messages if their level is above
* or equal to the currently configured log level, making it easy to
* turn some messages on or off.
* .Pp
* This function has the same usage as
* .Xr vprintf 3 .
* Consult that page for the list of format specifiers and their
* arguments. This function is typically not used directly, see the
* other log functions for the most common use cases.
*/
extern void Logv(LogConfig *, int, const char *, va_list);
extern void
LogTo(LogConfig *, int, const char *,...);
/**
* Log a message using
* .Fn Logv .
* with the specified configuration. This function has the same usage
* as
* .Xr printf 3 .
*/
extern void LogTo(LogConfig *, int, const char *, ...);
extern void
Log(int, const char *,...);
/**
* Log a message to the global log using
* .Fn Logv .
* This function has the same usage as
* .Xr printf 3 .
*/
extern void Log(int, const char *, ...);
#endif

View file

@ -24,6 +24,18 @@
#ifndef TELODENDRIA_MATRIX_H
#define TELODENDRIA_MATRIX_H
/***
* @Nm Matrix
* @Nd Functions for writing Matrix API Endpoints.
* @Dd March 6 2023
* @Xr HttpServer Log Config Db
*
* .Nm
* provides some helper functions that bind to the HttpServer API and
* add basic Matrix functionality, turning an HTTP server into a
* Matrix homeserver.
*/
#include <HttpServer.h>
#include <HttpRouter.h>
#include <Log.h>
@ -32,6 +44,12 @@
#include <Config.h>
#include <Db.h>
/**
* The valid errors that can be used with
* .Fn MatrixErrorCreate .
* These values exactly follow the errors defined in the Matrix
* specification.
*/
typedef enum MatrixError
{
M_FORBIDDEN,
@ -68,25 +86,70 @@ typedef enum MatrixError
M_CANNOT_LEAVE_SERVER_NOTICE_ROOM
} MatrixError;
/**
* The arguments that should be passed through the void pointer to the
* .Fn MatrixHttpHandler
* function. This structure should be populated once, and then never
* modified again for the duration of the HTTP server.
*/
typedef struct MatrixHttpHandlerArgs
{
Db *db;
HttpRouter *router;
} MatrixHttpHandlerArgs;
extern void
MatrixHttpHandler(HttpServerContext *, void *);
/**
* The HTTP handler function that handles all Matrix homeserver
* functionality. It should be passed into
* .Fn HttpServerCreate ,
* and it expects that a pointer to a MatrixHttpHandlerArgs
* will be provided, because that is what the void pointer is
* cast to.
*/
extern void MatrixHttpHandler(HttpServerContext *, void *);
extern HashMap *
MatrixErrorCreate(MatrixError);
/**
* A convenience function that constructs an error payload, including
* the error code and message, given just a MatrixError.
*/
extern HashMap * MatrixErrorCreate(MatrixError);
extern HashMap *
MatrixGetAccessToken(HttpServerContext *, char **);
/**
* Read the request headers and parameters, and attempt to obtain an
* access token from them. The Matrix specification says that an access
* token can either be provided via the Authorization header, or in a
* .Sy GET
* parameter. This function checks both, and stores the access token it
* finds in the passed character pointer.
* .Pp
* The specification does not say whether the header or parameter
* should be preferred if both are provided. This function prefers the
* header.
* .Pp
* If this function returns a non-NULL value, then the return value
* should be immediately passed along to the client and no further
* logic should be performed.
*/
extern HashMap * MatrixGetAccessToken(HttpServerContext *, char **);
extern HashMap *
MatrixRateLimit(HttpServerContext *, Db *);
/**
* Determine whether or not the request should be rate limited. It is
* expected that this function will be called before most, if not all
* of the caller's logic.
* .Pp
* If this function returns a non-NULL value, then the return value
* should be immediately passed along to the client and no further
* logic should be performed.
*/
extern HashMap * MatrixRateLimit(HttpServerContext *, Db *);
extern HashMap *
MatrixClientWellKnown(char *, char *);
/**
* Build a ``well-known'' JSON object, which contains information
* about the homeserver base URL and identity server, both of which
* should be provided by the caller in that order. This object can be
* sent to a client as-is, or it can be added as a value nested inside
* of a more complex response. Both occur in the Matrix specification.
*/
extern HashMap * MatrixClientWellKnown(char *, char *);
#endif

View file

@ -24,8 +24,63 @@
#ifndef TELODENDRIA_MEMORY_H
#define TELODENDRIA_MEMORY_H
/***
* @Nm Memory
* @Nd Smart memory management.
* @Dd January 9 2023
*
* .Nm
* is an API that allows for smart memory management and profiling. It
* wraps the standard library functions
* .Xr malloc 3 ,
* .Xr realloc 3 ,
* and
* .Xr free 3 ,
* and offers identical semantics, while providing functionality that
* the standard library doesn't have, such as getting statistics on the
* total memory allocated on the heap, and getting the size of a block
* given a pointer. Additionally, thanks to preprocessor macros, the
* exact file and line number at which an allocation, re-allocation, or
* free occured can be obtained given a pointer. Finally, all the
* blocks allocated on the heap can be iterated and evaluated, and a
* callback function can be executed every time a memory operation
* occurs.
* .Pp
* In the future, this API could include a garbage collector that
* automatically frees memory it detects as being no longer in use.
* However, this feature does not yet exist.
* .Pp
* A number of macros are available, which make the
* .Nm
* API much easier to use. They are as follows:
* .Bl -bullet -offset indent
* .It
* .Fn Malloc "x"
* .It
* .Fn Realloc "x" "y"
* .It
* .Fn Free "x"
* .El
* .Pp
* These macros expand to
* .Fn MemoryAllocate ,
* .Fn MemoryReallocate ,
* and
* .Fn MemoryFree
* with the second and third parameters set to __FILE__ and __LINE__.
* This allows
* .Nm
* to be used exactly how the standard library functions would be
* used. In fact, the functions to which these macros expand are not
* intended to be used directly; for the best results, use these
* macros.
*/
#include <stddef.h>
/**
* These values are passed into the memory hook function to indicate
* the action that just happened.
*/
typedef enum MemoryAction
{
MEMORY_ALLOCATE,
@ -38,45 +93,133 @@ typedef enum MemoryAction
#define Realloc(x, s) MemoryReallocate(x, s, __FILE__, __LINE__)
#define Free(x) MemoryFree(x, __FILE__, __LINE__)
/**
* The memory information is opaque, but can be accessed using the
* functions defined by this API.
*/
typedef struct MemoryInfo MemoryInfo;
extern void *
MemoryAllocate(size_t, const char *, int);
/**
* Allocate the specified number of bytes on the heap. This function
* has the same semantics as
* .Xr malloc 3 ,
* except that it takes the file name and line number at which the
* allocation occurred.
*/
extern void * MemoryAllocate(size_t, const char *, int);
extern void *
MemoryReallocate(void *, size_t, const char *, int);
/**
* Change the size of the object pointed to by the given pointer
* to the given number of bytes. This function has the same semantics
* as
* .Xr realloc 3 ,
* except that it takes the file name and line number at which the
* reallocation occurred.
*/
extern void * MemoryReallocate(void *, size_t, const char *, int);
/**
* Free the memory at the given pointer. This function has the same
* semantics as
* .Xr free 3 ,
* except that it takes the file name and line number at which the
* free occurred.
*/
extern void MemoryFree(void *, const char *, int);
/**
* Get the total number of bytes that the program has allocated on the
* heap. This operation iterates over all heap allocations made with
* .Fn MemoryAllocate
* and then returns a total count, in bytes.
*/
extern size_t MemoryAllocated(void);
/**
* Iterate over all heap allocations made with
* .Fn MemoryAllocate
* and call
* .Fn MemoryFree
* on them. This function immediately invalidates all pointers to
* blocks on the heap, and any subsequent attempt to read or write to
* data on the heap will result in undefined behavior. This is
* typically called at the end of the program, just before exit.
*/
extern void MemoryFreeAll(void);
/**
* Fetch information about an allocation. This function takes a raw
* pointer, and if
* . Nm
* knows about the pointer, it returns a structure that can be used
* to obtain information about the block of memory that the pointer
* points to.
*/
extern MemoryInfo * MemoryInfoGet(void *);
/**
* Get the size in bytes of the block of memory represented by the
* specified memory info structure.
*/
extern size_t MemoryInfoGetSize(MemoryInfo *);
/**
* Get the file name in which the block of memory represented by the
* specified memory info structure was allocated.
*/
extern const char * MemoryInfoGetFile(MemoryInfo *);
/**
* Get the line number on which the block of memory represented by the
* specified memory info structure was allocated.
*/
extern int MemoryInfoGetLine(MemoryInfo *);
/**
* Get a pointer to the block of memory represented by the specified
* memory info structure.
*/
extern void * MemoryInfoGetPointer(MemoryInfo *);
/**
* This function takes a pointer to a function that takes the memory
* info structure, as well as a void pointer for caller-provided
* arguments. It iterates over all the heap memory currently allocated
* at the time of calling, executing the function on each allocation.
*/
extern void MemoryIterate(void (*) (MemoryInfo *, void *), void *);
/**
* Specify a function to be executed whenever a memory operation
* occurs. The MemoryAction argument specifies the operation that
* occurred on the block of memory represented by the memory info
* structure. The function also takes a void pointer to caller-provided
* arguments.
*/
extern void MemoryHook(void (*) (MemoryAction, MemoryInfo *, void *), void *);
/**
* Read over the block of memory represented by the given memory info
* structure and generate a hexadecimal and ASCII string for each
* chunk of the block. This function takes a callback function that
* takes the following parameters in order:
* .Bl -bullet -offset indent
* .It
* The current offset from the beginning of the block of memory in
* bytes.
* .It
* A null-terminated string containing the next 16 bytes of the block
* encoded as space-separated hex values.
* .It
* A null-terminated string containing the ASCII representation of the
* same 16 bytes of memory. This ASCII representation is safe to print
* to a terminal or other text device, because non-printable characters
* are encoded as a . (period).
* .It
* Caller-passed pointer.
* .El
*/
extern void
MemoryFree(void *, const char *, int);
extern size_t
MemoryAllocated(void);
extern void
MemoryFreeAll(void);
extern MemoryInfo *
MemoryInfoGet(void *);
extern size_t
MemoryInfoGetSize(MemoryInfo *);
extern const char *
MemoryInfoGetFile(MemoryInfo *);
extern int
MemoryInfoGetLine(MemoryInfo *);
extern void *
MemoryInfoGetPointer(MemoryInfo *);
extern void
MemoryIterate(void (*) (MemoryInfo *, void *), void *);
extern void
MemoryHook(void (*) (MemoryAction, MemoryInfo *, void *), void *);
extern void
MemoryHexDump(MemoryInfo *, void (*) (size_t, char *, char *, void *), void *);
MemoryHexDump(MemoryInfo *, void (*) (size_t, char *, char *, void *), void *);
#endif

View file

@ -24,29 +24,82 @@
#ifndef TELODENDRIA_QUEUE_H
#define TELODENDRIA_QUEUE_H
/***
* @Nm Queue
* @Nd A simple static queue data structure.
* @Dd November 25 2022
* @Xr Array HashMap
*
* .Nm
* implements 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 have
* to manually maintain data and make sure it remains valid as long as
* it is in the queue. The advantage of this is that
* .Nm
* doesn't have to copy data, and thus doesn't care how big the data
* is. Furthermore, any 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.
*/
#include <stddef.h>
/**
* These functions operate on a queue structure that is opaque to the
* caller.
*/
typedef struct Queue Queue;
extern Queue *
QueueCreate(size_t);
/**
* Allocate a new queue that is able to store the specified number of
* items in it.
*/
extern Queue * QueueCreate(size_t);
extern void
QueueFree(Queue *);
/**
* Free the memory associated with the specified queue structure. Note
* that this function does not free any of the values stored in the
* queue; it is the caller's job to manage memory for each item.
* Typically, the caller would dequeue all the items in the queue and
* deal with them before freeing the queue itself.
*/
extern void QueueFree(Queue *);
extern int
QueuePush(Queue *, void *);
/**
* Push an element into the queue. This function returns a boolean
* value indicating whether or not the push succeeded. Pushing items
* into the queue will fail if the queue is full.
*/
extern int QueuePush(Queue *, void *);
extern void *
QueuePop(Queue *);
/**
* Pop an element out of the queue. This function returns NULL if the
* queue is empty. Otherwise, it returns a pointer to the item that is
* next up in the queue.
*/
extern void * QueuePop(Queue *);
extern void *
QueuePeek(Queue *);
/**
* Retrieve a pointer to the item that is next up in the queue without
* actually discarding it, such that the next call to
* .Fn QueuePeek
* or
* .Fn QueuePop
* will return the same pointer.
*/
extern void * QueuePeek(Queue *);
extern int
QueueFull(Queue *);
/**
* Determine whether or not the queue is full.
*/
extern int QueueFull(Queue *);
extern int
QueueEmpty(Queue *);
/**
* Determine whether or not the queue is empty.
*/
extern int QueueEmpty(Queue *);
#endif

View file

@ -24,12 +24,58 @@
#ifndef TELODENDRIA_RAND_H
#define TELODENDRIA_RAND_H
/***
* @Nm Rand
* @Nd Thread-safe random numbers.
* @Dd February 16 2023
* @Xr Util
*
* .Nm
* is used for generating random numbers in a thread-safe way.
* Currently, one generator state is shared across all threads, which
* means that only one thread can generate random numbers at a time.
* This state is protected with a mutex to guarantee this behavior.
* In the future, a seed pool may be maintained to allow multiple
* threads to generate random numbers at the same time.
* .Pp
* The generator state is seeded on the first call to a function that
* needs it. The seed is determined by the current timestamp, the ID
* of the process, and the thread ID. These should all be sufficiently
* random sources, so the seed should be secure enough.
* .Pp
* .Nm
* currently uses a simple Mersenne Twister algorithm to generate
* random numbers. This algorithm was chosen because it is extremely
* popular and widespread. While it is likely not cryptographically
* secure, and does suffer some unfortunate pitfalls, this algorithm
* has stood the test of time and is simple enough to implement, so
* it was chosen over the alternatives.
* .Pp
* .Nm
* does not use any random number generator functions from the C
* standard library, since these are often flawed.
*/
#include <stddef.h>
extern int
RandInt(unsigned int);
/**
* Generate a single random integer between 0 and the passed value.
*/
extern int RandInt(unsigned int);
extern void
RandIntN(int *, size_t, unsigned int);
/**
* Generate the number of integers specified by the second argument
* storing them into the buffer pointed to in the first argument.
* Ensure that each number is between 0 and the third argument.
* .Pp
* This function allows a caller to get multiple random numbers at once
* in a more efficient manner than repeatedly calling
* .Fn RandInt ,
* since each call to these functions
* has to lock and unlock a mutex. It is therefore better to obtain
* multiple random numbers in one pass if multiple are needed.
*/
extern void RandIntN(int *, size_t, unsigned int);
#endif /* TELODENDRIA_RAND_H */

View file

@ -24,7 +24,19 @@
#ifndef TELODENDRIA_ROUTES_H
#define TELODENDRIA_ROUTES_H
#include <string.h>
/***
* @Nm Routes
* @Nd Matrix API endpoint handler functions.
* @Dd April 28 2023
* @Xr Matrix HttpRouter
*
* .Nm
* provides all of the Matrix API route functions, which for the sake
* of brevity are not documented here---consult the official Matrix
* specification for documentation on Matrix routes and the
* .Xr telodendria-admin
* page for admin API routes.
*/
#include <HashMap.h>
#include <Array.h>
@ -32,25 +44,34 @@
#include <HttpRouter.h>
#include <Matrix.h>
#include <string.h>
#define MATRIX_PATH_EQUALS(pathPart, str) \
((pathPart != NULL) && (strcmp(pathPart, str) == 0))
/**
* Every route function takes this structure, which contains the data
* it needs to successfully handle an API request.
*/
typedef struct RouteArgs
{
MatrixHttpHandlerArgs *matrixArgs;
HttpServerContext *context;
} RouteArgs;
HttpRouter *
RouterBuild(void);
/**
* Build an HTTP router that sets up all the route functions to be
* executed at the correct HTTP paths.
*/
extern HttpRouter * RouterBuild(void);
#define ROUTE(name) \
extern void * \
name(Array *, void *)
#define ROUTE_IMPL(name, matchesName, argsName) \
#define ROUTE_IMPL(name, path, args) \
void * \
name(Array * matchesName, void * argsName)
name(Array * path, void * args)
ROUTE(RouteVersions);
ROUTE(RouteWellKnown);

View file

@ -24,24 +24,68 @@
#ifndef TELODENDRIA_STR_H
#define TELODENDRIA_STR_H
/***
* @Nm Str
* @Nd Functions for creating and manipulating strings.
* @Dd February 15 2023
* @Xr Memory
*
* .Nm
* provides string-related functions. It is called
* .Nm ,
* not String, because some platforms (Windows) do not have
* case-sensitive filesystems, which poses a problem since
* .Pa string.h
* is a standard library header.
*/
#include <stddef.h>
extern char *
StrUtf8Encode(unsigned long);
/**
* Take a UTF-8 codepoint and encode it into a string buffer containing
* between 1 and 4 bytes. The string buffer is allocated on the heap,
* so it should be freed when it is no longer needed.
*/
extern char * StrUtf8Encode(unsigned long);
extern char *
StrDuplicate(const char *);
/**
* Duplicate a null-terminated string, returning a new string on the
* heap. This is useful when a function takes in a string that it needs
* to store for long amounts of time, even perhaps after the original
* string is gone.
*/
extern char * StrDuplicate(const char *);
extern char *
StrSubstr(const char *, size_t, size_t);
/**
* Extract part of a null-terminated string, returning a new string on
* the heap containing only the requested subsection. Like the
* substring functions included with most programming languages, the
* starting index is inclusive, and the ending index is exclusive.
*/
extern char * StrSubstr(const char *, size_t, size_t);
extern char *
StrConcat(size_t,...);
/**
* A varargs function that takes a number of null-terminated strings
* specified by the first argument, and returns a new string that
* contains their concatenation. It works similarly to
* .Xr strcat 3 ,
* but it takes care of allocating memory big enough to hold all the
* strings. Any string in the list may be NULL. If a NULL pointer is
* passed, it is treated like an empty string.
*/
extern char * StrConcat(size_t,...);
extern int
StrBlank(const char *str);
/**
* Return a boolean value indicating whether or not the null-terminated
* string consists only of blank characters, as determined by
* .Xr isblank 3 .
*/
extern int StrBlank(const char *str);
extern char *
StrRandom(size_t);
/**
* Generate a string of the specified length, containing random
* lowercase and uppercase letters.
*/
extern char * StrRandom(size_t);
#endif /* TELODENDRIA_STR_H */

View file

@ -24,26 +24,121 @@
#ifndef TELODENDRIA_UIA_H
#define TELODENDRIA_UIA_H
/***
* @Nm Uia
* @Nd User Interactive Authentication.
* @Dd April 28 2023
* @Xr User Db Cron
*
* .Nm
* takes care of all the logic for performing user interactive
* authentication as defined by the Matrix specification. API endpoints
* that require authentication via user interactive authentication
* build up flows and add any necessary parameters, and pass them all
* into
* .Fn UiaComplete .
* The goal is to make it easy for the numerous API endpoints that
* utilize this authentication mechanism to implement it.
*/
#include <Array.h>
#include <HashMap.h>
#include <HttpServer.h>
#include <Matrix.h>
/**
* An opaque structure that represents a single stage, which consists
* of the type and a JSON object that contains implementation-specific
* parameters for completing the stage.
*/
typedef struct UiaStage UiaStage;
extern UiaStage *
UiaStageBuild(char *, HashMap *);
/**
* Build a single stage with the type and a JSON object of parameters
* the client may require to complete it. Consult the Matrix
* specification for the valid types.
*/
extern UiaStage * UiaStageBuild(char *, HashMap *);
extern Array *
UiaDummyFlow(void);
/**
* Build a flow that consists only of a dummy stage. This is useful
* when an endpoint is required by the specification to use user
* interactive authentication, but doesn't want to actually require
* the user to do anything. Since the dummy flow is a fairly common
* flow, it seemed sensible to have a function for it. Other flows are
* built manually by the caller that that wishes to perform user
* interactive authentication.
*/
extern Array * UiaDummyFlow(void);
extern void
UiaCleanup(MatrixHttpHandlerArgs *);
/**
* This function should be called periodically to purge old sessions.
* Sessions are only valid for a few minutes after their last access.
* After that, they should be purged so that the database doesn't fill
* up with old session files. This function is specifically designed
* to be called via the Cron API.
*/
extern void UiaCleanup(MatrixHttpHandlerArgs *);
/**
* Validate an auth object and maintain session state to track the
* progress of a client through user interactive authentication flows.
* The idea is that an API endpoint will not progress until user
* interactive authentication has succeeded.
* .Pp
* This function does the bulk of the work for user interactive
* authentication. It takes many parameters:
* .Bl -bullet -offset indent
* .It
* An array of arrays of stages. Stages should be created with
* .Fn UiaStageBuild
* and then put into an array to create a flow. Those flows should then
* be put into an array and passed as this parameter. It is important
* to note here that because of the loose typing of the Array API, it
* is very easy to make mistakes here; if you are implementing a new
* endpoint that requires user interactive authentication, it is best
* to refer to the source code of an existing endpoint to get a better
* idea of how it works.
* .It
* An HTTP server context. This is required to set the response headers
* in the even of an error.
* .It
* The database where user interactive authentication sessions are
* persisted.
* .It
* The JSON request body that contains the client's auth object, which
* will be read, parsed, and handled as appropriate.
* .It
* A pointer to a pointer where a JSON response can be placed if
* necessary. If this function encounters a client error, such as a
* failure to authenticate, or outstanding stages that have not yet
* been completed, it will place a JSON response here that is expected
* to be returned to the client. This response will include a
* description of all the flows, stages, and the stage parameters.
* .It
* A valid configuration structure, because a few values are read from
* the configuration during certain stages of authentication.
* .El
* .Pp
* This function returns an integer value less than zero if it
* experiences an internal failure, such as a failure to allocate
* memory memory, or a corrupted database. It returns 0 if the client
* has remaining stages to complete, including the current stage if
* that one did not complete successfully. In this case, this function
* will set the proper response headers and the passed response
* pointer, so the caller should immediately return the response to
* the client. This function returns 1 if and only if the client has
* successfully completed all stages. Only in this latter case shall
* the caller proceed with its logic.
*/
extern int
UiaComplete(Array *, HttpServerContext *, Db *, HashMap *, HashMap **, Config *);
extern void
UiaFlowsFree(Array *);
/**
* Free an array of flows, as described above. Even though the caller
* constructs this array, it is convenient to free it in its
* entirety in a single function call.
*/
extern void UiaFlowsFree(Array *);
#endif

View file

@ -24,21 +24,50 @@
#ifndef TELODENDRIA_USER_H
#define TELODENDRIA_USER_H
/***
* @Nm User
* @Nd Convenience functions for working with local users.
* @Dd April 28 2023
* @Xr Db
*
* The
* .Nm
* API provides a wrapper over the database and offers an easy way to
* manage local users. It supports all of the locking mechanisms that
* the database does, and provides features for authenticating local
* users, among many other tasks.
*/
#include <Db.h>
#include <Json.h>
#define USER_DEACTIVATE (1 << 0)
#define USER_ISSUE_TOKENS (1 << 1)
#define USER_CONFIG (1 << 2)
#define USER_GRANT_PRIVILEGES (1 << 3)
#define USER_PROC_CONTROL (1 << 4)
#define USER_NONE 0
#define USER_ALL ((1 << 5) - 1)
/**
* Many functions here operate on an opaque user structure.
*/
typedef struct User User;
/**
* Local users can have privileges to access the administrator API.
* These are the individual privileges that Telodendria supports.
* Note that they are bit flags, so they can be bitwise OR-ed together
* to have multiple privileges.
*/
typedef enum UserPrivileges
{
USER_NONE = 0,
USER_DEACTIVATE = (1 << 0),
USER_ISSUE_TOKENS = (1 << 1),
USER_CONFIG = (1 << 2),
USER_GRANT_PRIVILEGES = (1 << 3),
USER_PROC_CONTROL = (1 << 4),
USER_ALL = ((1 << 5) - 1)
} UserPrivileges;
/**
* A description of an access token, which users use to authenticate
* with the client-server API.
*/
typedef struct UserAccessToken
{
char *user;
@ -47,95 +76,214 @@ typedef struct UserAccessToken
long lifetime;
} UserAccessToken;
/**
* Login information, which is in most cases returned to the user
* upon a successful login.
*/
typedef struct UserLoginInfo
{
UserAccessToken *accessToken;
char *refreshToken;
} UserLoginInfo;
/**
* A description of a Matrix user ID.
*/
typedef struct UserId
{
char *localpart;
char *server;
} UserId;
extern int
UserValidate(char *, char *);
/**
* Take a localpart and domain as separate parameters and validate them
* against the rules of the Matrix specification. The reasion the
* domain is required is because the spec imposes limitations on the
* length of the user ID, so the longer the domain name is, the shorter
* the local part is allowed to be. This function is used to ensure
* that client-provided Matrix IDs are valid on this server.
*/
extern int UserValidate(char *, char *);
extern int
UserHistoricalValidate(char *, char *);
/**
* This function behaves just like
* .Fn UserValidate ,
* except that it is a little more lenient in what is considers to be
* a valid Matrix ID. This is typically to validate users that exist
* on other servers, since some usernames may exist that are not fully
* spec compliant but remain in use since before the new restrictions
* were put in place.
*/
extern int UserHistoricalValidate(char *, char *);
extern int
UserExists(Db *, char *);
/**
* Determine whether the user identified by the specified localpart
* exists in the database.
*/
extern int UserExists(Db *, char *);
extern User *
UserCreate(Db *, char *, char *);
/**
* Create a new user with the specified localpart and password, in
* that order.
*/
extern User * UserCreate(Db *, char *, char *);
extern User *
UserLock(Db *, char *);
/**
* Take a localpart and obtain a database reference to the user
* identified by that localpart. This function behaves analogously
* to
* .Fn DbLock ,
* and in fact it uses it under the hood to ensure that the user can
* only be modified by the thread that has locked it.
*/
extern User * UserLock(Db *, char *);
extern User *
UserAuthenticate(Db *, char *);
/**
* Take an access token, figure out what user it belongs to, and then
* returns a reference to that user. This function should be used by
* most endpoints that require user authentication, since most
* endpoints are authenticated via access tokens.
*/
extern User * UserAuthenticate(Db *, char *);
extern int
UserUnlock(User *);
/**
* Return a user reference back to the database. This function uses
* .Fn DbUnlock
* under the hood.
*/
extern int UserUnlock(User *);
extern UserLoginInfo *
UserLogin(User *, char *, char *, char *, int);
/**
* Log in a user. This function takes the user's password, desired
* device ID and display name, and a boolean value indicating whether
* or not the client supports refresh tokens. This function logs the
* the user in and generates an access token to be returned to the
* client.
*/
extern UserLoginInfo * UserLogin(User *, char *, char *, char *, int);
extern char *
UserGetName(User *);
/**
* Get the localpart attached to a user object. This function may be
* useful in the few cases where the localpart is not known already.
*/
extern char * UserGetName(User *);
extern int
UserCheckPassword(User *, char *);
/**
* Take a password and verify it against a user object. Telodendria
* does not store passwords in plain text, so this function hashes the
* password and checks it against what is stored in the database.
*/
extern int UserCheckPassword(User *, char *);
extern int
UserSetPassword(User *, char *);
/**
* Reset the given user's password by hashing a plain text password and
* storing it in the database.
*/
extern int UserSetPassword(User *, char *);
extern int
UserDeactivate(User *);
extern int
UserDeactivated(User *);
/**
* Immediately deactivate the given user account such that it can no
* longer be used to log in, but the username is still reserved. This
* is to prevent future users from pretending to be a previous user
* of a given localpart. The user is logged out; all access tokens are
* invalidated.
*/
extern int UserDeactivated(User *);
extern HashMap *
UserGetDevices(User *);
/**
* Fetches the devices that belong to the user, in JSON format,
* identical to what's stored in the database. In fact, this JSON is
* still linked to the database, so it should not be freed with
* .Fn JsonFree .
*/
extern HashMap * UserGetDevices(User *);
extern UserAccessToken *
UserAccessTokenGenerate(User *, char *, int);
/**
* Generate a new access token for the given user. This is mainly
* used internally. It takes the device ID and a boolean value
* indicating whether or not the token should expire.
*/
extern UserAccessToken * UserAccessTokenGenerate(User *, char *, int);
extern int
UserAccessTokenSave(Db *, UserAccessToken *);
/**
* Write the specified access token to the database, returning a
* boolean value indicating success.
*/
extern int UserAccessTokenSave(Db *, UserAccessToken *);
extern void
UserAccessTokenFree(UserAccessToken *);
/**
* Free the memory associated with the given access token.
*/
extern void UserAccessTokenFree(UserAccessToken *);
extern int
UserDeleteToken(User *, char *);
/**
* Delete a specific access token by name.
*/
extern int UserDeleteToken(User *, char *);
extern char *
UserGetProfile(User *, char *);
/**
* Get a string property from the user's profile given the specified
* key.
*/
extern char * UserGetProfile(User *, char *);
extern void
UserSetProfile(User *, char *, char *);
/**
* Set a string property on the user's profile. A key/value pair should
* be provided.
*/
extern void UserSetProfile(User *, char *, char *);
extern int
UserDeleteTokens(User *, char *);
/**
* Delete all of the access tokens that belong to the specified user,
* except for the one provided by name, unless NULL is provided for
* the name.
*/
extern int UserDeleteTokens(User *, char *);
/**
* Get the current privileges of the user as a packed bit field. Use
* the flags defined in UserPrivileges to deterine what privileges a
* user has.
*/
extern int UserGetPrivileges(User *);
/**
* Set the privileges of the user.
*/
extern int UserSetPrivileges(User *, int);
/**
* Decode the JSON that represents the user privileges into a packed
* bit field for simple manipulation.
*/
extern int UserDecodePrivileges(JsonValue *);
/**
* Encode the packed bit field that represents user privileges as a
* JSON value.
*/
extern JsonValue *UserEncodePrivileges(int);
/**
* Convert a string privilege into its bit in the bit field. This is
* mainly intended to be used internally. At the time of writing, I
* don't recall exactly why it's in the public API.
*/
extern int UserDecodePrivilege(const char *);
extern UserId *
UserIdParse(char *, char *);
/**
* Parse either a localpart or a fully qualified Matrix ID. If the
* first argument is a localpart, then the second argument is used as
* the server name.
*/
extern UserId * UserIdParse(char *, char *);
extern void
UserIdFree(UserId *);
/**
* Free the memory associated with the parsed Matrix ID.
*/
extern void UserIdFree(UserId *);
#endif /* TELODENDRIA_USER_H */

View file

@ -25,31 +25,81 @@
#ifndef TELODENDRIA_UTIL_H
#define TELODENDRIA_UTIL_H
/***
* @Nm Util
* @Nd Some misc. helper functions that don't need their own headers.
* @Dd February 15 2023
*
* This header holds a number of random functions related to strings,
* time, the filesystem, and other simple tasks that don't require a
* full separate API. For the most part, the functions here are
* entirely standalone, depending only on POSIX functions, however
* there are a few that depend explicitly on a few other APIs. Those
* are noted.
*/
#include <stdio.h>
#include <stddef.h>
#include <sys/types.h>
#include <Stream.h>
extern unsigned long
UtilServerTs(void);
/**
* Get the current timestamp in milliseconds since the Unix epoch. This
* uses
* .Xr gettimeofday 2
* and time_t, and converts it to a single number, which is then
* returned to the caller.
* .Pp
* A note on the 2038 problem: as long as sizeof(long) >= 8, that is,
* as long as the long data type is 64 bits or more, then everything
* should be fine. On most, if not, all, 64-bit systems, long is 64
* bits. I would expect Telodendria to break for 32 bit systems
* eventually, but we should have a ways to go before that happens.
* I didn't want to try to hack together some system to store larger
* numbers than the architecture supports. But we can always
* re-evaluate over the next few years.
*/
extern unsigned long UtilServerTs(void);
extern unsigned long
UtilLastModified(char *);
/**
* Use
* .Xr stat 2
* to get the last modified time of the given file, or zero if there
* was an error getting the last modified time of a file. This is
* primarily useful for caching file data.
*/
extern unsigned long UtilLastModified(char *);
extern int
UtilMkdir(const char *, const mode_t);
/**
* This function behaves just like the system call
* .Xr mkdir 2 ,
* but it creates any intermediate directories as necessary, unlike
* .Xr mkdir 2 .
*/
extern int UtilMkdir(const char *, const mode_t);
extern int
UtilSleepMillis(long);
/**
* Sleep the calling thread for the given number of milliseconds.
* POSIX does not have a very friendly way to sleep, so this wraps
* .Xr nanosleep 2
* to make its usage much, much simpler.
*/
extern int UtilSleepMillis(long);
extern size_t
UtilParseBytes(char *);
/**
* This function works identically to the POSIX
* .Xr getdelim 3 ,
* except that it assumes pointers were allocated with the Memory API
* and it reads from a Stream instead of a file pointer.
*/
extern ssize_t UtilGetDelim(char **, size_t *, int, Stream *);
extern ssize_t
UtilGetDelim(char **, size_t *, int, Stream *);
extern ssize_t
UtilGetLine(char **, size_t *, Stream *);
/**
* This function is just a special case of
* .Fn UtilGetDelim
* that sets the delimiter to the newline character.
*/
extern ssize_t UtilGetLine(char **, size_t *, Stream *);
#endif /* TELODENDRIA_UTIL_H */

View file

@ -32,7 +32,7 @@ if which makewhatis 2>&1 > /dev/null; then
fi
export PATH="$(pwd)/tools/bin:$(pwd)/build/tools:$PATH"
export MANPATH="$(pwd)/man:$MANPATH"
export MANPATH="$(pwd)/man:$(pwd)/build/man:$MANPATH"
if [ "$(uname)" = "OpenBSD" ]; then
# Other platforms use different MALLOC_OPTIONS