Overhaul configuration globals, refactor main.c/.h, avoid acting like a full webserver
This commit is contained in:
@@ -1,35 +1,38 @@
|
||||
## STETHOSCOPE EXAMPLE CONFIG
|
||||
|
||||
# The name of this Stethoscope instance. This name is used for the page title. (default: "My Stethoscope")
|
||||
service_name=My Stethoscope
|
||||
|
||||
# Port to listen on. This should be reverse proxied by something like httpd or nginx. (default: 7800)
|
||||
listen=7800
|
||||
|
||||
# Path of the webroot server uses to load asset paths like the stylesheet. (default: )
|
||||
webroot=
|
||||
################################
|
||||
## STETHOSCOPE EXAMPLE CONFIG ##
|
||||
################################
|
||||
|
||||
# Name of this Stethoscope instance. This name is used for the page title. (default: "My Stethoscope")
|
||||
name=My Stethoscope
|
||||
# Text to show
|
||||
description=
|
||||
# Maximum time in seconds status history is shown on the status page. (default: 86400)
|
||||
status_retention=86400
|
||||
# Maximum time in seconds without change before fetching a new status peroid. (default: 60)
|
||||
status_refresh=60
|
||||
# Time in seconds to wait before attempting to reconnect to the endpoint. (default: 5)
|
||||
backoff=5
|
||||
|
||||
# These are example services.
|
||||
;[service.openssh]
|
||||
;name=OpenSSH Daemon
|
||||
;address=tcp://localhost:22
|
||||
[web]
|
||||
# Port to listen on. This should be reverse proxied by something like httpd or nginx. (default: 7800)
|
||||
listen=7800
|
||||
# Web path to stylesheet. If this is empty, the daemon will provide a barebones one.
|
||||
stylesheet=
|
||||
# Web path to image banner. If this is empty, the page will generate without a banner.
|
||||
banner=
|
||||
# Time in seconds between status page recaching. (default: 15)
|
||||
recache_delay=15
|
||||
|
||||
;[service.nginx]
|
||||
;name=Nginx
|
||||
;address=tcp://localhost:80,tcp://localhost:443,process://nginx,pidfile:///run/nginx.pid
|
||||
;backoff=1
|
||||
|
||||
;[service.email]
|
||||
;name=Email
|
||||
;address=tcp://localhost:25,tcp://localhost:143
|
||||
|
||||
;[service.tor]
|
||||
;name=Tor
|
||||
;address=tcp://127.0.0.1:9050
|
||||
|
||||
;[service.mumble]
|
||||
;name=mumble
|
||||
;address=tcp://localhost:64738,udp://localhost:64738
|
||||
## SERVICE EXAMPLE
|
||||
# Services are always defined in their own section with their name prepended by "service."
|
||||
;[service.example]
|
||||
# Display name for the service. This is publicly displayed on the status page.
|
||||
;name=A Service
|
||||
# Web path of the icon to use for this service. If empty, an icon will not be used.
|
||||
;icon=
|
||||
# Text to display below the service name.
|
||||
;text=
|
||||
# Comma-seperated list of endpoints to check the status of for this service
|
||||
# Endpoints must be in SCHEME://ADDRESS:PORT format or SCHEME://PATH format
|
||||
# Supported schemes include: tcp, udp, icmp, pid, proc
|
||||
;endpoints=
|
||||
355
src/main.c
355
src/main.c
@@ -9,9 +9,25 @@
|
||||
|
||||
#define CONFIG_SERVICE_PREFIX "service."
|
||||
|
||||
// Global
|
||||
char* daemon_name = "My Stethoscope";
|
||||
char* daemon_description = "";
|
||||
uint32_t status_retention = 84600;
|
||||
uint32_t status_refresh = 60;
|
||||
uint32_t global_backoff = 15;
|
||||
|
||||
// Web
|
||||
uint16_t web_port = 7800;
|
||||
char* web_stylesheet = "";
|
||||
char* web_banner = "";
|
||||
uint32_t web_recache = 30;
|
||||
|
||||
uint8_t daemon_enabled = 1;
|
||||
mxml_node_t* cached_index;
|
||||
|
||||
static void signal_handler(int signo) {
|
||||
if (signo == SIGINT || signo == SIGKILL) {
|
||||
running = 0;
|
||||
daemon_enabled = 0;
|
||||
} else if (signo == SIGUSR1) {
|
||||
printf("USR1 signal detected! Not doing anything for now.\n");
|
||||
}
|
||||
@@ -19,74 +35,74 @@ static void signal_handler(int signo) {
|
||||
|
||||
static int config_handler(void* user, const char* section, const char* name, const char* value) {
|
||||
if (strcmp(section, "") == 0) {
|
||||
if (strcmp(name, "service_name") == 0) {
|
||||
if (strcmp(name, "name") == 0) {
|
||||
daemon_name = malloc(strlen(value)+1);
|
||||
strcpy(daemon_name, value);
|
||||
} else if (strcmp(name, "webroot") == 0) {
|
||||
webroot = malloc(strlen(value)+1);
|
||||
strcpy(webroot, value);
|
||||
} else if (strcmp(name, "banner") == 0) {
|
||||
daemon_banner = malloc(strlen(value)+1);
|
||||
strcpy(daemon_banner, value);
|
||||
} else if (strcmp(name, "listen") == 0) {
|
||||
listen_port = atoi(value);
|
||||
} else if (strcmp(name, "description") == 0) {
|
||||
daemon_description = malloc(strlen(value)+1);
|
||||
strcpy(daemon_description, value);
|
||||
} else if (strcmp(name, "status_retention") == 0) {
|
||||
status_retention = atoi(value);
|
||||
} else if (strcmp(name, "status_refresh") == 0) {
|
||||
int result = atoi(value);
|
||||
status_refresh = result > 1 ? result : 1;
|
||||
} else if (strcmp(name, "backoff") == 0) {
|
||||
int result = atoi(value);
|
||||
global_backoff = result > 1 ? result : 1;
|
||||
} else if (strcmp(name, "web_refresh") == 0) {
|
||||
int result = atoi(value);
|
||||
web_refresh = result > 1 ? result : 1;
|
||||
} else if (strcmp(name, "status_refresh") == 0) {
|
||||
status_refresh = atoi(value);
|
||||
} else if (strcmp(name, "retention") == 0) {
|
||||
retention = atoi(value);
|
||||
} else {
|
||||
printf("Unknown configuration entry \'%s\'\n", name);
|
||||
printf("Unknown configuration entry \'%s\' in global\n", name);
|
||||
}
|
||||
} else if (strcmp(section, "web") == 0) {
|
||||
if (strcmp(name, "listen") == 0) {
|
||||
web_port = atoi(value);
|
||||
} else if (strcmp(name, "stylesheet") == 0) {
|
||||
web_stylesheet = malloc(strlen(value)+1);
|
||||
strcpy(web_stylesheet, value);
|
||||
} else if (strcmp(name, "banner") == 0) {
|
||||
web_banner = malloc(strlen(value)+1);
|
||||
strcpy(web_banner, value);
|
||||
} else if (strcmp(name, "recache_delay") == 0) {
|
||||
web_recache = atoi(value);
|
||||
} else {
|
||||
printf("Unknown configuration entry \'%s\' in \'%s\'\n", name, section);
|
||||
}
|
||||
} else if (strncmp(CONFIG_SERVICE_PREFIX, section, strlen(CONFIG_SERVICE_PREFIX)) == 0) {
|
||||
const char* id = section+strlen(CONFIG_SERVICE_PREFIX);
|
||||
Vector* service_vector = (Vector*)user;
|
||||
struct Service* current_service = NULL;
|
||||
|
||||
Iterator it = vector_begin(service_vector);
|
||||
Iterator end = vector_end(service_vector);
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct Service* current = *(struct Service**)iterator_get(&it);
|
||||
if (strcmp(id, current->id) == 0) {
|
||||
current_service = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (current_service == NULL) {
|
||||
current_service = malloc(4096);
|
||||
current_service->id = malloc(strlen(id)+1);
|
||||
strcpy(current_service->id, id);
|
||||
current_service->backoff = 0;
|
||||
vector_push_back(service_vector, ¤t_service);
|
||||
struct Service* service = get_service_of_id_or_null(id);
|
||||
if (service == NULL) {
|
||||
service = malloc(sizeof(struct Service));
|
||||
service->id = malloc(strlen(id)+1);
|
||||
strcpy(service->id, id);
|
||||
service->name = "";
|
||||
service->text = "";
|
||||
service->icon = "";
|
||||
service->backoff = global_backoff;
|
||||
vector_push_back(&services, &service);
|
||||
}
|
||||
if (strcmp(name, "name") == 0) {
|
||||
current_service->name = malloc(strlen(value)+1);
|
||||
strcpy(current_service->name, value);
|
||||
service->name = malloc(strlen(value)+1);
|
||||
strcpy(service->name, value);
|
||||
} else if (strcmp(name, "text") == 0) {
|
||||
current_service->text = malloc(strlen(value)+1);
|
||||
strcpy(current_service->text, value);
|
||||
service->text = malloc(strlen(value)+1);
|
||||
strcpy(service->text, value);
|
||||
} else if (strcmp(name, "icon") == 0) {
|
||||
current_service->icon = malloc(strlen(value)+1);
|
||||
strcpy(current_service->icon, value);
|
||||
} else if (strcmp(name, "address") == 0) {
|
||||
service->icon = malloc(strlen(value)+1);
|
||||
strcpy(service->icon, value);
|
||||
} else if (strcmp(name, "endpoints") == 0) {
|
||||
char* endpoints = malloc(strlen(value));
|
||||
strncpy(endpoints, value, strlen(value));
|
||||
char* token = strtok(endpoints, ",");
|
||||
vector_setup(¤t_service->endpoints, 255, sizeof(struct Endpoint*));
|
||||
vector_setup(&service->endpoints, 255, sizeof(struct Endpoint*));
|
||||
while (token != NULL) {
|
||||
add_endpoint(current_service, token);
|
||||
add_endpoint(service, token);
|
||||
fflush(stdout);
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
vector_shrink_to_fit(&service->endpoints);
|
||||
} else if (strcmp(name, "backoff") == 0) {
|
||||
current_service->backoff = atoi(value);
|
||||
service->backoff = atoi(value);
|
||||
} else {
|
||||
printf("Unknown configuration entry \'%s\' in section \'%s\'\n", name, id);
|
||||
printf("Unknown configuration entry \'%s\' in \'%s\'\n", name, section);
|
||||
}
|
||||
} else {
|
||||
printf("Unknown configuration section \'%s\'\n", section);
|
||||
@@ -109,12 +125,242 @@ static void init_signals() {
|
||||
sigaction(SIGPIPE, &sa, NULL);
|
||||
}
|
||||
|
||||
static enum MHD_Result http_response(void *cls, struct MHD_Connection *conn, const char* uri, const char* method, const char* version, const char* upload_Data, size_t* upload_data_size, void** req_cls) {
|
||||
const union MHD_ConnectionInfo *info = MHD_get_connection_info(conn, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
|
||||
const char* client_ip = (char*)malloc(64);
|
||||
uint16_t client_port = 0;
|
||||
if (info != NULL) {
|
||||
const struct sockaddr *addr = info->client_addr;
|
||||
char ip_str[INET6_ADDRSTRLEN];
|
||||
|
||||
if (addr->sa_family == AF_INET) {
|
||||
const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr;
|
||||
client_ip = inet_ntop(AF_INET, &addr4->sin_addr, ip_str, sizeof(ip_str));
|
||||
client_port = ntohs(addr4->sin_port);
|
||||
} else if (addr->sa_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
|
||||
client_ip = inet_ntop(AF_INET6, &addr6->sin6_addr, ip_str, sizeof(ip_str));
|
||||
client_port = ntohs(addr6->sin6_port);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t response_code = 0;
|
||||
char* page;
|
||||
size_t page_size = 0;
|
||||
|
||||
if (strcmp(method, "GET") != 0 || strcmp(uri, "/") != 0)
|
||||
response_code = 400;
|
||||
|
||||
if (response_code != 200) {
|
||||
mxml_node_t* doc = mxmlNewXML("1.0");
|
||||
if (strcmp(uri, "/") == 0 && response_code == 0) {
|
||||
doc = cached_index;
|
||||
response_code = 200;
|
||||
} else {
|
||||
mxml_node_t* html = mxmlNewElement(doc, "html");
|
||||
mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml");
|
||||
mxmlElementSetAttr(html, "xml:lang", "en");
|
||||
mxmlElementSetAttr(html, "lang", "en");
|
||||
|
||||
mxml_node_t* head = mxmlNewElement(html, "head");
|
||||
mxml_node_t *title = mxmlNewElement(head, "title");
|
||||
mxmlNewText(title, 0, daemon_name);
|
||||
|
||||
mxml_node_t* meta_charset = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type");
|
||||
mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8");
|
||||
|
||||
mxml_node_t* meta_viewport = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_viewport, "name", "viewport");
|
||||
mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0");
|
||||
|
||||
mxml_node_t* body = mxmlNewElement(html, "body");
|
||||
|
||||
mxml_node_t* center = mxmlNewElement(body, "center");
|
||||
|
||||
mxml_node_t* error_header = mxmlNewElement(center, "h1");
|
||||
char err_str[5];
|
||||
snprintf(err_str, sizeof(err_str)-1, "%i", response_code);
|
||||
mxmlNewText(error_header, 0, err_str);
|
||||
}
|
||||
page = mxmlSaveAllocString(doc, MXML_NO_CALLBACK);
|
||||
page_size = strlen(page);
|
||||
}
|
||||
|
||||
printf("[%lu] %s %s \"%s\" %s:%u %zu %zu %i\n", time(NULL), method, uri, version, client_ip, client_port, *upload_data_size, page_size, response_code);
|
||||
fflush(stdout);
|
||||
|
||||
struct MHD_Response* response = MHD_create_response_from_buffer_static(page_size, page);
|
||||
enum MHD_Result ret = MHD_queue_response(conn, response_code, response);
|
||||
MHD_destroy_response(response);
|
||||
return ret;
|
||||
};
|
||||
|
||||
mxml_node_t* generate_index() {
|
||||
mxml_node_t* doc = mxmlNewXML("1.0");
|
||||
mxml_node_t* html = mxmlNewElement(doc, "html");
|
||||
mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml");
|
||||
mxmlElementSetAttr(html, "xml:lang", "en");
|
||||
mxmlElementSetAttr(html, "lang", "en");
|
||||
|
||||
mxml_node_t* head = mxmlNewElement(html, "head");
|
||||
mxml_node_t *title = mxmlNewElement(head, "title");
|
||||
mxmlNewText(title, 0, daemon_name);
|
||||
|
||||
mxml_node_t* meta_charset = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type");
|
||||
mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8");
|
||||
|
||||
mxml_node_t* meta_viewport = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_viewport, "name", "viewport");
|
||||
mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0");
|
||||
|
||||
if (web_stylesheet != 0 && strcmp(web_stylesheet, "") != 0) {
|
||||
mxml_node_t* stylesheet = mxmlNewElement(head, "link");
|
||||
mxmlElementSetAttr(stylesheet, "rel", "stylesheet");
|
||||
mxmlElementSetAttr(stylesheet, "href", web_stylesheet);
|
||||
} else {
|
||||
mxml_node_t* stylesheet = mxmlNewElement(head, "style");
|
||||
mxmlNewText(stylesheet, 0, INTERNAL_STYLESHEET);
|
||||
}
|
||||
|
||||
mxml_node_t* body = mxmlNewElement(html, "body");
|
||||
|
||||
mxml_node_t* center = mxmlNewElement(body, "center");
|
||||
|
||||
if (web_banner != 0 && strlen(web_banner) > 0) {
|
||||
mxml_node_t* banner = mxmlNewElement(center, "img");
|
||||
mxmlElementSetAttr(banner, "class", "banner");
|
||||
mxmlElementSetAttr(banner, "src", web_banner);
|
||||
}
|
||||
|
||||
mxml_node_t* header = mxmlNewElement(center, "h1");
|
||||
mxmlElementSetAttr(header, "class", "header");
|
||||
mxmlNewText(header, 0, daemon_name);
|
||||
|
||||
int row_index = 0;
|
||||
struct XMLServiceRow table_rows[services.size];
|
||||
|
||||
mxml_node_t* table = mxmlNewElement(center, "table");
|
||||
mxmlElementSetAttr(table, "width", "100%");
|
||||
|
||||
mxml_node_t* colgroup = mxmlNewElement(table, "colgroup");
|
||||
|
||||
int col_count = 1;
|
||||
long now = microtime();
|
||||
long earliest_time = 0;
|
||||
|
||||
Iterator count_it = vector_begin(&status_timeline);
|
||||
Iterator peroid_end = vector_end(&status_timeline);
|
||||
for (; !iterator_equals(&count_it, &peroid_end); iterator_increment(&count_it)) {
|
||||
struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&count_it);
|
||||
if (earliest_time == 0) earliest_time = peroid->time;
|
||||
else if (peroid->time <= (now - (long)status_retention * 1000000)) {
|
||||
earliest_time = peroid->time;
|
||||
} else col_count++;
|
||||
}
|
||||
|
||||
Iterator service_it = vector_begin(&services);
|
||||
Iterator service_end = vector_end(&services);
|
||||
for (; !iterator_equals(&service_it, &service_end); iterator_increment(&service_it)) {
|
||||
struct Service* service = *(struct Service**)iterator_get(&service_it);
|
||||
mxml_node_t* header_row = mxmlNewElement(table, "tr");
|
||||
mxmlElementSetAttr(header_row, "class", "title");
|
||||
mxml_node_t* header_cell = mxmlNewElement(header_row, "td");
|
||||
mxmlElementSetAttr(header_cell, "align", "left");
|
||||
char* span_str = (char*)malloc(50);
|
||||
snprintf(span_str, 50, "%d", col_count);
|
||||
mxmlElementSetAttr(header_cell, "colspan", span_str);
|
||||
|
||||
if (service->text != 0 && strlen(service->text) > 0) {
|
||||
mxml_node_t* details = mxmlNewElement(header_cell, "details");
|
||||
mxmlElementSetAttr(details, "open", "1");
|
||||
mxml_node_t* summary = mxmlNewElement(details, "summary");
|
||||
if (service->icon != 0 && strlen(service->icon) > 0) {
|
||||
mxml_node_t* icon = mxmlNewElement(summary, "img");
|
||||
mxmlElementSetAttr(icon, "src", service->icon);
|
||||
}
|
||||
mxml_node_t* title_bold = mxmlNewElement(summary, "b");
|
||||
mxmlNewText(title_bold, service->icon != 0, service->name);
|
||||
mxmlNewText(details, 0, service->text);
|
||||
} else {
|
||||
if (service->icon != 0 && strlen(service->icon) > 0) {
|
||||
mxml_node_t* icon = mxmlNewElement(header_cell, "img");
|
||||
mxmlElementSetAttr(icon, "src", service->icon);
|
||||
}
|
||||
mxml_node_t* title_bold = mxmlNewElement(header_cell, "b");
|
||||
mxmlNewText(title_bold, service->icon != 0, service->name);
|
||||
}
|
||||
|
||||
mxml_node_t* status_row = mxmlNewElement(table, "tr");
|
||||
table_rows[row_index] = (struct XMLServiceRow){ service, status_row };
|
||||
row_index++;
|
||||
}
|
||||
|
||||
long last_time = earliest_time;
|
||||
long total_time = now - earliest_time;
|
||||
Iterator peroid_it = vector_begin(&status_timeline);
|
||||
for (; !iterator_equals(&peroid_it, &peroid_end); iterator_increment(&peroid_it)) {
|
||||
struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&peroid_it);
|
||||
if (peroid->time < earliest_time) continue;
|
||||
|
||||
long duration = peroid->time - last_time;
|
||||
if (total_time > 0 && duration > 0) {
|
||||
double col_size = (double)duration / total_time * 100.0L;
|
||||
char* size_str = (char*)malloc(50);
|
||||
snprintf(size_str, 50, "%lf%%", col_size);
|
||||
|
||||
mxml_node_t* col = mxmlNewElement(colgroup, "col");
|
||||
mxmlElementSetAttr(col, "width", size_str);
|
||||
last_time = peroid->time;
|
||||
}
|
||||
|
||||
Iterator sit = vector_begin(&peroid->server_statuses);
|
||||
Iterator send = vector_end(&peroid->server_statuses);
|
||||
for (; !iterator_equals(&sit, &send); iterator_increment(&sit)) {
|
||||
struct ServerStatus* srvstat = *(struct ServerStatus**)iterator_get(&sit);
|
||||
|
||||
struct XMLServiceRow* row_struct;
|
||||
for (int x = 0; x < services.size; x++)
|
||||
if (table_rows[x].service == srvstat->service) {
|
||||
row_struct = &table_rows[x];
|
||||
break;
|
||||
}
|
||||
|
||||
if (row_struct->service != 0) {
|
||||
mxml_node_t* cell = mxmlNewElement(row_struct->row, "td");
|
||||
mxmlElementSetAttr(cell, "_status", state_text(srvstat->state));
|
||||
mxmlElementSetAttrf(cell, "_ping", "%.02f ms", srvstat->ping);
|
||||
|
||||
long peroid_time_s = peroid->time / 1000000;
|
||||
char* time_str = (char*)malloc(20);
|
||||
strftime(time_str, 20, "%a %b %d %I:%M %P", localtime(&peroid_time_s));
|
||||
mxmlElementSetAttr(cell, "_time", time_str);
|
||||
|
||||
mxmlElementSetAttrf(cell, "_toptext", "%s - %.02f ms", state_text(srvstat->state), srvstat->ping);
|
||||
mxmlElementSetAttr(cell, "_bottomtext", time_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
mxmlNewElement(colgroup, "col");
|
||||
|
||||
mxmlNewElement(center, "hr");
|
||||
mxml_node_t* footer = mxmlNewElement(center, "p");
|
||||
mxmlElementSetAttr(footer, "class", "footer");
|
||||
mxmlNewText(footer, 0, "Powered by ");
|
||||
mxml_node_t* prj_link = mxmlNewElement(footer, "a");
|
||||
mxmlElementSetAttr(prj_link, "href", "https://git.canithesis.org/Canithesis/stethoscope");
|
||||
mxmlNewText(prj_link, 0, "Stethoscope");
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
printf("Starting Stethoscope...\n");
|
||||
fflush(stdout);
|
||||
init_signals();
|
||||
|
||||
vector_setup(&status_timeline, 100, sizeof(struct StatusPeroid*));
|
||||
vector_setup(&status_timeline, 1000, sizeof(struct StatusPeroid*));
|
||||
|
||||
char* config_path;
|
||||
for (int c = 1; c < argc; c++) {
|
||||
@@ -138,7 +384,7 @@ int main(int argc, char** argv) {
|
||||
// Load configuration file
|
||||
vector_setup(&services, 255, sizeof(struct Service*));
|
||||
printf("Loading configuration file: %s\n", config_path);
|
||||
int err = ini_parse(config_path, config_handler, &services);
|
||||
int err = ini_parse(config_path, config_handler, NULL);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "Failed to load configuration file: %i\n", err);
|
||||
return 1;
|
||||
@@ -156,14 +402,13 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
vector_shrink_to_fit(&services);
|
||||
|
||||
struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_DUAL_STACK | MHD_USE_INTERNAL_POLLING_THREAD, listen_port, NULL, NULL, &http_response, NULL, MHD_OPTION_END);
|
||||
struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_DUAL_STACK | MHD_USE_INTERNAL_POLLING_THREAD, web_port, NULL, NULL, &http_response, NULL, MHD_OPTION_END);
|
||||
if (daemon == NULL) {
|
||||
fprintf(stderr, "Failed to start HTTP daemon\n");
|
||||
return 2;
|
||||
}
|
||||
int last_doc_generated = 0;
|
||||
running = 1;
|
||||
while (daemon != NULL && running == 1) {
|
||||
long last_doc_generated = 0;
|
||||
while (daemon != NULL && daemon_enabled == 1) {
|
||||
time_t now = time(NULL);
|
||||
if (status_refresh > 0 && queued_time <= now - status_refresh) {
|
||||
queued_time = now;
|
||||
@@ -173,8 +418,8 @@ int main(int argc, char** argv) {
|
||||
report_queued = 0;
|
||||
report(1);
|
||||
}
|
||||
if (last_doc_generated <= now - web_refresh || last_doc_generated < (queued_time / 1000000)) {
|
||||
index_doc = generate_index();
|
||||
if (last_doc_generated <= now - web_recache || last_doc_generated < (queued_time / 1000000)) {
|
||||
cached_index = generate_index();
|
||||
last_doc_generated = now;
|
||||
}
|
||||
sleep(1);
|
||||
|
||||
294
src/main.h
294
src/main.h
@@ -15,300 +15,14 @@
|
||||
#include "util.h"
|
||||
#include "services.c"
|
||||
|
||||
#define PAGE_INDEX 0
|
||||
#define PAGE_ASSET 1
|
||||
#define PAGE_ERROR 2
|
||||
|
||||
mxml_node_t* index_doc;
|
||||
|
||||
uint8_t running = 0;
|
||||
char* daemon_name = "Stethoscope";
|
||||
uint16_t listen_port = 7800;
|
||||
char* webroot = "share/";
|
||||
char* daemon_banner = "";
|
||||
uint32_t global_backoff = 15;
|
||||
uint32_t web_refresh = 15;
|
||||
uint32_t status_refresh = 30;
|
||||
uint32_t retention = 86400;
|
||||
|
||||
int get_asset(char** buffer, const char* path, size_t* size) {
|
||||
*size = 0;
|
||||
if (strlen(webroot) + strlen(path) + 2 > PATH_MAX)
|
||||
return 404;
|
||||
|
||||
char full_path[PATH_MAX] = {};
|
||||
snprintf(full_path, sizeof(full_path), "%s%s", webroot, path);
|
||||
|
||||
char resolved_path[PATH_MAX] = {};
|
||||
if (realpath(full_path, resolved_path) == NULL)
|
||||
return 404;
|
||||
|
||||
char resolved_base[PATH_MAX] = {};
|
||||
if (realpath(webroot, resolved_base) == NULL)
|
||||
return 404;
|
||||
|
||||
size_t resolved_base_len = strlen(resolved_base);
|
||||
if (strncmp(resolved_path, resolved_base, resolved_base_len) != 0)
|
||||
return 404;
|
||||
|
||||
if (resolved_path[resolved_base_len] != '\0' && resolved_path[resolved_base_len] != '/')
|
||||
return 404;
|
||||
|
||||
FILE* asset = fopen(resolved_path, "rb");
|
||||
if (!asset) return 404;
|
||||
|
||||
fseek(asset, 0, SEEK_END);
|
||||
*size = ftell(asset);
|
||||
rewind(asset);
|
||||
|
||||
*buffer = (char*)malloc(*size + 1);
|
||||
if (!*buffer) {
|
||||
fclose(asset);
|
||||
*size = 0;
|
||||
return 500;
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(*buffer, 1, *size, asset);
|
||||
fclose(asset);
|
||||
|
||||
if (bytes_read != *size) {
|
||||
free(*buffer);
|
||||
*size = 0;
|
||||
return 500;
|
||||
}
|
||||
|
||||
return 200;
|
||||
};
|
||||
|
||||
struct XMLServiceRow {
|
||||
struct Service* service;
|
||||
mxml_node_t* row;
|
||||
};
|
||||
|
||||
static enum MHD_Result http_response(void *cls, struct MHD_Connection *conn, const char* uri, const char* method, const char* version, const char* upload_Data, size_t* upload_data_size, void** req_cls) {
|
||||
const union MHD_ConnectionInfo *info = MHD_get_connection_info(conn, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
|
||||
const char* client_ip = (char*)malloc(64);
|
||||
uint16_t client_port = 0;
|
||||
if (info != NULL) {
|
||||
const struct sockaddr *addr = info->client_addr;
|
||||
char ip_str[INET6_ADDRSTRLEN];
|
||||
#define INTERNAL_STYLESHEET \
|
||||
"td[_status=UP]{background-color:green;}\n" \
|
||||
"td[_status=PARTIAL]{background-color:yellow;}" \
|
||||
"td[_status=DOWN]{background-color:red;}"
|
||||
|
||||
if (addr->sa_family == AF_INET) {
|
||||
const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr;
|
||||
client_ip = inet_ntop(AF_INET, &addr4->sin_addr, ip_str, sizeof(ip_str));
|
||||
client_port = ntohs(addr4->sin_port);
|
||||
} else if (addr->sa_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
|
||||
client_ip = inet_ntop(AF_INET6, &addr6->sin6_addr, ip_str, sizeof(ip_str));
|
||||
client_port = ntohs(addr6->sin6_port);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t response_code = 0;
|
||||
char* page;
|
||||
size_t page_size = 0;
|
||||
|
||||
if (strcmp(method, "GET") == 0) {
|
||||
if (strcmp(uri, "/") != 0)
|
||||
response_code = get_asset(&page, uri, &page_size);
|
||||
} else response_code = 400;
|
||||
|
||||
if (response_code != 200) {
|
||||
mxml_node_t* doc = mxmlNewXML("1.0");
|
||||
if (strcmp(uri, "/") == 0 && response_code == 0) {
|
||||
doc = index_doc;
|
||||
response_code = 200;
|
||||
} else {
|
||||
mxml_node_t* html = mxmlNewElement(doc, "html");
|
||||
mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml");
|
||||
mxmlElementSetAttr(html, "xml:lang", "en");
|
||||
mxmlElementSetAttr(html, "lang", "en");
|
||||
|
||||
mxml_node_t* head = mxmlNewElement(html, "head");
|
||||
mxml_node_t *title = mxmlNewElement(head, "title");
|
||||
mxmlNewText(title, 0, daemon_name);
|
||||
|
||||
mxml_node_t* meta_charset = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type");
|
||||
mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8");
|
||||
|
||||
mxml_node_t* meta_viewport = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_viewport, "name", "viewport");
|
||||
mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0");
|
||||
|
||||
mxml_node_t* body = mxmlNewElement(html, "body");
|
||||
|
||||
mxml_node_t* center = mxmlNewElement(body, "center");
|
||||
|
||||
mxml_node_t* error_header = mxmlNewElement(center, "h1");
|
||||
char err_str[5];
|
||||
snprintf(err_str, sizeof(err_str)-1, "%i", response_code);
|
||||
mxmlNewText(error_header, 0, err_str);
|
||||
}
|
||||
page = mxmlSaveAllocString(doc, MXML_NO_CALLBACK);
|
||||
page_size = strlen(page);
|
||||
}
|
||||
|
||||
printf("[%lu] %s %s \"%s\" %s:%u %zu %zu %i\n", time(NULL), method, uri, version, client_ip, client_port, *upload_data_size, page_size, response_code);
|
||||
fflush(stdout);
|
||||
|
||||
struct MHD_Response* response = MHD_create_response_from_buffer_static(page_size, page);
|
||||
enum MHD_Result ret = MHD_queue_response(conn, response_code, response);
|
||||
MHD_destroy_response(response);
|
||||
return ret;
|
||||
};
|
||||
|
||||
mxml_node_t* generate_index() {
|
||||
mxml_node_t* doc = mxmlNewXML("1.0");
|
||||
mxml_node_t* html = mxmlNewElement(doc, "html");
|
||||
mxmlElementSetAttr(html, "xmlns", "http://www.w3.org/1999/xhtml");
|
||||
mxmlElementSetAttr(html, "xml:lang", "en");
|
||||
mxmlElementSetAttr(html, "lang", "en");
|
||||
|
||||
mxml_node_t* head = mxmlNewElement(html, "head");
|
||||
mxml_node_t *title = mxmlNewElement(head, "title");
|
||||
mxmlNewText(title, 0, daemon_name);
|
||||
|
||||
mxml_node_t* meta_charset = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_charset, "http-equiv", "Content-Type");
|
||||
mxmlElementSetAttr(meta_charset, "content", "text/html; charset=utf-8");
|
||||
|
||||
mxml_node_t* meta_viewport = mxmlNewElement(head, "meta");
|
||||
mxmlElementSetAttr(meta_viewport, "name", "viewport");
|
||||
mxmlElementSetAttr(meta_viewport, "content", "width=device-width, initial-scale=1.0");
|
||||
|
||||
mxml_node_t* stylesheet = mxmlNewElement(head, "link");
|
||||
mxmlElementSetAttr(stylesheet, "rel", "stylesheet");
|
||||
mxmlElementSetAttr(stylesheet, "href", "style.css");
|
||||
|
||||
mxml_node_t* body = mxmlNewElement(html, "body");
|
||||
|
||||
mxml_node_t* center = mxmlNewElement(body, "center");
|
||||
|
||||
if (daemon_banner != 0 && strlen(daemon_banner) > 0) {
|
||||
mxml_node_t* banner = mxmlNewElement(center, "img");
|
||||
mxmlElementSetAttr(banner, "class", "banner");
|
||||
mxmlElementSetAttr(banner, "src", daemon_banner);
|
||||
}
|
||||
|
||||
mxml_node_t* header = mxmlNewElement(center, "h1");
|
||||
mxmlElementSetAttr(header, "class", "header");
|
||||
mxmlNewText(header, 0, daemon_name);
|
||||
|
||||
int row_index = 0;
|
||||
struct XMLServiceRow table_rows[services.size];
|
||||
|
||||
mxml_node_t* table = mxmlNewElement(center, "table");
|
||||
mxmlElementSetAttr(table, "width", "100%");
|
||||
|
||||
mxml_node_t* colgroup = mxmlNewElement(table, "colgroup");
|
||||
|
||||
int col_count = 1;
|
||||
long now = microtime();
|
||||
long earliest_time = 0;
|
||||
|
||||
Iterator count_it = vector_begin(&status_timeline);
|
||||
Iterator peroid_end = vector_end(&status_timeline);
|
||||
for (; !iterator_equals(&count_it, &peroid_end); iterator_increment(&count_it)) {
|
||||
struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&count_it);
|
||||
if (earliest_time == 0) earliest_time = peroid->time;
|
||||
else if (peroid->time <= (now - (long)retention * 1000000)) {
|
||||
earliest_time = peroid->time;
|
||||
} else col_count++;
|
||||
}
|
||||
|
||||
Iterator service_it = vector_begin(&services);
|
||||
Iterator service_end = vector_end(&services);
|
||||
for (; !iterator_equals(&service_it, &service_end); iterator_increment(&service_it)) {
|
||||
struct Service* service = *(struct Service**)iterator_get(&service_it);
|
||||
mxml_node_t* header_row = mxmlNewElement(table, "tr");
|
||||
mxmlElementSetAttr(header_row, "class", "title");
|
||||
mxml_node_t* header_cell = mxmlNewElement(header_row, "td");
|
||||
mxmlElementSetAttr(header_cell, "align", "left");
|
||||
char* span_str = (char*)malloc(50);
|
||||
snprintf(span_str, 50, "%d", col_count);
|
||||
mxmlElementSetAttr(header_cell, "colspan", span_str);
|
||||
|
||||
if (service->text != 0 && strlen(service->text) > 0) {
|
||||
mxml_node_t* details = mxmlNewElement(header_cell, "details");
|
||||
mxmlElementSetAttr(details, "open", "1");
|
||||
mxml_node_t* summary = mxmlNewElement(details, "summary");
|
||||
if (service->icon != 0 && strlen(service->icon) > 0) {
|
||||
mxml_node_t* icon = mxmlNewElement(summary, "img");
|
||||
mxmlElementSetAttr(icon, "src", service->icon);
|
||||
}
|
||||
mxml_node_t* title_bold = mxmlNewElement(summary, "b");
|
||||
mxmlNewText(title_bold, service->icon != 0, service->name);
|
||||
mxmlNewText(details, 0, service->text);
|
||||
} else {
|
||||
if (service->icon != 0 && strlen(service->icon) > 0) {
|
||||
mxml_node_t* icon = mxmlNewElement(header_cell, "img");
|
||||
mxmlElementSetAttr(icon, "src", service->icon);
|
||||
}
|
||||
mxml_node_t* title_bold = mxmlNewElement(header_cell, "b");
|
||||
mxmlNewText(title_bold, service->icon != 0, service->name);
|
||||
}
|
||||
|
||||
mxml_node_t* status_row = mxmlNewElement(table, "tr");
|
||||
table_rows[row_index] = (struct XMLServiceRow){ service, status_row };
|
||||
row_index++;
|
||||
}
|
||||
|
||||
long last_time = earliest_time;
|
||||
long total_time = now - earliest_time;
|
||||
Iterator peroid_it = vector_begin(&status_timeline);
|
||||
for (; !iterator_equals(&peroid_it, &peroid_end); iterator_increment(&peroid_it)) {
|
||||
struct StatusPeroid* peroid = *(struct StatusPeroid**)iterator_get(&peroid_it);
|
||||
if (peroid->time < earliest_time) continue;
|
||||
|
||||
long duration = peroid->time - last_time;
|
||||
if (total_time > 0 && duration > 0) {
|
||||
double col_size = (double)duration / total_time * 100.0L;
|
||||
char* size_str = (char*)malloc(50);
|
||||
snprintf(size_str, 50, "%lf%%", col_size);
|
||||
|
||||
mxml_node_t* col = mxmlNewElement(colgroup, "col");
|
||||
mxmlElementSetAttr(col, "width", size_str);
|
||||
last_time = peroid->time;
|
||||
}
|
||||
|
||||
Iterator sit = vector_begin(&peroid->server_statuses);
|
||||
Iterator send = vector_end(&peroid->server_statuses);
|
||||
for (; !iterator_equals(&sit, &send); iterator_increment(&sit)) {
|
||||
struct ServerStatus* srvstat = *(struct ServerStatus**)iterator_get(&sit);
|
||||
|
||||
struct XMLServiceRow* row_struct;
|
||||
for (int x = 0; x < services.size; x++)
|
||||
if (table_rows[x].service == srvstat->service) {
|
||||
row_struct = &table_rows[x];
|
||||
break;
|
||||
}
|
||||
|
||||
if (row_struct->service != 0) {
|
||||
mxml_node_t* cell = mxmlNewElement(row_struct->row, "td");
|
||||
mxmlElementSetAttr(cell, "_status", state_text(srvstat->state));
|
||||
mxmlElementSetAttrf(cell, "_ping", "%.02f ms", srvstat->ping);
|
||||
|
||||
long peroid_time_s = peroid->time / 1000000;
|
||||
char* time_str = (char*)malloc(20);
|
||||
strftime(time_str, 20, "%a %b %d %I:%M %P", localtime(&peroid_time_s));
|
||||
mxmlElementSetAttr(cell, "_time", time_str);
|
||||
|
||||
mxmlElementSetAttrf(cell, "_toptext", "%s - %.02f ms", state_text(srvstat->state), srvstat->ping);
|
||||
mxmlElementSetAttr(cell, "_bottomtext", time_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
mxmlNewElement(colgroup, "col");
|
||||
|
||||
mxmlNewElement(center, "hr");
|
||||
mxml_node_t* footer = mxmlNewElement(center, "p");
|
||||
mxmlElementSetAttr(footer, "class", "footer");
|
||||
mxmlNewText(footer, 0, "Powered by ");
|
||||
mxml_node_t* prj_link = mxmlNewElement(footer, "a");
|
||||
mxmlElementSetAttr(prj_link, "href", "https://git.canithesis.org/Canithesis/stethoscope");
|
||||
mxmlNewText(prj_link, 0, "Stethoscope");
|
||||
|
||||
return doc;
|
||||
}
|
||||
#endif
|
||||
@@ -346,6 +346,17 @@ void deinit_service(struct Service** self) {
|
||||
}
|
||||
}
|
||||
|
||||
struct Service* get_service_of_id_or_null(const char* id) {
|
||||
Iterator it = vector_begin(&services);
|
||||
Iterator end = vector_end(&services);
|
||||
for (; !iterator_equals(&it, &end); iterator_increment(&it)) {
|
||||
struct Service* current = *(struct Service**)iterator_get(&it);
|
||||
if (strcmp(id, current->id) == 0)
|
||||
return current;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* state_text(enum State state) {
|
||||
switch (state) {
|
||||
case NONE: return "NONE";
|
||||
|
||||
Reference in New Issue
Block a user