first kinda working version

This commit is contained in:
Gwendolyn Kostial 2021-12-10 15:11:40 +01:00
parent 5df47b17b2
commit 6d5c6040fe
7 changed files with 5433 additions and 0 deletions

363
bird.h Normal file
View File

@ -0,0 +1,363 @@
#ifndef _BIRD_HEADER
#define _BIRD_HEADER 1
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include "string.h"
#include "logging.h"
#define BIRD_BUF_SIZE 1024
typedef struct bird_code_s {
char code[4];
char* message;
} bird_code_t;
const bird_code_t bird_success_codes[] = {
{"0002", "Reading configuration"},
{"0003", "Reconfigured"},
{"0004", "Reconfiguration in progress"},
{"0005", "Reconfiguration already in progress, queueing"},
{"0006", "Reconfiguration ignored, shutting down"},
{"0007", "Shutdown ordered"},
{"0008", "Already disabled"},
{"0009", "Disabled"},
{"0010", "Already enabled"},
{"0011", "Enabled"},
{"0012", "Restarted"},
{"0013", "Status report"},
{"0014", "Route count"},
{"0015", "Reloading"},
{"0016", "Access restricted"},
};
const bird_code_t bird_error_codes[] = {
{"8000", "Reply too long"},
{"8001", "Route not found"},
{"8002", "Configuration file error"},
{"8003", "No protocols match"},
{"8004", "Stopped due to reconfiguration"},
{"8005", "Protocol is down => cannot dump"},
{"8006", "Reload failed"},
{"8007", "Access denied"},
{"9000", "Command too long"},
{"9001", "Parse error"},
{"9002", "Invalid symbol type"},
};
const char* code_get_message(const bird_code_t *codes, size_t codes_len, const char code[4]) {
for(size_t i = 0; i < codes_len; ++i) {
if (memcmp(codes[i].code, code, 4) == 0) {
return codes[i].message;
}
}
return NULL;
}
typedef int bird_connection;
typedef struct bird_response_s {
bool success;
string message;
} bird_response;
bool bird_initialize(bird_connection conn);
bird_connection bird_connect_inet(const char* host, const char* port);
bird_connection bird_connect_uds(const char* path);
bird_response bird_command(bird_connection conn, string_view* command);
void bird_close(bird_connection conn);
void bird_free_response(bird_response res);
bird_connection bird_connect_inet(const char* host, const char* port) {
struct addrinfo hints;
struct addrinfo *result, *rp;
int conn;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if(0 != getaddrinfo(host, port, &hints, &result)) {
return -1;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
conn = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (conn == -1) {
continue;
}
if (connect(conn, rp->ai_addr, rp->ai_addrlen) != -1) {
break; // success
}
close(conn);
}
freeaddrinfo(result);
if (rp == NULL) { // no successful connection
return -1;
}
if (!bird_initialize(conn)) {
bird_close(conn);
return -1;
}
return conn;
}
bird_connection bird_connect_uds(const char* path) {
int conn;
struct sockaddr_un addr;
if (strlen(path) >= sizeof(addr.sun_path)) {
log_error("bird connection failed: socket path too long (path is \"%s\")\n", path);
return -1;
}
log_trace("bird socket path is \"%s\"\n", path);
conn = socket(AF_UNIX, SOCK_STREAM, 0);
if (conn == -1) {
log_error("bird connection failed: socket(...) returned -1, errno: %d (%s)\n", errno, strerror(errno));
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, path);
if (connect(conn, (struct sockaddr *)&addr, SUN_LEN(&addr)) == -1) {
log_error("bird connection failed: connect(...) returned -1, errno: %d (%s)\n", errno, strerror(errno));
return -1;
}
log_info("bird connection successful\n");
if (!bird_initialize(conn)) {
bird_close(conn);
return -1;
}
return conn;
}
bool bird_initialize(bird_connection conn) {
// read and discard welcome message
char buf[1024];
read(conn, buf, 1024);
// restrict
bird_response res = bird_command(conn, (string_view*)string_from("restrict"));
if (!res.success) {
string_null_terminate(res.message);
log_error("bird restrict failed: %s", res.message);
}
return res.success;
}
bird_response bird_command_v1(bird_connection conn, string_view* command) {
log_info("bird command: %.*s\n", command->len, command->buf);
bird_response response;
const char *ptr = command->buf;
size_t commandlen = command->len;
while(commandlen > 0) {
int writelen = write(conn, ptr, commandlen);
if (writelen < 1) {
response.success = false;
response.message = string_from("Bird connection problem: <TODO>");
return response;
}
ptr += writelen;
commandlen -= writelen;
}
write(conn, "\n", 1);
char buf[BIRD_BUF_SIZE];
string_view buf_view;
buf_view.buf = &(buf[0]);
buf_view.len = 0;
response.message = string_init(BIRD_BUF_SIZE);
string last_line = string_init(0);
string data = string_init(0);
bool completed = false;
while(!completed) {
log_trace("before read\n");
buf_view.len = read(conn, buf, BIRD_BUF_SIZE - 1);
buf[BIRD_BUF_SIZE - 1] = '\0';
log_trace("read buf: \n=======\n%s=======\n", buf);
log_trace("read %d bytes\n", buf_view.len);
string_copy(data, (string_view*)last_line);
log_trace("copied last_line to data\n");
string_concat(data, &buf_view);
log_trace("concated buf_view onto data\n");
string_view *lines;
int num_lines = string_split((string_view*) data, '\n', &lines);
log_trace("split data into %d lines\n", num_lines);
int actual_num_lines = num_lines;
if (buf_view.len == BIRD_BUF_SIZE) {
string_copy(last_line, &(lines[num_lines - 1]));
actual_num_lines = num_lines - 1;
}
for(int i = 0; i < actual_num_lines; ++i) {
string_view line = lines[i];
if (line.len == 0 || string_is_whitespace(&line)) {
continue;
}
if (line.len >= 4) {
if (memcmp(line.buf, "0000", 4) == 0) {
response.success = true;
completed = true;
log_trace("received 0000, response complete\n");
break;
}
const char* code_msg;
code_msg = code_get_message(bird_success_codes, sizeof(bird_success_codes) / sizeof(bird_code_t), line.buf);
if (code_msg != NULL) {
response.success = true;
string_copy_chars(response.message, code_msg);
completed = true;
log_trace("received success code, response complete (%s)\n", code_msg);
break;
}
code_msg = code_get_message(bird_error_codes, sizeof(bird_error_codes) / sizeof(bird_code_t), line.buf);
if (code_msg != NULL) {
response.success = false;
string_copy_chars(response.message, code_msg);
completed = true;
log_trace("received error code, response complete (%s)\n", code_msg);
break;
}
}
string_view line_part;
if (line.buf[0] == '1' || line.buf[0] == '2') {
line_part = string_get_view(&line, 5, -1);
string_concat(response.message, &line_part);
string_append(response.message, "\n");
} else if (line.buf[0] == ' ') {
line_part = string_get_view(&line, 1, -1);
string_concat(response.message, &line_part);
string_append(response.message, "\n");
} else if (line.buf[0] == '+') {
line_part = string_get_view(&line, 1, -1);
string_concat(response.message, &line_part);
} else {
string_append(response.message, "<<<unparsable_string(");
string_concat(response.message, &line);
string_append(response.message, ")>>>\n");
}
}
free(lines);
}
string_free(last_line);
string_free(data);
string_null_terminate(response.message);
log_trace("bird response: %s\n", response.message->buf);
return response;
}
bird_response bird_command(bird_connection conn, string_view* command) {
log_info("bird command: %.*s\n", command->len, command->buf);
bird_response response;
const char *ptr = command->buf;
size_t commandlen = command->len;
while(commandlen > 0) {
int writelen = write(conn, ptr, commandlen);
if (writelen < 1) {
response.success = false;
response.message = string_from("Bird connection problem: <TODO>");
return response;
}
ptr += writelen;
commandlen -= writelen;
}
write(conn, "\n", 1);
char buf[BIRD_BUF_SIZE];
string_view buf_view;
buf_view.buf = &(buf[0]);
buf_view.len = 0;
response.message = string_init(BIRD_BUF_SIZE);
string last_line = string_init(0);
string data = string_init(0);
bool completed = false;
while(!completed) {
log_trace("before read\n");
buf_view.len = read(conn, buf, BIRD_BUF_SIZE - 1);
buf[BIRD_BUF_SIZE - 1] = '\0';
log_trace("read buf: \n=======\n%s=======\n", buf);
log_trace("read %d bytes\n", buf_view.len);
string_copy(data, (string_view*)last_line);
log_trace("copied last_line to data\n");
string_concat(data, &buf_view);
log_trace("concated buf_view onto data\n");
string_view *lines;
int num_lines = string_split((string_view*) data, '\n', &lines);
log_trace("split data into %d lines\n", num_lines);
int actual_num_lines = num_lines;
if (buf_view.len == BIRD_BUF_SIZE) {
string_copy(last_line, &(lines[num_lines - 1]));
actual_num_lines = num_lines - 1;
}
for(int i = 0; i < actual_num_lines; ++i) {
string_view line = lines[i];
if (line.len == 0) {
continue;
}
string_view line_content;
bool append_newline = true;
if (line.buf[0] == '0' && line.buf[1] == '0' && line.buf[2] == '0' && line.buf[3] == '0') {
completed = true;
response.success = true;
line_content = string_get_view(&line, 0, 0);
} else if (line.buf[0] == '0') {
completed = true;
response.success = true;
line_content = string_get_view(&line, 5, -1);
} else if (line.buf[0] == '8' || line.buf[0] == '9') {
completed = true;
response.success = false;
line_content = string_get_view(&line, 5, -1);
} else if (line.buf[0] == '1' || line.buf[0] == '2') {
line_content = string_get_view(&line, 5, -1);
} else if (line.buf[0] == ' ') {
line_content = string_get_view(&line, 1, -1);
} else if (line.buf[0] == '+') {
line_content = string_get_view(&line, 5, -1);
append_newline = false;
} else {
line_content = string_get_view(&line, 0, 0);
string_append(response.message, "<<<unparsable_string(");
string_concat(response.message, &line);
string_append(response.message, ")>>>\n");
}
string_concat(response.message, &line_content);
if (append_newline) {
string_append_char(response.message, '\n');
}
}
free(lines);
}
string_free(last_line);
string_free(data);
string_null_terminate(response.message);
log_trace("bird response: %s\n", response.message->buf);
return response;
}
void bird_close(bird_connection conn) {
close(conn);
}
void bird_free_response(bird_response res) {
string_free(res.message);
}
#endif

