From b70c3f0bedd45bb91907b78e935ea56f90d022ee Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Sat, 29 Apr 2023 02:54:49 +0000 Subject: [PATCH] Finish converting all existing documentation. Next up is writing new docs. --- TODO.txt | 3 +- src/HeaderParser.c | 1 - src/Util.c | 50 --------- src/include/Config.h | 108 +++++++++++++++--- src/include/Html.h | 47 +++++++- src/include/Log.h | 91 +++++++++++++--- src/include/Matrix.h | 83 ++++++++++++-- src/include/Memory.h | 213 ++++++++++++++++++++++++++++++------ src/include/Queue.h | 81 +++++++++++--- src/include/Rand.h | 54 ++++++++- src/include/Routes.h | 31 +++++- src/include/Str.h | 68 ++++++++++-- src/include/Uia.h | 111 +++++++++++++++++-- src/include/User.h | 254 ++++++++++++++++++++++++++++++++++--------- src/include/Util.h | 80 +++++++++++--- tools/env.sh | 2 +- 16 files changed, 1035 insertions(+), 242 deletions(-) diff --git a/TODO.txt b/TODO.txt index 84f5f44..d2d51e8 100644 --- a/TODO.txt +++ b/TODO.txt @@ -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 diff --git a/src/HeaderParser.c b/src/HeaderParser.c index 8e08a95..b12171f 100644 --- a/src/HeaderParser.c +++ b/src/HeaderParser.c @@ -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] = '['; diff --git a/src/Util.c b/src/Util.c index 71297ef..3589b69 100644 --- a/src/Util.c +++ b/src/Util.c @@ -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) { diff --git a/src/include/Config.h b/src/include/Config.h index e3833bd..2bb0c8a 100644 --- a/src/include/Config.h +++ b/src/include/Config.h @@ -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 #include #include +/** + * 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 */ diff --git a/src/include/Html.h b/src/include/Html.h index 172dc71..301503f 100644 --- a/src/include/Html.h +++ b/src/include/Html.h @@ -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 #define HtmlBeginJs(stream) StreamPuts(stream, \ @@ -46,10 +77,18 @@ "

" \ ""); -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 */ diff --git a/src/include/Log.h b/src/include/Log.h index 42df5d0..ea10dd1 100644 --- a/src/include/Log.h +++ b/src/include/Log.h @@ -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 diff --git a/src/include/Matrix.h b/src/include/Matrix.h index b85538a..2239906 100644 --- a/src/include/Matrix.h +++ b/src/include/Matrix.h @@ -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 #include #include @@ -32,6 +44,12 @@ #include #include +/** + * 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 diff --git a/src/include/Memory.h b/src/include/Memory.h index 7146f77..40318a9 100644 --- a/src/include/Memory.h +++ b/src/include/Memory.h @@ -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 +/** + * 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 diff --git a/src/include/Queue.h b/src/include/Queue.h index 6f19d3b..d3973e1 100644 --- a/src/include/Queue.h +++ b/src/include/Queue.h @@ -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 +/** + * 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 diff --git a/src/include/Rand.h b/src/include/Rand.h index 8542e91..6733bda 100644 --- a/src/include/Rand.h +++ b/src/include/Rand.h @@ -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 -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 */ diff --git a/src/include/Routes.h b/src/include/Routes.h index 0d379cf..9fdcd7f 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -24,7 +24,19 @@ #ifndef TELODENDRIA_ROUTES_H #define TELODENDRIA_ROUTES_H -#include +/*** + * @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 #include @@ -32,25 +44,34 @@ #include #include +#include + #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); diff --git a/src/include/Str.h b/src/include/Str.h index d6aed71..c0a89f0 100644 --- a/src/include/Str.h +++ b/src/include/Str.h @@ -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 -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 */ diff --git a/src/include/Uia.h b/src/include/Uia.h index 551bd6e..5052b2b 100644 --- a/src/include/Uia.h +++ b/src/include/Uia.h @@ -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 #include #include #include +/** + * 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 diff --git a/src/include/User.h b/src/include/User.h index c5ee471..f00e336 100644 --- a/src/include/User.h +++ b/src/include/User.h @@ -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 #include -#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 */ diff --git a/src/include/Util.h b/src/include/Util.h index d3036db..73b5dfb 100644 --- a/src/include/Util.h +++ b/src/include/Util.h @@ -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 #include #include #include -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 */ diff --git a/tools/env.sh b/tools/env.sh index e16096a..fcba733 100644 --- a/tools/env.sh +++ b/tools/env.sh @@ -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