diff options
| author | Jesper Jensen <jesper@jnsn.dev> | 2026-02-18 18:34:02 +0100 |
|---|---|---|
| committer | Jesper Jensen <jesper@jnsn.dev> | 2026-02-18 18:34:02 +0100 |
| commit | 5fbbd990d6b42f5e7534cdb68fa5c368a4a69169 (patch) | |
| tree | 3db3b955fcceb71fbb8ea161643a3a1044b2e4f2 /src | |
| parent | 78745e06f4a4365e9caa8ee12fb0d9e15fb59300 (diff) | |
Generate secrets for clients
Diffstat (limited to 'src')
| -rw-r--r-- | src/web.c | 103 | ||||
| -rw-r--r-- | src/web.h | 3 |
2 files changed, 99 insertions, 7 deletions
@@ -13,6 +13,7 @@ #include <microhttpd.h> #include <sqlite3.h> #include <cjson/cJSON.h> +#include <fcntl.h> #define PAGE "<html><head><title>libmicrohttpd demo</title>"\ "</head><body>libmicrohttpd demo</body></html>" @@ -22,6 +23,21 @@ static void sqliteError(void *pArg, int iErrCode, const char *zMsg) { fprintf(stderr, "(%d) %s\n", iErrCode, zMsg); } +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; +} void prepare_database(struct App *app) { // @CLEANUP: Move this to main? doesn't belong here at least @@ -176,6 +192,36 @@ void prepare_database(struct App *app) { abort(); } } + + if(maxVersion < 4) { + char *prog = + "BEGIN TRANSACTION;" + "ALTER TABLE clients ADD COLUMN secret TEXT NOT NULL DEFAULT ''"; + if(sqlite3_exec(conn, prog, NULL, NULL, NULL) != SQLITE_OK) { + abort(); + } + + sqlite3_stmt *stmt; + if(sqlite3_prepare_v2(conn, "INSERT INTO migration (id) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { + abort(); + } + + if(sqlite3_bind_int64(stmt, 1, 4) != SQLITE_OK) { + abort(); + } + + if(sqlite3_step(stmt) != SQLITE_DONE) { + abort(); + } + + if(sqlite3_finalize(stmt) != SQLITE_OK) { + abort(); + } + + if(sqlite3_exec(conn, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { + abort(); + } + } } enum ReportEvent { @@ -184,7 +230,8 @@ enum ReportEvent { }; // @CLEANUP: This should live somewhere else, but I don't have a spot for it yet. -int submit_report(struct App *app, char *client, enum ReportEvent event) { +// Returns 0 on success, -1 on error, -2 on secret mismatch +int submit_report(struct App *app, char *client, char *secret, enum ReportEvent event) { sqlite3 *conn; int err; if((err = sqlite3_open(app->dbname, &conn)) != SQLITE_OK) { @@ -196,7 +243,7 @@ int submit_report(struct App *app, char *client, enum ReportEvent event) { int64_t clientId; { sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "SELECT id FROM clients WHERE name = ?1", -1, &stmt, NULL) != SQLITE_OK) { + if(sqlite3_prepare_v2(conn, "SELECT id, secret FROM clients WHERE name = ?1", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_text(stmt, 1, client, -1, SQLITE_STATIC) != SQLITE_OK) { @@ -212,6 +259,13 @@ int submit_report(struct App *app, char *client, enum ReportEvent event) { } clientId = sqlite3_column_int64(stmt, 0); + const char *stored_secret = (const char *)sqlite3_column_text(stmt, 1); + if(stored_secret == NULL || strcmp(stored_secret, secret) != 0) { + sqlite3_finalize(stmt); + sqlite3_close(conn); + return -2; + } + if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); } @@ -313,14 +367,21 @@ int create_client(struct Request *req, char *name, struct CreateClientResult *re abort(); } + if(generate_secret(result->secret) != 0) { + abort(); + } + { sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "INSERT INTO clients (name) VALUES (?1)", -1, &stmt, NULL) != SQLITE_OK) { + if(sqlite3_prepare_v2(conn, "INSERT INTO clients (name, secret) VALUES (?1, ?2)", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC) != SQLITE_OK) { abort(); } + if(sqlite3_bind_text(stmt, 2, result->secret, -1, SQLITE_STATIC) != SQLITE_OK) { + abort(); + } if(sqlite3_step(stmt) != SQLITE_DONE) { abort(); @@ -373,7 +434,7 @@ int list_clients(struct Request *req, struct ListClientResult *result) { { sqlite3_stmt *stmt; - if(sqlite3_prepare_v2(conn, "SELECT c.id, c.name FROM clients c WHERE c.id > ?1 ORDER BY c.id ASC LIMIT ?2", -1, &stmt, NULL) != SQLITE_OK) { + if(sqlite3_prepare_v2(conn, "SELECT c.id, c.name, c.secret FROM clients c WHERE c.id > ?1 ORDER BY c.id ASC LIMIT ?2", -1, &stmt, NULL) != SQLITE_OK) { abort(); } if(sqlite3_bind_int64(stmt, 1, -1) != SQLITE_OK) { @@ -394,9 +455,11 @@ int list_clients(struct Request *req, struct ListClientResult *result) { int64_t clientId = sqlite3_column_int64(stmt, 0); const uint8_t *name = sqlite3_column_text(stmt, 1); + const uint8_t *secret = sqlite3_column_text(stmt, 2); result->clients[index].id = clientId; strncpy(result->clients[index].name, (char*)name, CLIENT_NAME_MAX); + strncpy(result->clients[index].secret, (char*)secret, CLIENT_SECRET_LEN); index++; } @@ -443,12 +506,13 @@ static char *render_clients_page(struct Request *request, size_t *out_len) { "<body>\n" " <h1>Clients</h1>\n" " <table border=\"1\">\n" - " <tr><th>Name</th><th>Actions</th></tr>\n"); + " <tr><th>Name</th><th>Secret</th><th>Actions</th></tr>\n"); for (int i = 0; i < result->clients_len; i++) { fprintf(f, " <tr>\n" " <td>%s</td>\n" + " <td>%s</td>\n" " <td>\n" " <form method=\"POST\" action=\"/clients/delete\" style=\"display:inline\">\n" " <input type=\"hidden\" name=\"client_id\" value=\"%ld\">\n" @@ -457,6 +521,7 @@ static char *render_clients_page(struct Request *request, size_t *out_len) { " </td>\n" " </tr>\n", result->clients[i].name, + result->clients[i].secret, result->clients[i].id ); } @@ -639,6 +704,11 @@ static enum MHD_Result handler( 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(); @@ -653,7 +723,26 @@ static enum MHD_Result handler( } - ret = submit_report(server->app, clientJson->valuestring, event); + ret = submit_report(server->app, clientJson->valuestring, secretJson->valuestring, event); + 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 ret; + ret = MHD_queue_response( + connection, + MHD_HTTP_UNAUTHORIZED, + response + ); + MHD_destroy_response(response); + return ret; + } if(ret != 0) { // Should actually return an error page, but this will do for now return MHD_NO; @@ -697,7 +786,7 @@ static enum MHD_Result handler( destroy_post_collector(&request->pp); char *buf = malloc(1024); - size_t buf_len = snprintf(buf, 1024, "{ \"id\": \"%ld\" }", result.id); + size_t buf_len = snprintf(buf, 1024, "{ \"id\": \"%ld\", \"secret\": \"%s\" }", result.id, result.secret); if(buf_len >= 1024) { abort(); } @@ -21,6 +21,7 @@ int web_begin(struct Server *server, int port); int web_join(struct Server *server); #define CLIENT_NAME_MAX 32 +#define CLIENT_SECRET_LEN 33 struct PostCollector { char *data; @@ -45,6 +46,7 @@ struct Request { struct CreateClientResult { int64_t id; char* name; + char secret[CLIENT_SECRET_LEN]; }; int create_client(struct Request *req, char *name, struct CreateClientResult *result); @@ -53,6 +55,7 @@ int delete_client(struct Request *req, int64_t client_id); struct ListClientResultClient { int64_t id; char name[CLIENT_NAME_MAX]; + char secret[CLIENT_SECRET_LEN]; }; struct ListClientResult { |