2673
favicon.h Normal file

File diff suppressed because it is too large Load Diff

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

1937
httpserver.h Normal file

File diff suppressed because it is too large Load Diff

40
logging.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef _LOGGING_HEADER
#define _LOGGING_HEADER 1
#include <stdarg.h>
// log levels:
// 0: no logging
// 1: errors
// 2: warnings
// 3: info
// 4: trace
#define log_error(...) log_message(1, __VA_ARGS__)
#define log_warning(...) log_message(2, __VA_ARGS__)
#define log_info(...) log_message(3, __VA_ARGS__)
#ifdef LOG_ENABLE_TRACE
#define log_trace(...) log_message(4, __VA_ARGS__)
#endif
#ifndef LOG_ENABLE_TRACE
#define log_trace(...) do {} while(0)
#endif
int log_level = 1;
void log_message(int level, const char * format, ...) {
va_list args;
va_start(args, format);
if(level <= log_level) {
// TODO: format log messages with timestamp, log level and newline at the end
vfprintf(stderr, format, args);
}
va_end(args);
}
#endif

199
main.c Normal file
View File

@ -0,0 +1,199 @@
#define HTTPSERVER_IMPL
#define LOG_ENABLE_TRACE 1
#include <stdbool.h>
#include "httpserver.h"
#include <stdlib.h>
#include "favicon.h"
#include "string.h"
#include "bird.h"
#include "logging.h"
#define TRACEROUTE "traceroute -%s -A -q1 -N32 -w1 -m15 %s"
#define ERROR_NO_BIRD_SOCKET_CONFIGURED "no bird socket configured"
#define ERROR_BIRD_CONNECTION_FAILED "connection to bird daemon failed"
const char * config_ipv4_source;
const char * config_ipv6_source;
const char * bird_socket_path;
const char * bird6_socket_path;
const char * security_shared_secret;
string_view * security_access_list;
size_t security_access_list_length;
int request_target_is(http_request_t* request, char const * target) {
http_string_t url = http_request_path(request);
int len = strlen(target);
return len == url.len && memcmp(url.buf, target, url.len) == 0;
}
int request_is_get(http_request_t* request) {
http_string_t method = http_request_method(request);
return method.len == 3 && memcmp(method.buf, "GET", method.len) == 0;
}
void handle_traceroute(http_request_t* request, http_response_t* response, bool ipv6, string_view* query) {
http_response_header(response, "Content-Type", "text/plain");
// TODO: use strings
FILE *fp;
char buf[256];
char* command = malloc(sizeof(TRACEROUTE) + query->len);
assert(command != NULL /* command allocation was successful */);
char* destination = malloc(query->len + 1);
assert(command != NULL /* destination allocation was successful */);
memcpy(destination, query->buf, query->len);
destination[query->len] = '\0';
sprintf(command, TRACEROUTE, ipv6 ? "6" : "4", destination);
/* Open the command for reading. */
fp = popen(command, "r");
if (fp == NULL) {
http_response_status(response, 500);
http_response_body(response, "traceroute failed", sizeof("traceroute failed") - 1);
http_respond(request, response);
} else {
string str = string_init(0);
/* Read the output a line at a time - output it. */
while (fgets(buf, sizeof(buf), fp) != NULL) {
string_append(str, buf);
}
/* close */
pclose(fp);
http_response_status(response, 200);
http_response_body(response, str->buf, str->len);
http_respond(request, response);
string_free(str);
}
}
void handle_bird(http_request_t* request, http_response_t* response, bool ipv6, string_view* query) {
http_response_header(response, "Content-Type", "text/plain");
const char * socket_path = ipv6 ? bird6_socket_path : bird_socket_path;
if (socket_path == NULL) {
http_response_status(response, 500);
http_response_body(response, ERROR_NO_BIRD_SOCKET_CONFIGURED, sizeof(ERROR_NO_BIRD_SOCKET_CONFIGURED) - 1);
http_respond(request, response);
} else {
bird_connection bird = bird_connect_uds(socket_path);
if (bird == -1) {
http_response_status(response, 500);
http_response_body(response, ERROR_BIRD_CONNECTION_FAILED, sizeof(ERROR_BIRD_CONNECTION_FAILED) - 1);
http_respond(request, response);
} else {
bird_response bird_res = bird_command(bird, query);
// todo: use status? depends whether the lg main application expects an error status code when command failed
http_response_status(response, 200);
http_response_body(response, bird_res.message->buf, bird_res.message->len);
http_respond(request, response);
bird_free_response(bird_res);
}
}
}
void handle_favicon(http_request_t* request, http_response_t* response) {
http_response_status(response, 200);
http_response_header(response, "Content-Type", "image/x-icon");
http_response_body(response, favicon_ico, favicon_ico_len);
http_respond(request, response);
}
void handle_request(http_request_t* request) {
http_response_t* response = http_response_init();
if (!request_is_get(request)) {
http_response_status(response, 405);
http_respond(request, response);
} else {
if (request_target_is(request, "/favicon.ico")) {
handle_favicon(request, response);
} else {
http_string_t query = http_request_query(request, "q");
string query_decoded = string_urldecode(&HTTP_STR_TO_VIEW(query));
if (query_decoded == NULL) {
http_response_status(response, 400);
http_respond(request, response);
} else {
if (request_target_is(request, "/traceroute")) {
handle_traceroute(request, response, false, (string_view*)query_decoded);
} else if (request_target_is(request, "/traceroute6")) {
handle_traceroute(request, response, true, (string_view*)query_decoded);
} else if (request_target_is(request, "/bird")) {
handle_bird(request, response, false, (string_view*)query_decoded);
} else if (request_target_is(request, "/bird6")) {
handle_bird(request, response, true, (string_view*)query_decoded);
} else {
http_response_status(response, 404);
http_respond(request, response);
}
string_free(query_decoded);
}
}
}
}
int main() {
const char * log_level_str = getenv("LOG_LEVEL");
if (log_level_str != NULL) {
log_level = atoi(log_level_str);
}
const char * bind_ip = getenv("BIND_IP");
const char * bind_port = getenv("BIND_PORT");
bird_socket_path = getenv("BIRD_SOCKET");
bird6_socket_path = getenv("BIRD6_SOCKET");
config_ipv4_source = getenv("IPV4_SOURCE");
config_ipv6_source = getenv("IPV6_SOURCE");
char * access_list = getenv("ACCESS_LIST");
security_shared_secret = getenv("SHARED_SECRET");
if (access_list != NULL) {
string_view access_list_str = (string_view) {access_list, strlen(access_list)};
security_access_list_length = string_split(&access_list_str, ',', &security_access_list); // don't care about freeing this since it's needed until the end of the program
}
if (bind_ip == NULL) {
bind_ip = "0.0.0.0";
}
if (bind_port == NULL) {
bind_port = "5000";
}
if (bird6_socket_path == NULL && bird_socket_path != NULL) {
bird6_socket_path = bird_socket_path;
} else if (bird_socket_path == NULL && bird6_socket_path != NULL) {
bird_socket_path = bird6_socket_path;
}
int port = atoi(bind_port);
// TODO: implement binding to an IP
http_server_t* server = http_server_init(port, handle_request);
http_server_listen(server);
}

