summaryrefslogtreecommitdiff
path: root/src/web.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/web.c')
-rw-r--r--src/web.c191
1 files changed, 187 insertions, 4 deletions
diff --git a/src/web.c b/src/web.c
index e0d6d55..c55278a 100644
--- a/src/web.c
+++ b/src/web.c
@@ -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,