aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2019-06-08 11:25:19 +0200
committercbdev <cb@cbcdn.com>2019-06-08 11:25:19 +0200
commit3bb2d8fd96618c11f499c8dddb97a62015b1ea53 (patch)
treecab1ebf146c717ac9de071efad615ced22725623
parentc9062e7d1a1d917f618b71ac3810b4f3e396ab0d (diff)
downloadwebsocksy-3bb2d8fd96618c11f499c8dddb97a62015b1ea53.tar.gz
websocksy-3bb2d8fd96618c11f499c8dddb97a62015b1ea53.tar.bz2
websocksy-3bb2d8fd96618c11f499c8dddb97a62015b1ea53.zip
Implement configuration file parsing
-rw-r--r--README.md19
-rw-r--r--config.c110
-rw-r--r--plugins/backend_file.md33
-rw-r--r--websocksy.c9
-rw-r--r--websocksy.cfg11
5 files changed, 160 insertions, 22 deletions
diff --git a/README.md b/README.md
index 10a2f73..11cbfc1 100644
--- a/README.md
+++ b/README.md
@@ -74,14 +74,27 @@ The listen port may either be exposed to incoming WebSocket clients directly or
### Command line arguments
-* `-p <port>`: Set the listen port for incoming WebSocket connections
-* `-l <host>`: Set the host for listening for incoming WebSocket connections
+* `-p <port>`: Set the listen port for incoming WebSocket connections (Default: `8001`)
+* `-l <host>`: Set the host for listening for incoming WebSocket connections (Default: `::`)
* `-b <backend>`: Select external backend
* `-c <option>=<value>`: Pass configuration option to backend
### Configuration file
-TBD
+If only one option is passed to `websocksy`, it is interpreted as the path to a configuration file to be read.
+A configuration file should consist of one `[core]` section, configuring central options and an optional `[backend]`
+section, configuring the current backend. Options are set as lines of `<option> = <value>` pairs.
+
+In the `[core]` section, the following options are recognized:
+
+* `port`: Listen port for incoming WebSocket connections
+* `listen`: Host for incoming WebSocket connections
+* `backend`: External backend selection
+
+In the `[backend]` section, all options are passed directly to the backend and thus are dependent on the specific
+implementation. Backends should provide their own documentation files.
+
+An [example configuration file](websocksy.cfg) using the `file` backend is available in the repository.
## Default backend
diff --git a/config.c b/config.c
index 7da08eb..0b57714 100644
--- a/config.c
+++ b/config.c
@@ -1,5 +1,6 @@
#include <string.h>
#include <stdio.h>
+#include <ctype.h>
#include "websocksy.h"
#include "config.h"
@@ -10,8 +11,98 @@ static enum /*_config_file_section*/ {
cfg_backend
} config_section = cfg_main;
+static int config_file_line(ws_config* config, char* key, char* value, size_t line_no){
+ if(!strcmp(key, "port")){
+ free(config->port);
+ config->port = strdup(value);
+ }
+ else if(!strcmp(key, "listen")){
+ free(config->host);
+ config->host = strdup(value);
+ }
+ else if(!strcmp(key, "backend")){
+ //clean up the previously registered backend
+ if(config->backend.cleanup){
+ config->backend.cleanup();
+ }
+ //load the backend plugin
+ if(plugin_backend_load(PLUGINS, value, &(config->backend))){
+ return 1;
+ }
+ if(config->backend.init() != WEBSOCKSY_API_VERSION){
+ fprintf(stderr, "Loaded backend %s was built for a different API version\n", value);
+ return 1;
+ }
+ }
+ return 0;
+}
+
int config_parse_file(ws_config* config, char* filename){
- return 1;
+ ssize_t line_current;
+ size_t line_alloc = 0, line_no = 1, key_len;
+ char* line = NULL, *key, *value;
+ FILE* input = fopen(filename, "r");
+ if(!input){
+ fprintf(stderr, "Failed to open %s as configuration file\n", filename);
+ return 1;
+ }
+
+ for(line_current = getline(&line, &line_alloc, input); line_current >= 0; line_current = getline(&line, &line_alloc, input)){
+ if(!strncmp(line, "[core]", 6)){
+ config_section = cfg_main;
+ }
+ else if(!strncmp(line, "[backend]", 9)){
+ config_section = cfg_backend;
+ }
+ else if(line_current > 0){
+ //right-trim newlines & spaces from value
+ for(line_current--; line_current && isspace(line[line_current]); line_current--){
+ line[line_current] = 0;
+ }
+
+ if(line_current > 0){
+ //left-trim key
+ for(key = line; *key && isspace(key[0]); key++){
+ }
+
+ value = strchr(key, '=');
+ if(value){
+ *value = 0;
+ //left-trim value
+ for(value++; *value && isspace(value[0]); value++){
+ }
+
+ //right-trim key
+ for(key_len = strlen(key) - 1; *key && key_len && isspace(key[key_len]); key_len--){
+ key[key_len] = 0;
+ }
+
+ switch(config_section){
+ case cfg_main:
+ if(config_file_line(config, key, value, line_no)){
+ free(line);
+ fclose(input);
+ return 1;
+ }
+ break;
+ case cfg_backend:
+ if(config->backend.config(key, value)){
+ fprintf(stderr, "Backend configuration failed in %s line %lu\n", filename, line_no);
+ free(line);
+ fclose(input);
+ return 1;
+ }
+ break;
+ }
+ }
+ }
+ }
+ line_no++;
+ }
+
+ free(line);
+ fclose(input);
+ return 0;
}
int config_parse_arguments(ws_config* config, int argc, char** argv){
@@ -33,24 +124,13 @@ int config_parse_arguments(ws_config* config, int argc, char** argv){
}
switch(argv[u][1]){
case 'p':
- config->port = argv[u + 1];
+ config_file_line(config, "port", argv[u + 1], 0);
break;
case 'l':
- config->host = argv[u + 1];
+ config_file_line(config, "listen", argv[u + 1], 0);
break;
case 'b':
- //clean up the previously registered backend
- if(config->backend.cleanup){
- config->backend.cleanup();
- }
- //load the backend plugin
- if(plugin_backend_load(PLUGINS, argv[u + 1], &(config->backend))){
- return 1;
- }
- if(config->backend.init() != WEBSOCKSY_API_VERSION){
- fprintf(stderr, "Loaded backend %s was built for a different API version\n", argv[u + 1]);
- return 1;
- }
+ config_file_line(config, "backend", argv[u + 1], 0);
break;
case 'c':
if(!strchr(argv[u + 1], '=')){
diff --git a/plugins/backend_file.md b/plugins/backend_file.md
new file mode 100644
index 0000000..7359c94
--- /dev/null
+++ b/plugins/backend_file.md
@@ -0,0 +1,33 @@
+# The `file` backend
+
+The `file` peer discovery backend traverses one or several configurable files to find a peer for an incoming
+WebSocket connection.
+
+## Operation
+
+For any incoming WebSocket connection, the backend constructs a series of file names to read from a set of
+user-configurable templates which may use the following variables:
+
+* `%endpoint%`: The complete endpoint path (minus trailing and leading slashes)
+* `%cookie:<name>%`: If present, parse the HTTP `Cookie` header to find a cookie by name and replace the variable with the cookie's value
+* `%header:<tag>%`: Find a header by its tag and replace the variable by it's value if present
+
+All slashes in variable values are replaced by underscores to defend against directory traversal attacks.
+
+If a specified file or variable does not exist (ie. the header or cookie is missing), the expression is skipped and the next one is evaluated. When the end of the template expression list is reached without a valid peer being found, the WebSocket connection will be rejected.
+
+The files accessed via the expression list must contain lines of the form
+
+`<network peer> <protocol> [<framing>] [<framing-config>]`
+
+If the WebSocket client indicates one or more supported subprotocol, the first line found in any of the traversed
+files matching any of the indicated protocols is used as the connection peer. The special value `*` for the
+protocol always matches the first indicated protocol. If the client does not indicate a subprotocol, the first
+configuration line read will be used.
+
+## Backend configuration
+
+This backend accepts the following configuration options:
+
+* `path`: The base path for the template expression list. All template evaluations will be appended to this path.
+* `expression`: Adds a template expression to the list. Specify multiple times to extend the list. Expressions are evaluated in the order they are specified.
diff --git a/websocksy.c b/websocksy.c
index f948727..33ee794 100644
--- a/websocksy.c
+++ b/websocksy.c
@@ -22,7 +22,6 @@
/* TODO
* - TLS
- * - config file
* - pings
*/
@@ -44,8 +43,8 @@ char* xstr_lower(char* in){
/* Daemon configuration */
static ws_config config = {
- .host = DEFAULT_HOST,
- .port = DEFAULT_PORT,
+ .host = NULL,
+ .port = NULL,
/* Assign the built-in defaultpeer backend by default */
.backend.init = backend_defaultpeer_init,
.backend.config = backend_defaultpeer_configure,
@@ -80,6 +79,7 @@ int client_register(websocket* ws){
return 0;
}
+/* Clean up and close all connections */
void client_cleanup(){
size_t n;
for(n = 0; n < socks; n++){
@@ -201,6 +201,7 @@ static void signal_handler(int signum){
static int usage(char* fn){
fprintf(stderr, "\nwebsocksy v%s - Proxy between websockets and 'real' sockets\n", WEBSOCKSY_VERSION);
fprintf(stderr, "Usage:\n");
+ fprintf(stderr, "\t%s <configuration file>\n", fn);
fprintf(stderr, "\t%s [-p <port>] [-l <listen address>] [-b <discovery backend>] [-c <option>=<value>]\n", fn);
fprintf(stderr, "Arguments:\n");
fprintf(stderr, "\t-p <port>\t\tWebSocket listen port (Current: %s, Default: %s)\n", config.port, DEFAULT_PORT);
@@ -297,7 +298,7 @@ int main(int argc, char** argv){
}
//open listening socket
- listen_fd = network_socket(config.host, config.port, SOCK_STREAM, 1);
+ listen_fd = network_socket(config.host ? config.host : DEFAULT_HOST, config.port ? config.port : DEFAULT_PORT, SOCK_STREAM, 1);
if(listen_fd < 0){
exit(usage(argv[0]));
}
diff --git a/websocksy.cfg b/websocksy.cfg
new file mode 100644
index 0000000..e986405
--- /dev/null
+++ b/websocksy.cfg
@@ -0,0 +1,11 @@
+[core]
+port = 8000
+backend = file
+
+[backend]
+path = files/
+expression = %endpoint%.txt
+expression = %cookie:session%.txt
+expression = %header:X-Foo-Bar%.txt
+expression = default.txt
+