221
string.h Normal file
View File

@ -0,0 +1,221 @@
#ifndef _STRING_HEADER
#define _STRING_HEADER 1
#include <assert.h>
#include <ctype.h>
#include "logging.h"
#define HTTP_STR_TO_VIEW(http_str) ((string_view){(char*)http_str.buf, (size_t)http_str.len})
typedef struct string_s {
char* buf;
size_t len;
size_t size;
} *string;
typedef struct string_view_s {
char* buf;
size_t len;
} string_view;
const size_t po2s[] = { 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 };
size_t find_next_size(size_t old_size) {
if (old_size >= 16384) {
return (old_size / 16384 + 1) * 16384;
} else {
for(size_t i = 0; i < sizeof(po2s)/sizeof(size_t); ++i) {
if (old_size < po2s[i]) {
return po2s[i];
}
}
assert(false /*can not reach here */);
}
}
string string_init(size_t size) {
size_t actual_size = find_next_size(size);
string str = malloc(sizeof (struct string_s));
assert(str != NULL /* str allocation was successful */);
str->len = 0;
str->size = actual_size;
str->buf = malloc(actual_size);
assert(str->buf != NULL /* str->buf allocation was successful */);
return str;
}
string string_from(const char * content) {
size_t length = strlen(content);
size_t size = find_next_size(length);
string str = string_init(size);
memcpy(str->buf, content, length);
str->len = length;
return str;
}
void string_free(string str) {
free(str->buf);
free(str);
}
void string_grow(string str, size_t amount) {
size_t new_size = find_next_size(str->size + amount);
str->buf = realloc(str->buf, new_size);
assert(str->buf != NULL /* str->buf reallocation was successful */);
str->size = new_size;
}
void string_append_with_len(string str, const char * content, size_t len) {
if (str->len + len + 1 > str->size) {
string_grow(str, len + 1);
}
assert(str->len + len <= str->size /* string is large enough to fit appended content */);
memcpy(str->buf + str->len, content, len);
str->len += len;
}
void string_append(string str, const char * content) {
size_t len = strlen(content);
string_append_with_len(str, content, len);
}
void string_append_char(string str, char ch) {
string_append_with_len(str, &ch, 1);
}
void string_concat(string str, const string_view *other) {
string_append_with_len(str, other->buf, other->len);
}
void string_copy(string str, const string_view *other) {
str->len = 0;
string_concat(str, other);
}
void string_copy_chars(string str, const char *other) {
str->len = 0;
string_append(str, other);
}
void string_null_terminate(string str) {
if (str->len + 1 > str->size) {
string_grow(str, 1);
}
assert(str->len + 1 <= str->size /* string is large enough to fit null terminator at the end */);
str->buf[str->len] = '\0';
}
string_view string_get_view(string_view *str, size_t offset, int length) {
string_view view;
if (length == -1) {
length = str->len - offset;
}
if (offset < str->len && offset + length - 1 < str->len) {
view.buf = str->buf + offset;
view.len = length;
} else {
view.buf = str->buf + str->len - 1;
view.len = 0;
}
return view;
}
size_t string_split(string_view *str, char delimiter, string_view **out) {
const char* strPtr = str->buf;
size_t remainingLength = str->len;
const char* newlinePtr;
string_view *lines = malloc(sizeof(string_view));
assert(lines != NULL /* lines allocation was successful */);
int nextLineIndex = 0;
size_t offset, length;
while(NULL != (newlinePtr = memchr(strPtr, delimiter, remainingLength))) {
offset = strPtr - str->buf;
length = newlinePtr - strPtr;
lines[nextLineIndex] = string_get_view(str, offset, length);
nextLineIndex += 1;
strPtr = newlinePtr + 1;
remainingLength -= length + 1;
lines = realloc(lines, sizeof(string_view) * (nextLineIndex + 1));
assert(lines != NULL /* lines allocation was successful */);
}
offset = strPtr - str->buf;
lines[nextLineIndex] = string_get_view(str, offset, -1);
*out = lines;
return nextLineIndex + 1;
}
bool string_is_whitespace(string_view *str) {
for (size_t i = 0; i < str->len; ++i) {
char ch = str->buf[i];
if (!isspace(ch)) return false;
}
return true;
}
int hex_char_to_num(char ch) {
if (ch >= '0' && ch <= '9') {
return ch - '0';
}
if (ch >= 'a' && ch <= 'f') {
return ch - 'a' + 10;
}
if (ch >= 'A' && ch <= 'F') {
return ch - 'A' + 10;
}
return -1;
}
int decode_url_hex(const char * input) {
char ch1 = input[0];
char ch2 = input[1];
int n1 = hex_char_to_num(ch1);
int n2 = hex_char_to_num(ch2);
if (n1 == -1 || n2 == -1) {
return -1;
} else {
return n1 * 16 + n2;
}
}
string string_urldecode(string_view *str) {
string result = string_init(str->len);
int c;
for(size_t i = 0; i < str->len; ++i) {
c = str->buf[i];
if (c == '+') {
c = ' ';
} else if (c == '%') {
if (i + 2 < str->len) {
c = decode_url_hex(&(str->buf[i + 1]));
i += 2;
if (c == -1) {
goto error;
}
} else {
goto error;
}
}
string_append_char(result, (char)c);
}
return result;
error:
string_free(result);
return NULL;
}
#endif