#ifndef H_MAIN #define H_MAIN #include #include #include #include #include #include #include #include #include "../deps/vector/vector.h" #include "mxml.h" #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 = 5; uint32_t web_refresh = 30; 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; } buffer[*size] = 0; 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]; 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, "src", daemon_banner); } mxml_node_t* header = mxmlNewElement(center, "h1"); 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 / 1000; 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* credit_text = mxmlNewElement(center, "p"); mxmlNewText(credit_text, 0, "Powered by "); mxml_node_t* prj_link = mxmlNewElement(credit_text, "a"); mxmlElementSetAttr(prj_link, "href", "https://git.canithesis.org/Canithesis/stethoscope"); mxmlNewText(prj_link, 0, "Stethoscope"); return doc; } #endif