From 00abf345b94304ab82611fadb94484a028d69a70 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Sun, 20 Dec 2020 16:47:40 +0100 Subject: [PATCH] Added basic file transfer --- src/cache.c | 107 ++++++++++++++++++++++++++++++++++++++++-- src/cache.h | 17 ++++--- src/client.c | 85 +++++++++++++++++++++++++-------- src/necronda-server.c | 6 ++- src/necronda-server.h | 4 ++ src/uri.c | 5 +- src/uri.h | 6 +-- 7 files changed, 192 insertions(+), 38 deletions(-) diff --git a/src/cache.c b/src/cache.c index 36ddbd4..94a12e3 100644 --- a/src/cache.c +++ b/src/cache.c @@ -10,22 +10,123 @@ int magic_init() { magic = magic_open(MAGIC_MIME); - magic_load(magic, "/usr/share/misc/magic.mgc"); + if (magic == NULL) { + fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno)); + return -1; + } + if (magic_load(magic, MAGIC_FILE) != 0) { + fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic)); + return -2; + } return 0; } int cache_init() { - magic_init(); + if (magic_init() != 0) { + return -1; + } + int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL); + if (shm_id < 0) { + fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno)); + return -2; + } + + void *shm = shmat(shm_id, NULL, SHM_RDONLY); + if (shm == (void *) -1) { + fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno)); + return -3; + } + cache = shm; + + void *shm_rw = shmat(shm_id, NULL, 0); + if (shm_rw == (void *) -1) { + fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno)); + return -4; + } + cache = shm_rw; + memset(cache, 0, FILE_CACHE_SIZE * sizeof(cache_entry)); + // TODO load cache from file + shmdt(shm_rw); + cache = shm; + + return 0; +} + +int cache_unload() { + int shm_id = shmget(SHM_KEY, 0, 0); + if (shm_id < 0) { + fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno)); + } else if (shmctl(shm_id, IPC_RMID, NULL) < 0) { + fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno)); + } + shmdt(cache); + return 0; +} + +int cache_update_entry(int entry_num, const char *filename) { + void *cache_ro = cache; + int shm_id = shmget(SHM_KEY, 0, 0); + void *shm_rw = shmat(shm_id, NULL, 0); + if (shm_rw == (void *) -1) { + print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno)); + return -1; + } + cache = shm_rw; + + struct stat statbuf; + stat(filename, &statbuf); + memcpy(&cache[entry_num].meta.stat, &statbuf, sizeof(statbuf)); + + strcpy(cache[entry_num].filename, filename); + magic_setflags(magic, MAGIC_MIME_TYPE); + strcpy(cache[entry_num].meta.type, magic_file(magic, filename)); + magic_setflags(magic, MAGIC_MIME_ENCODING); + strcpy(cache[entry_num].meta.charset, magic_file(magic, filename)); + cache[entry_num].is_valid_etag = 0; + cache[entry_num].is_updating = 0; + + shmdt(shm_rw); + cache = cache_ro; return 0; } int uri_cache_init(http_uri *uri) { if (uri->filename == NULL) { - return -1; + return 0; } + int i; + for (i = 0; i < FILE_CACHE_SIZE; i++) { + if (cache[i].filename[0] != 0 && strncmp(cache[i].filename, uri->filename, cache[i].filename_len) == 0) { + uri->meta = &cache[i].meta; + if (cache[i].is_updating) { + return 0; + } else { + break; + } + } + } + if (uri->meta == NULL) { + for (i = 0; i < FILE_CACHE_SIZE; i++) { + if (cache[i].filename[0] == 0) { + if (cache_update_entry(i, uri->filename) != 0) { + return -1; + } + uri->meta = &cache[i].meta; + break; + } + } + } else { + struct stat statbuf; + stat(uri->filename, &statbuf); + if (uri->meta->stat.st_mtimensec != statbuf.st_mtimensec) { + if (cache_update_entry(i, uri->filename) != 0) { + return -1; + } + } + } return 0; } diff --git a/src/cache.h b/src/cache.h index 599dc0c..e68e613 100644 --- a/src/cache.h +++ b/src/cache.h @@ -9,24 +9,27 @@ #define NECRONDA_SERVER_CACHE_H #include +#include +#include magic_t magic; typedef struct { - char *etag; - char *type; - char *subtype; - char *filename_comp; + char etag[64]; + char type[24]; + char charset[16]; + char filename_comp[256]; struct stat stat; } meta_data; typedef struct { - char *filename; + char filename[256]; unsigned short filename_len; + unsigned char is_valid_etag:1; + unsigned char is_updating:1; meta_data meta; } cache_entry; -cache_entry cache[FILE_CACHE_SIZE]; -int cache_entries = 0; +cache_entry *cache; #endif //NECRONDA_SERVER_CACHE_H diff --git a/src/client.c b/src/client.c index a6b57f6..5a2c6c7 100644 --- a/src/client.c +++ b/src/client.c @@ -39,11 +39,12 @@ int client_request_handler(sock *client, int req_num) { struct timespec begin, end; int ret, client_keep_alive, dir_mode; char buf0[1024], buf1[1024]; - char msg_buf[4096], msg_pre_buf[4096]; - char err_msg[256]; + char msg_buf[4096], msg_pre_buf[4096], err_msg[256]; + char buffer[CHUNK_SIZE]; err_msg[0] = 0; char *host, *hdr_connection, *webroot; unsigned long content_length = 0; + FILE *file = NULL; http_res res; sprintf(res.version, "1.1"); @@ -165,13 +166,27 @@ int client_request_handler(sock *client, int req_num) { } if (uri.is_static) { - uri_init_cache(&uri); + res.status = http_get_status(200); http_add_header_field(&res.hdr, "Allow", "GET, HEAD"); http_add_header_field(&res.hdr, "Accept-Ranges", "bytes"); if (strncmp(req.method, "GET", 3) != 0 && strncmp(req.method, "HEAD", 4) != 0) { res.status = http_get_status(405); goto respond; } + + ret = uri_cache_init(&uri); + if (ret != 0) { + res.status = http_get_status(500); + sprintf(err_msg, "Unable to communicate with internal file cache."); + goto respond; + } + sprintf(buf0, "%s, charset=%s", uri.meta->type, uri.meta->charset); + http_add_header_field(&res.hdr, "Content-Type", buf0); + + file = fopen(uri.filename, "rb"); + fseek(file, 0, 2); + content_length = ftell(file); + fseek(file, 0, 0); } respond: @@ -185,31 +200,63 @@ int client_request_handler(sock *client, int req_num) { if (http_get_header_field(&res.hdr, "Accept-Ranges", HTTP_PRESERVE_UPPER) == NULL) { http_add_header_field(&res.hdr, "Accept-Ranges", "none"); } - unsigned long len = 0; + if (res.status->code >= 400 && res.status->code < 600) { http_error_msg *http_msg = http_get_error_msg(res.status->code); sprintf(msg_pre_buf, http_error_document, res.status->code, res.status->msg, http_msg != NULL ? http_msg->err_msg : "", err_msg[0] != 0 ? err_msg : ""); - len = sprintf(msg_buf, http_default_document, res.status->code, res.status->msg, - msg_pre_buf, res.status->code >= 300 && res.status->code < 400 ? "info" : "error", - http_error_icon, "#C00000"); - sprintf(buf0, "%li", len); - http_add_header_field(&res.hdr, "Content-Length", buf0); + content_length = sprintf(msg_buf, http_default_document, res.status->code, res.status->msg, + msg_pre_buf, res.status->code >= 300 && res.status->code < 400 ? "info" : "error", + http_error_icon, "#C00000"); http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8"); - } else { - sprintf(buf0, "%li", content_length); - http_add_header_field(&res.hdr, "Content-Length", buf0); } + sprintf(buf0, "%li", content_length); + http_add_header_field(&res.hdr, "Content-Length", buf0); http_send_response(client, &res); + clock_gettime(CLOCK_MONOTONIC, &end); + char *location = http_get_header_field(&res.hdr, "Location", HTTP_PRESERVE_UPPER); + unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000; + print("%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), res.status->code, res.status->msg, + location != NULL ? " -> " : "", location != NULL ? location : "", format_duration(micros, buf0), CLR_STR); + if (strncmp(req.method, "HEAD", 4) != 0) { + unsigned long snd_len = 0; + unsigned long len = 0; if (res.status->code >= 400 && res.status->code < 600) { - int snd_len = 0; - while (snd_len < len) { + while (snd_len < content_length) { if (client->enc) { - ret = SSL_write(client->ssl, msg_buf, (int) (len - snd_len)); + ret = SSL_write(client->ssl, msg_buf, (int) (content_length - snd_len)); + if (ret <= 0) { + print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret)); + } } else { - ret = send(client->socket, msg_buf, len - snd_len, 0); + ret = send(client->socket, msg_buf, content_length - snd_len, 0); + if (ret < 0) { + print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno)); + } + } + if (ret < 0) { + break; + } + snd_len += ret; + } + } else if (file != NULL) { + while (snd_len < content_length) { + len = fread(&buffer, 1, CHUNK_SIZE, file); + if (client->enc) { + ret = SSL_write(client->ssl, buffer, (int) len); + if (ret <= 0) { + print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret)); + } + } else { + ret = send(client->socket, buffer, len, 0); + if (ret < 0) { + print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno)); + } + } + if (ret <= 0) { + break; } snd_len += ret; } @@ -217,10 +264,8 @@ int client_request_handler(sock *client, int req_num) { } clock_gettime(CLOCK_MONOTONIC, &end); - char *location = http_get_header_field(&res.hdr, "Location", HTTP_PRESERVE_UPPER); - unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000; - print("%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), res.status->code, res.status->msg, - location != NULL ? " -> " : "", location != NULL ? location : "", format_duration(micros, buf0), CLR_STR); + micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000; + print("Transfer complete: %s", format_duration(micros, buf0)); uri_free(&uri); abort: diff --git a/src/necronda-server.c b/src/necronda-server.c index f9c6aee..a9385b9 100644 --- a/src/necronda-server.c +++ b/src/necronda-server.c @@ -80,6 +80,7 @@ void destroy() { if (kills > 0) { fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills); } + cache_unload(); exit(2); } @@ -137,6 +138,7 @@ void terminate() { } else { fprintf(stderr, "Goodbye\n"); } + cache_unload(); exit(0); } @@ -239,7 +241,9 @@ int main(int argc, const char *argv[]) { signal(SIGINT, terminate); signal(SIGTERM, terminate); - cache_init(); + if (cache_init() != 0) { + return 1; + } openssl_init(); client.ctx = SSL_CTX_new(TLS_server_method()); diff --git a/src/necronda-server.h b/src/necronda-server.h index 904a78b..707a99b 100644 --- a/src/necronda-server.h +++ b/src/necronda-server.h @@ -33,8 +33,10 @@ #define REQ_PER_CONNECTION 100 #define CLIENT_TIMEOUT 3600 +#define CHUNK_SIZE 4096 #define CLIENT_MAX_HEADER_SIZE 8192 #define FILE_CACHE_SIZE 1024 +#define SHM_KEY 255641 #define ERR_STR "\x1B[1;31m" #define CLR_STR "\x1B[0m" @@ -52,6 +54,8 @@ #define SERVER_STR "Necronda/" NECRONDA_VERSION #define NECRONDA_DEFAULT "www.necronda.net" +#define MAGIC_FILE "/usr/share/file/misc/magic.mgc" + int SOCKETS[NUM_SOCKETS]; pid_t CHILDREN[MAX_CHILDREN]; diff --git a/src/uri.c b/src/uri.c index 7f73ccb..cc50fe2 100644 --- a/src/uri.c +++ b/src/uri.c @@ -36,6 +36,7 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo uri->query = NULL; uri->filename = NULL; uri->uri = NULL; + uri->meta = NULL; uri->is_static = 1; uri->is_dir = 0; if (uri_str[0] != '/') { @@ -155,10 +156,6 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo return 0; } -int uri_init_cache(http_uri *uri) { - return 0; -} - void uri_free(http_uri *uri) { if (uri->webroot != NULL) free(uri->webroot); if (uri->req_path != NULL) free(uri->req_path); diff --git a/src/uri.h b/src/uri.h index b7af308..d2969c6 100644 --- a/src/uri.h +++ b/src/uri.h @@ -23,9 +23,9 @@ typedef struct { char *query; // "username=test" char *filename; // "/account/index.php" char *uri; // "/account/login?username=test" - meta_data meta; - unsigned int is_static:1; - unsigned int is_dir:1; + meta_data *meta; + unsigned char is_static:1; + unsigned char is_dir:1; } http_uri;