diff options
Diffstat (limited to 'src/web.c')
| -rw-r--r-- | src/web.c | 191 |
1 files changed, 187 insertions, 4 deletions
@@ -14,6 +14,7 @@ #include <sqlite3.h> #include <cjson/cJSON.h> #include <fcntl.h> +#include <crypt.h> #define PAGE "<html><head><title>libmicrohttpd demo</title>"\ "</head><body>libmicrohttpd demo</body></html>" @@ -39,6 +40,26 @@ static int generate_secret(char *out) { 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; +} + void prepare_database(struct App *app) { // @CLEANUP: Move this to main? doesn't belong here at least sqlite3_config(SQLITE_CONFIG_LOG, sqliteError, NULL); @@ -614,6 +635,9 @@ static char *render_clients_page(struct Request *request, size_t *out_len) { "</head>\n" "<body>\n" " <h1>Clients</h1>\n" + " <form method=\"POST\" action=\"/logout\" style=\"float:right;margin-top:-40px;\">\n" + " <button type=\"submit\">Logout</button>\n" + " </form>\n" " <table border=\"1\">\n" " <tr><th>Name</th><th>Secret</th><th>Actions</th></tr>\n"); @@ -745,6 +769,39 @@ static char *render_client_detail_page(struct Request *request, int64_t client_i return buf; } +static char *render_login_page(const char *error_msg, size_t *out_len) { + char *buf = NULL; + size_t buf_len = 0; + FILE *f = open_memstream(&buf, &buf_len); + if (!f) return NULL; + + fprintf(f, + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + " <title>Login</title>\n" + "</head>\n" + "<body>\n" + " <h1>Login</h1>\n"); + + if (error_msg) { + fprintf(f, " <p style=\"color: red;\">%s</p>\n", error_msg); + } + + fprintf(f, + " <form method=\"POST\" action=\"/login\">\n" + " <p><label>Username: <input type=\"text\" name=\"username\" required></label></p>\n" + " <p><label>Password: <input type=\"password\" name=\"password\" required></label></p>\n" + " <p><button type=\"submit\">Login</button></p>\n" + " </form>\n" + "</body>\n" + "</html>\n"); + + fclose(f); + *out_len = buf_len; + return buf; +} + static enum MHD_Result create_post_collector(struct PostCollector *c) { c->data = malloc(512); c->size = 0; @@ -799,6 +856,14 @@ form_iterator(void *cls, 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; @@ -831,7 +896,7 @@ static enum MHD_Result handler( request->host = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Host"); if(strcmp(method, "POST") == 0) { - if(strcmp(url, "/clients") == 0 || strcmp(url, "/clients/delete") == 0) { + if(strcmp(url, "/clients") == 0 || strcmp(url, "/clients/delete") == 0 || strcmp(url, "/login") == 0) { request->post_proc = MHD_create_post_processor( connection, 1024, &form_iterator, request); if (request->post_proc == NULL) { @@ -861,10 +926,71 @@ static enum MHD_Result handler( } } - if(strcmp(url, "/clients") == 0) { + if(strcmp(url, "/logout") == 0) { + 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"); + ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response); + MHD_destroy_response(response); + return ret; + } else if(strcmp(url, "/login") == 0) { 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 = render_login_page("Invalid credentials", &html_len); + if (!html) return MHD_NO; + 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 ret; + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; + } + + if (!validate_credentials(server->app, request->form.username, request->form.password)) { + size_t html_len; + char *html = render_login_page("Invalid credentials", &html_len); + if (!html) return MHD_NO; + 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 ret; + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; + } + + // Valid credentials - set cookie and redirect + char *token = generate_session_token(server->app, request->form.username, request->form.password); + if (token == NULL) return MHD_NO; + + 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; + } else if(strcmp(url, "/clients") == 0) { + MHD_destroy_post_processor(request->post_proc); + request->post_proc = NULL; + + // Auth check + 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); + return ret; + } + if (!request->form.name_set || request->form.name[0] == '\0') { return MHD_NO; } @@ -885,6 +1011,16 @@ static enum MHD_Result handler( MHD_destroy_post_processor(request->post_proc); request->post_proc = NULL; + // Auth check + 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); + return ret; + } + if (!request->form.client_id_set || request->form.client_id <= 0) { return MHD_NO; } @@ -1031,7 +1167,28 @@ static enum MHD_Result handler( } else if (strcmp(method, "GET") == 0) { if (*upload_data_size != 0) return MHD_NO; - if(strcmp(url, "/clients") == 0) { + if(strcmp(url, "/login") == 0) { + size_t html_len; + char *html = render_login_page(NULL, &html_len); + if (!html) return MHD_NO; + 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 ret; + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + MHD_destroy_response(response); + return ret; + } else if(strcmp(url, "/clients") == 0) { + // Auth check + 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); + return ret; + } + size_t html_len; char *html = render_clients_page(request, &html_len); @@ -1054,6 +1211,16 @@ static enum MHD_Result handler( MHD_destroy_response(response); return ret; } else if (strncmp(url, "/client/", 8) == 0) { + // Auth check + 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); + return ret; + } + int64_t client_id; if (sscanf(url + 8, "%ld", &client_id) != 1 || client_id <= 0) { // Bad Request @@ -1091,6 +1258,16 @@ static enum MHD_Result handler( MHD_destroy_response(response); return ret; } else { + // Auth check for default page + 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); + return ret; + } + struct MHD_Response *response = MHD_create_response_from_buffer( strlen(server->page), (void *)server->page, @@ -1127,10 +1304,16 @@ static void request_completed_callback ( static struct MHD_Daemon *d = NULL; -int web_begin(struct Server *server, int port) { +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, |
