#include "web.h" #include "app.h" #include "html.h" #include "mytime.h" #include #include #include #include #include #include #include #include #include #include #define PAGE "libmicrohttpd demo"\ "libmicrohttpd demo" static int generate_secret(char *out) { unsigned char bytes[16]; int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -1; ssize_t n = read(fd, bytes, 16); close(fd); if (n != 16) return -1; static const char hex[] = "0123456789abcdef"; for (int i = 0; i < 16; i++) { out[i*2] = hex[(bytes[i] >> 4) & 0x0f]; out[i*2+1] = hex[bytes[i] & 0x0f]; } out[32] = '\0'; return 0; } static char *generate_session_token(struct App *app, const char *username, const char *password) { char combined[128]; snprintf(combined, sizeof(combined), "%s:%s", username, password); return crypt(combined, app->session_secret); } static int validate_credentials(struct App *app, const char *username, const char *password) { return strcmp(username, app->admin_user) == 0 && strcmp(password, app->admin_pass) == 0; } static int is_authenticated(struct App *app, struct MHD_Connection *connection) { const char *cookie = MHD_lookup_connection_value(connection, MHD_COOKIE_KIND, "session"); if (cookie == NULL) return 0; char *expected = generate_session_token(app, app->admin_user, app->admin_pass); if (expected == NULL) return 0; return strcmp(cookie, expected) == 0; } static enum MHD_Result create_post_collector(struct PostCollector *c) { c->data = malloc(512); c->size = 0; c->capacity = 512; return MHD_YES; } static enum MHD_Result collect_post(struct PostCollector *c, const char *data, size_t *data_len) { size_t newSize = c->size + *data_len; if(newSize > c->capacity) { while(newSize > c->capacity) c->capacity *= 2; c->data = realloc(c->data, c->capacity); } memcpy(c->data + c->size, data, *data_len); c->size = newSize; return MHD_YES; } static enum MHD_Result destroy_post_collector(struct PostCollector *c) { if(c->capacity != 0) { free(c->data); c->capacity = 0; } return MHD_YES; } static enum MHD_Result form_iterator(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size) { struct Request *request = cls; if (strcmp(key, "name") == 0 && off + size < CLIENT_NAME_MAX) { memcpy(request->form.name + off, data, size); request->form.name[off + size] = '\0'; request->form.name_set = 1; } else if (strcmp(key, "client_id") == 0 && off == 0) { char buf[32] = {0}; size_t copy_size = size < 31 ? size : 31; memcpy(buf, data, copy_size); request->form.client_id = strtoll(buf, NULL, 10); request->form.client_id_set = 1; } else if (strcmp(key, "username") == 0 && off + size < 32) { memcpy(request->form.username + off, data, size); request->form.username[off + size] = '\0'; request->form.username_set = 1; } else if (strcmp(key, "password") == 0 && off + size < 64) { memcpy(request->form.password + off, data, size); request->form.password[off + size] = '\0'; request->form.password_set = 1; } return MHD_YES; } static enum HandlerStatus handle_logout( struct MHD_Connection *connection, struct Request *request ) { if (strcmp(request->url, "/logout") != 0) return HANDLER_DECLINE; if (strcmp(request->method, "POST") != 0) return HANDLER_DECLINE; struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Set-Cookie", "session=; Path=/; HttpOnly; Max-Age=0"); MHD_add_response_header(response, "Location", "/login"); enum MHD_Result ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); MHD_destroy_response(response); return ret == MHD_YES ? HANDLER_ACCEPTED : HANDLER_ERROR; } static enum HandlerStatus handle_login( struct MHD_Connection *connection, struct Request *request ) { if (strcmp(request->url, "/login") != 0) return HANDLER_DECLINE; struct Server *server = request->server; enum MHD_Result ret; if (strcmp(request->method, "POST") == 0) { if (request->post_proc == NULL && !request->form.username_set) { request->post_proc = MHD_create_post_processor( connection, 1024, &form_iterator, request); if (request->post_proc == NULL) return HANDLER_ERROR; return HANDLER_ACCEPTED; } MHD_destroy_post_processor(request->post_proc); request->post_proc = NULL; if (!request->form.username_set || !request->form.password_set) { size_t html_len; char *html = html_render_login_page("Invalid credentials", &html_len); if (!html) return HANDLER_ERROR; struct MHD_Response *response = MHD_create_response_from_buffer( html_len, html, MHD_RESPMEM_MUST_FREE); ret = MHD_add_response_header(response, "Content-Type", "text/html"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response(connection, MHD_HTTP_OK, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } if (!validate_credentials(server->app, request->form.username, request->form.password)) { size_t html_len; char *html = html_render_login_page("Invalid credentials", &html_len); if (!html) return HANDLER_ERROR; struct MHD_Response *response = MHD_create_response_from_buffer( html_len, html, MHD_RESPMEM_MUST_FREE); ret = MHD_add_response_header(response, "Content-Type", "text/html"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response(connection, MHD_HTTP_OK, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } char *token = generate_session_token(server->app, request->form.username, request->form.password); if (token == NULL) return HANDLER_ERROR; char cookie_header[128]; snprintf(cookie_header, sizeof(cookie_header), "session=%s; Path=/; HttpOnly", token); struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Set-Cookie", cookie_header); MHD_add_response_header(response, "Location", "/clients"); ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); MHD_destroy_response(response); return ret == MHD_YES ? HANDLER_ACCEPTED : HANDLER_ERROR; } if (strcmp(request->method, "GET") == 0) { size_t html_len; char *html = html_render_login_page(NULL, &html_len); if (!html) return HANDLER_ERROR; struct MHD_Response *response = MHD_create_response_from_buffer( html_len, html, MHD_RESPMEM_MUST_FREE); ret = MHD_add_response_header(response, "Content-Type", "text/html"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response(connection, MHD_HTTP_OK, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } return HANDLER_DECLINE; } static enum HandlerStatus handle_clients( struct MHD_Connection *connection, struct Request *request ) { if (strcmp(request->url, "/clients") != 0) return HANDLER_DECLINE; struct Server *server = request->server; enum MHD_Result ret; if (strcmp(request->method, "POST") == 0) { if (request->post_proc == NULL && !request->form.name_set) { request->post_proc = MHD_create_post_processor( connection, 1024, &form_iterator, request); if (request->post_proc == NULL) return HANDLER_ERROR; return HANDLER_ACCEPTED; } MHD_destroy_post_processor(request->post_proc); request->post_proc = NULL; if (!is_authenticated(server->app, connection)) { struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Location", "/login"); ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } if (!request->form.name_set || request->form.name[0] == '\0') { return HANDLER_ERROR; } struct CreateClientResult result; ret = create_client(server->app, request->form.name, &result); if(ret != 0) return HANDLER_ERROR; // Post-Redirect-Get: redirect back to GET /clients struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT); ret = MHD_add_response_header(response, "Location", "/clients"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } if (strcmp(request->method, "GET") == 0) { if (!is_authenticated(server->app, connection)) { struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Location", "/login"); ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } struct ListClientResult *clients = malloc(sizeof(struct ListClientResult) + sizeof(struct ListClientResultClient) * 16); ret = list_clients(server->app, clients); if(ret != 0) { free(clients); return HANDLER_ERROR; } size_t html_len; char *html = html_render_clients_page(request, clients, &html_len); free(clients); if (!html) { return HANDLER_ERROR; } struct MHD_Response *response = MHD_create_response_from_buffer( html_len, html, MHD_RESPMEM_MUST_FREE ); ret = MHD_add_response_header(response, "Content-Type", "text/html"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response( connection, MHD_HTTP_OK, response ); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } return HANDLER_DECLINE; } static enum HandlerStatus handle_clients_delete( struct MHD_Connection *connection, struct Request *request ) { if (strcmp(request->url, "/clients/delete") != 0) return HANDLER_DECLINE; if (strcmp(request->method, "POST") != 0) return HANDLER_DECLINE; struct Server *server = request->server; enum MHD_Result ret; if (request->post_proc == NULL && !request->form.client_id_set) { request->post_proc = MHD_create_post_processor( connection, 1024, &form_iterator, request); if (request->post_proc == NULL) return HANDLER_ERROR; return HANDLER_ACCEPTED; } MHD_destroy_post_processor(request->post_proc); request->post_proc = NULL; if (!is_authenticated(server->app, connection)) { struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Location", "/login"); ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } if (!request->form.client_id_set || request->form.client_id <= 0) { return HANDLER_ERROR; } ret = delete_client(server->app, request->form.client_id); if(ret != 0) return HANDLER_ERROR; struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT); ret = MHD_add_response_header(response, "Location", "/clients"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } static enum HandlerStatus handle_api_report( struct MHD_Connection *connection, struct Request *request ) { if (strcmp(request->url, "/api/report") != 0) return HANDLER_DECLINE; if (strcmp(request->method, "POST") != 0) return HANDLER_DECLINE; struct Server *server = request->server; enum MHD_Result ret; if (request->pp.capacity == 0) { create_post_collector(&request->pp); return HANDLER_ACCEPTED; } cJSON *rootJson = cJSON_ParseWithLength(request->pp.data, request->pp.size); cJSON *clientJson = cJSON_GetObjectItemCaseSensitive(rootJson, "client"); if(!cJSON_IsString(clientJson) || clientJson->valuestring == NULL) { abort(); } cJSON *secretJson = cJSON_GetObjectItemCaseSensitive(rootJson, "secret"); if(!cJSON_IsString(secretJson) || secretJson->valuestring == NULL) { abort(); } cJSON *eventJson = cJSON_GetObjectItemCaseSensitive(rootJson, "event"); if(!cJSON_IsString(eventJson) || eventJson->valuestring == NULL) { abort(); } int is_finish; if(strcmp(eventJson->valuestring, "begin_backup") == 0) { is_finish = 0; } else if(strcmp(eventJson->valuestring, "end_backup") == 0) { is_finish = 1; } else { abort(); } ret = submit_report(server->app, clientJson->valuestring, secretJson->valuestring, is_finish); if(ret == -2) { cJSON_Delete(rootJson); destroy_post_collector(&request->pp); struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT ); ret = MHD_add_response_header(response, "Content-Type", "application/json"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response( connection, MHD_HTTP_UNAUTHORIZED, response ); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } if(ret != 0) { return HANDLER_ERROR; } cJSON_Delete(rootJson); destroy_post_collector(&request->pp); struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT ); ret = MHD_add_response_header(response, "Content-Type", "application/json"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response( connection, MHD_HTTP_NO_CONTENT, response ); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } static enum HandlerStatus handle_client_detail( struct MHD_Connection *connection, struct Request *request ) { if (strncmp(request->url, "/client/", 8) != 0) return HANDLER_DECLINE; if (strcmp(request->method, "GET") != 0) return HANDLER_DECLINE; struct Server *server = request->server; enum MHD_Result ret; if (!is_authenticated(server->app, connection)) { struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT); MHD_add_response_header(response, "Location", "/login"); ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } int64_t client_id; if (sscanf(request->url + 8, "%ld", &client_id) != 1 || client_id <= 0) { struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT); ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } struct GetClientOnlyResult client_result; ret = get_client(server->app, client_id, &client_result); if(ret != 0) { return HANDLER_ERROR; } if(!client_result.found) { struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT ); ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } struct GetBackupsResult *backups_result = malloc(sizeof(struct GetBackupsResult) + sizeof(struct BackupRecord) * MAX_BACKUPS_DISPLAY); backups_result->backups_len = MAX_BACKUPS_DISPLAY; ret = get_client_backups(server->app, client_id, backups_result); if(ret != 0) { free(backups_result); return HANDLER_ERROR; } size_t html_len; char *html = html_render_client_detail_page(request, &client_result, backups_result, &html_len); free(backups_result); if (!html) { struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT ); ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } struct MHD_Response *response = MHD_create_response_from_buffer( html_len, html, MHD_RESPMEM_MUST_FREE ); ret = MHD_add_response_header(response, "Content-Type", "text/html"); if(ret != MHD_YES) return HANDLER_ERROR; ret = MHD_queue_response( connection, MHD_HTTP_OK, response ); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } static enum HandlerStatus handle_root( struct MHD_Connection *connection, struct Request *request ) { if (strcmp(request->url, "/") != 0) return HANDLER_DECLINE; if (strcmp(request->method, "GET") != 0) return HANDLER_DECLINE; struct Server *server = request->server; enum MHD_Result ret; if (!is_authenticated(server->app, connection)) { struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT ); MHD_add_response_header(response, "Location", "/login"); ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } struct MHD_Response *response = MHD_create_response_from_buffer( 0, "", MHD_RESPMEM_PERSISTENT ); MHD_add_response_header(response, "Location", "/clients"); ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response); MHD_destroy_response(response); if(ret != MHD_YES) return HANDLER_ERROR; return HANDLER_ACCEPTED; } static enum HandlerStatus handle_not_found( struct MHD_Connection *connection, struct Request *request ) { // This handler MUST accept the request since it will be the last one in // the chain struct MHD_Response *response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); MHD_destroy_response(response); return HANDLER_ACCEPTED; } static RouteHandler handlers[] = { handle_logout, handle_login, handle_clients, handle_clients_delete, handle_api_report, handle_client_detail, handle_root, handle_not_found, // Catch all }; #define HANDLER_COUNT (sizeof(handlers) / sizeof(handlers[0])) static enum MHD_Result handler( void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr ) { struct Server *server = cls; struct Request *request = *ptr; if (request == NULL) { request = calloc(1, sizeof(struct Request)); if(request == NULL) { return MHD_NO; } *ptr = request; request->server = server; request->url = url; request->method = method; // @COMPL: The proxy might rewrite this, and we should support that request->host = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Host"); // @COMPL: There are other headers we should look into request->proto = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "X-Forwarded-Proto"); // Fall through - handlers will set up POST processing if needed } if (*upload_data_size != 0) { if (request->post_proc != NULL) { MHD_post_process(request->post_proc, upload_data, *upload_data_size); } else if (request->pp.capacity > 0) { collect_post(&request->pp, upload_data, upload_data_size); } *upload_data_size = 0; return MHD_YES; } for (size_t i = 0; i < HANDLER_COUNT; i++) { enum HandlerStatus status = handlers[i](connection, request); if (status == HANDLER_ACCEPTED) return MHD_YES; if (status == HANDLER_ERROR) return MHD_NO; // HANDLER_DECLINE: try next } // Should never reach here - handle_not_found always accepts abort(); } static void request_completed_callback ( void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe ) { struct Request *request = *con_cls; if (request->post_proc != NULL) { MHD_destroy_post_processor(request->post_proc); } destroy_post_collector(&request->pp); free(request); } static struct MHD_Daemon *d = NULL; int web_begin(struct Server *server, int port, const char *admin_user, const char *admin_pass) { assert(d == NULL); server->page = PAGE; server->app->admin_user = admin_user; server->app->admin_pass = admin_pass; if (generate_secret(server->app->session_secret) != 0) { return 1; } d = MHD_start_daemon( MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DUAL_STACK, port, NULL, NULL, &handler, server, MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL, MHD_OPTION_END ); if (d == NULL) return 1; return 0; } int web_join(struct Server *server) { assert(d != NULL); MHD_stop_daemon(d); d = NULL; return 0; }