#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 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;
int ret;
// For the first call where con_cls points at NULL we have to create the
// connection state
struct Request *request = *ptr;
if (request == NULL) {
request = calloc(1, sizeof(struct Request));
if(request == NULL) {
return MHD_NO;
}
*ptr = request;
request->server = server;
// @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");
if(strcmp(method, "POST") == 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) {
free(request);
return MHD_NO;
}
} else {
create_post_collector(&request->pp);
}
}
return MHD_YES;
}
if(strcmp(method, "POST") == 0) {
if (request->post_proc != NULL) {
if (*upload_data_size != 0) {
MHD_post_process(request->post_proc, upload_data, *upload_data_size);
*upload_data_size = 0;
return MHD_YES;
}
} else {
collect_post(&request->pp, upload_data, upload_data_size);
if (*upload_data_size != 0) {
*upload_data_size = 0;
return MHD_YES;
}
}
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 = 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 = 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;
}
struct CreateClientResult result;
ret = create_client(server->app, request->form.name, &result);
if(ret != 0) return MHD_NO;
// 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 ret;
ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response);
MHD_destroy_response(response);
return ret;
} else if(strcmp(url, "/clients/delete") == 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.client_id_set || request->form.client_id <= 0) {
return MHD_NO;
}
ret = delete_client(server->app, request->form.client_id);
if(ret != 0) return MHD_NO;
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 ret;
ret = MHD_queue_response(connection, MHD_HTTP_SEE_OTHER, response);
MHD_destroy_response(response);
return ret;
} else if(strcmp(url, "/api/report") == 0) {
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 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;
}
cJSON_Delete(rootJson);
// We've taken in all the data, now we can use it
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_NO_CONTENT,
response
);
MHD_destroy_response(response);
return ret;
} else if(strcmp(url, "/api/client") == 0) {
cJSON *rootJson = cJSON_ParseWithLength(request->pp.data, request->pp.size);
cJSON *nameJson = cJSON_GetObjectItemCaseSensitive(rootJson, "name");
if(!cJSON_IsString(nameJson) || nameJson->valuestring == NULL) {
abort();
}
struct CreateClientResult result;
ret = create_client(server->app, nameJson->valuestring, &result);
if(ret != 0) {
// Should actually return an error page, but this will do for now
return MHD_NO;
}
cJSON_Delete(rootJson);
// We've taken in all the data, now we can use it
destroy_post_collector(&request->pp);
char *buf = malloc(1024);
size_t buf_len = snprintf(buf, 1024, "{ \"id\": \"%ld\", \"secret\": \"%s\" }", result.id, result.secret);
if(buf_len >= 1024) {
abort();
}
struct MHD_Response *response = MHD_create_response_from_buffer(
buf_len,
buf,
MHD_RESPMEM_MUST_FREE
);
ret = MHD_add_response_header(response, "Content-Type", "application/json");
if(ret != MHD_YES) return ret;
ret = MHD_queue_response(
connection,
MHD_HTTP_OK,
response
);
MHD_destroy_response(response);
return ret;
} else {
// Not found
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_NOT_FOUND,
response
);
MHD_destroy_response(response);
return ret;
}
} else if (strcmp(method, "GET") == 0) {
if (*upload_data_size != 0) return MHD_NO;
if(strcmp(url, "/login") == 0) {
size_t html_len;
char *html = 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;
}
// Fetch clients from app layer
struct ListClientResult *clients = malloc(sizeof(struct ListClientResult) + sizeof(struct ListClientResultClient) * 16);
ret = list_clients(server->app, clients);
if(ret != 0) {
free(clients);
return MHD_NO;
}
// Render HTML
size_t html_len;
char *html = html_render_clients_page(request->host, clients, &html_len);
free(clients);
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 (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
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);
return ret;
}
// Fetch client data from app layer
struct GetClientOnlyResult client_result;
ret = get_client(server->app, client_id, &client_result);
if(ret != 0) {
return MHD_NO;
}
if(!client_result.found) {
// Not 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);
return ret;
}
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 MHD_NO;
}
// Render HTML
size_t html_len;
char *html = html_render_client_detail_page(request->host, request->proto, &client_result, backups_result, &html_len);
free(backups_result);
if (!html) {
// Not 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);
return ret;
}
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 {
// 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,
MHD_RESPMEM_PERSISTENT
);
ret = MHD_queue_response(
connection,
MHD_HTTP_OK,
response
);
MHD_destroy_response(response);
return ret;
}
}
return MHD_NO;
}
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;
}