From 79732cf0d63d87c4d403e022d51f8aba73d257c2 Mon Sep 17 00:00:00 2001 From: Darren VanBuren Date: Mon, 12 Sep 2016 22:08:46 -0700 Subject: [PATCH] Initial Commit of hypercube --- .gitignore | 3 + README | 18 + access.cfg.dist | 2 + akbuf/Makefile | 7 + akbuf/akbuf.c | 585 +++++++++++++++++++++++ akbuf/akbuf.h | 133 ++++++ asio/AKepoll.h | 3 + asio/AKepoll.s | 60 +++ asio/Makefile | 6 + asio/asio.c | 352 ++++++++++++++ asio/asio.h | 47 ++ cfg.c | 261 +++++++++++ cfg.h | 49 ++ config.h.dist | 21 + http.c | 970 +++++++++++++++++++++++++++++++++++++++ http.h | 47 ++ hypercube.cfg.dist | 38 ++ hypercube.h | 61 +++ hypercube.includes | 3 + hypercube.pro | 36 ++ log.c | 88 ++++ log.h | 7 + main.c | 68 +++ net.c | 298 ++++++++++++ net.h | 38 ++ setdist.sh | 2 + tracker.c | 1095 ++++++++++++++++++++++++++++++++++++++++++++ tracker.cfg.dist | 33 ++ tracker.h | 48 ++ 29 files changed, 4379 insertions(+) create mode 100644 .gitignore create mode 100644 README create mode 100644 access.cfg.dist create mode 100644 akbuf/Makefile create mode 100644 akbuf/akbuf.c create mode 100644 akbuf/akbuf.h create mode 100644 asio/AKepoll.h create mode 100644 asio/AKepoll.s create mode 100644 asio/Makefile create mode 100644 asio/asio.c create mode 100644 asio/asio.h create mode 100644 cfg.c create mode 100644 cfg.h create mode 100644 config.h.dist create mode 100644 http.c create mode 100644 http.h create mode 100644 hypercube.cfg.dist create mode 100644 hypercube.h create mode 100644 hypercube.includes create mode 100644 hypercube.pro create mode 100644 log.c create mode 100644 log.h create mode 100644 main.c create mode 100644 net.c create mode 100644 net.h create mode 100755 setdist.sh create mode 100644 tracker.c create mode 100644 tracker.cfg.dist create mode 100644 tracker.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4df6999 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build-Desktop-Debug +dist.h +*.o diff --git a/README b/README new file mode 100644 index 0000000..5bd1f85 --- /dev/null +++ b/README @@ -0,0 +1,18 @@ + hypercube är ungefär som rap på skånska + coolt men lite skumt + +How to compile (no autoconf, sorry...): + - Edit asio/asio.h and select what async I/O interface you wanna use. + - Edit Makefile. + - If you are on Linux, USE_LINUX_SENDFILE should be defined. + - If you are on Linux, using epoll (kernel >=2.6), and have a libc which + doesn't support them (warnings about 'epoll_* is not implemented and + will always fail' on linking), uncomment the AKEPOLL* lines. + - Maybe put that final touch to config.h. + It ships with DEBUG enabled. You probably want to disable this. + +Now make :-). +The rest of the configuration is in your CFG_FILE (hypercube.cfg by +default). + + - anakata@anakata.hack.se || anakata@g-con.org diff --git a/access.cfg.dist b/access.cfg.dist new file mode 100644 index 0000000..15d5bc4 --- /dev/null +++ b/access.cfg.dist @@ -0,0 +1,2 @@ + +#deny .* /status \ No newline at end of file diff --git a/akbuf/Makefile b/akbuf/Makefile new file mode 100644 index 0000000..073560a --- /dev/null +++ b/akbuf/Makefile @@ -0,0 +1,7 @@ +CC=gcc +#CCFLAGS=-Wall -Werror -ggdb #-pg +CCFLAGS=-ggdb + +all: akbuf.o +akbuf.o: akbuf.c akbuf.h + $(CC) $(CCFLAGS) -c akbuf.c diff --git a/akbuf/akbuf.c b/akbuf/akbuf.c new file mode 100644 index 0000000..dcc8e04 --- /dev/null +++ b/akbuf/akbuf.c @@ -0,0 +1,585 @@ +#include +#include +#include +#include +#include +#include + +#include "akbuf.h" + +akbuf_ctx *ctx_hash[AKBUF_CTX_HASH_SIZE]; +unsigned int ctx_hash_initialized = 0; +unsigned int prng_initialized = 0; + +//#define RANDFUNC vanilla_rand /* XXX */ +#define RANDFUNC randfunc_inc + +//#define AKBUF_DEBUG + +#define AK_REALSIZE(size) (((size) <= GRAIN_SIZE && (size) != 0)? (size) : (((size) | (GRAIN_SIZE - 1)) + 1)) +#define CTX_HASH_FN(hnd) ((hnd) & (AKBUF_CTX_HASH_SIZE - 1)) + +#ifdef AKBUF_TEST +#define AKBUF_DEBUG +#endif + +#ifdef AKBUF_DEBUG +void dump_all_ctxs(void) +{ +#if 0 + akbuf_ctx *ctx; + + printf("dumping ctxs head %.8x:\n", (unsigned int)ctx_head); + ctx = ctx_head; + while (ctx != NULL) + { + printf(" ctx @ %.8x; handle %.8x bufs %.8x\n", (unsigned int)ctx, (unsigned int)ctx->hnd, (unsigned int)ctx->head); + ctx = ctx->next; + } + printf("end of dump\n"); +#endif +} +#endif +unsigned int vanilla_rand(void) +{ + if (prng_initialized == 0) + { + struct timeval tv; + + gettimeofday(&tv, NULL); + srand(tv.tv_sec ^ tv.tv_usec ^ (getpid() << 16)); + prng_initialized = 1; + } + return rand(); +} +static unsigned int cur_ctx; +unsigned int randfunc_inc(void) +{ + if (prng_initialized == 0) + { + struct timeval tv; + + gettimeofday(&tv, NULL); + cur_ctx = tv.tv_usec; + prng_initialized = 1; + } + return cur_ctx ++; +} +void akbuf_panic(unsigned char *file, unsigned int line, unsigned char *from_file, unsigned int from_line, unsigned char *format, ...) +{ + char msg[2048]; + + va_list va; + if (from_file != NULL) + { + snprintf(msg, sizeof(msg) - 1, "AKbuf PANIC @ %s:%u (from %s:%u): ", file, line, from_file, from_line); msg[sizeof(msg) - 1] = 0; + } else + { + snprintf(msg, sizeof(msg) - 1, "AKbuf PANIC @ %s:%u: ", file, line); msg[sizeof(msg) - 1] = 0; + } + write(2, msg, strlen(msg)); + va_start(va, format); + vsnprintf(msg, sizeof(msg) - 1, format, va); msg[sizeof(msg) - 1] = 0; + va_end(va); + write(2, msg, strlen(msg)); + write(2, "\n", 1); + _exit(1); +} +akbuf_ctx *akbuf_find_ctx(akbuf_ctxh hnd) +{ + akbuf_ctx *cur; + + if (ctx_hash_initialized == 0) + { + unsigned int i; + + for (i = 0; i < AKBUF_CTX_HASH_SIZE; i ++) ctx_hash[i] = NULL; + ctx_hash_initialized = 1; + } + cur = ctx_hash[CTX_HASH_FN(hnd)]; + while (cur != NULL) { if (cur->hnd == hnd) return cur; cur = cur->next; } + return NULL; +} +akbuf_ctxh akbuf_new_ctx(void) +{ + akbuf_ctx *newCtx; + unsigned int idx; + + newCtx = malloc(sizeof(*newCtx)); + AKbuf_ASSERT(newCtx != NULL); + do + { + newCtx->hnd = RANDFUNC(); + } while (akbuf_find_ctx(newCtx->hnd) != NULL || newCtx->hnd == AKbuf_INVALID_CTX); + newCtx->head = NULL; + newCtx->prev = NULL; + newCtx->next = ctx_hash[idx = CTX_HASH_FN(newCtx->hnd)]; + if (newCtx->next != NULL) newCtx->next->prev = newCtx; + ctx_hash[idx] = newCtx; + return newCtx->hnd; +} +akbuf *akbuf_init(akbuf_ctxh ctxh, AKsize_t _size) +{ + AKsize_t size; + akbuf *newBuf; + akbuf_ctx *ctx; + + if ((ctx = akbuf_find_ctx(ctxh)) == NULL) AKbuf_PANIC("akbuf_init(): Invalid ctx %.8x", ctxh); + size = AK_REALSIZE(_size); +#ifdef AKBUF_DEBUG + printf("DEBUG: req size %.8x, final size %.8x\n", _size, size); +#endif + AKbuf_ASSERT(size >= _size); + newBuf = malloc(sizeof(akbuf)); + AKbuf_ASSERT(newBuf != NULL); + newBuf->in_use = 1; + newBuf->size = size; + newBuf->idx = 0; + newBuf->prev = NULL; + newBuf->next = ctx->head; + newBuf->head = malloc(size); + AKbuf_ASSERT(newBuf->head != NULL); + if (ctx->head != NULL) ctx->head->prev = newBuf; + ctx->head = newBuf; +#ifdef AKBUF_DEBUG + memset(newBuf->head, 'A', newBuf->size); +#endif + return newBuf; +} +void akbuf_set_size(akbuf *buf, AKsize_t _size) +{ + AKsize_t size; + + AKbuf_dASSERT(buf != NULL); + size = AK_REALSIZE(_size); + AKbuf_ASSERT(size >= _size); + if (size == buf->size) return; + buf->head = realloc(buf->head, size); + AKbuf_ASSERT(buf->head != NULL); + buf->size = size; + if (buf->idx > size) buf->idx = size; + return; +} +void akbuf_consume(akbuf *buf, AKsize_t len) +{ + AKbuf_dASSERT(buf != NULL); + if (len == 0) return; + AKbuf_ASSERT(buf->idx >= len && buf->idx <= buf->size); + memcpy(buf->head, buf->head + len, buf->idx - len); + buf->idx -= len; + if (len >= GRAIN_SIZE) akbuf_set_size(buf, buf->idx); + return; +} +unsigned char akbuf_eat_byte(akbuf *buf) +{ + unsigned char ret; + + AKbuf_dASSERT(buf != NULL); + AKbuf_ASSERT(buf->idx > 0 && buf->idx <= buf->size); + ret = *buf->head; + memcpy(buf->head, buf->head + 1, buf->idx - 1); + buf->idx --; + return ret; +} +void akbuf_set_data(akbuf *buf, unsigned char *data, AKsize_t len) +{ + AKbuf_dASSERT(buf != NULL && data != NULL); + if (len > buf->size) akbuf_set_size(buf, len); + memcpy(buf->head, data, len); + buf->idx = len; + return; +} +void akbuf_vsprintf(akbuf *buf, unsigned char *format, va_list va) +{ + AKssize_t n; + + AKbuf_dASSERT(buf != NULL && format != NULL); + if ((n = vsnprintf(buf->head, buf->size, format, va)) >= buf->size && n >= 0) + { + AKbuf_ASSERT((n + 1) > n); + akbuf_set_size(buf, n + 1); + n = vsnprintf(buf->head, buf->size, format, va); + } + AKbuf_ASSERT(n >= 0); + buf->idx = n; + return; +} +void akbuf_sprintf(akbuf *buf, unsigned char *format, ...) +{ + va_list va; + + va_start(va, format); + akbuf_vsprintf(buf, format, va); + va_end(va); + return; +} +void akbuf_appendf(akbuf *buf, unsigned char *format, ...) +{ + akbuf_ctxh ctx; + akbuf *appendbuf; + va_list va; + + ctx = akbuf_new_ctx(); + appendbuf = akbuf_init(ctx, 0); + va_start(va, format); + akbuf_vsprintf(appendbuf, format, va); + va_end(va); + akbuf_append_buf(buf, appendbuf); + akbuf_free_ctx(ctx); +} +void akbuf_append_data(akbuf *buf, unsigned char *data, AKsize_t len) +{ + AKbuf_dASSERT(buf != NULL && data != NULL); + AKbuf_ASSERT(buf->idx + len >= buf->idx && buf->idx <= buf->size); + if (buf->idx + len >= buf->size) akbuf_set_size(buf, buf->idx + len); + memcpy(buf->head + buf->idx, data, len); buf->idx += len; + return; +} +void akbuf_append_byte(akbuf *buf, unsigned char b) +{ + AKbuf_dASSERT(buf != NULL); + AKbuf_ASSERT(buf->idx + 1 > buf->idx && buf->idx <= buf->size); + if (buf->idx + 1 >= buf->size) akbuf_set_size(buf, buf->idx + 1); + buf->head[buf->idx ++] = b; + return; +} +int akbuf_chr(akbuf *buf, unsigned char b) +{ + unsigned char *p; + + AKbuf_dASSERT(buf != NULL); + if ((p = memchr(buf->head, b, buf->idx)) == NULL) return -1; + return (int)(p - buf->head); +} +void akbuf_asciiz(akbuf *buf) +{ + AKbuf_dASSERT(buf != NULL); + AKbuf_ASSERT(buf->idx + 1 > buf->idx && buf->idx <= buf->size); + if (buf->idx == buf->size) akbuf_set_size(buf, buf->idx + 1); + buf->head[buf->idx] = 0; + return; +} +void akbuf_free(akbuf_ctxh ctxh, akbuf *buf) +{ + akbuf_ctx *ctx; + akbuf *cur; + + AKbuf_dASSERT(buf != NULL); + if ((ctx = akbuf_find_ctx(ctxh)) == NULL) AKbuf_PANIC("akbuf_free(): Invalid ctx %.8x", ctxh); + cur = ctx->head; + while (cur != buf && cur != NULL) cur = cur->next; + if (cur == NULL) AKbuf_PANIC("akbuf_free(): Unknown akbuf %.8x", (unsigned int)buf); +#ifdef AKBUF_DEBUG + printf("DEBUG: freeing buf %.8x\n", (unsigned int)cur); +#endif + AKbuf_ASSERT(cur->in_use == 1 && cur->head != NULL); + cur->in_use = 0; + if (cur->prev != NULL) cur->prev->next = cur->next; + if (cur->next != NULL) cur->next->prev = cur->prev; + if (cur == ctx->head) ctx->head = cur->next; + cur->prev = cur->next = NULL; + cur->idx = (unsigned int)-1; +#ifdef AKBUF_DEBUG + memset(cur->head, 'F', cur->size); +#else + //memset(cur->head, 'F', (cur->size > GRAIN_SIZE)? GRAIN_SIZE : cur->size); +#endif + cur->size = 0; + AKfree(cur->head); cur->head = NULL; + free(cur); +} +void _akbuf_free_ctx(akbuf_ctxh ctxh, unsigned char *from_file, unsigned int from_line) +{ + akbuf_ctx *ctx; + akbuf *cur, *prev; + unsigned int idx; + + if (ctxh == AKbuf_INVALID_CTX) return; + if ((ctx = akbuf_find_ctx(ctxh)) == NULL) AKbuf_PANIC_FROM("akbuf_free_ctx(): Invalid ctx %.8x", ctxh); + cur = ctx->head; + while (cur != NULL) + { + prev = cur; + cur = cur->next; + AKbuf_ASSERT(prev->in_use == 1 && prev->head != NULL); + prev->in_use = 0; + prev->prev = prev->next = NULL; + prev->idx = (unsigned int)-1; +#ifdef AKBUF_DEBUG + printf("DEBUG: prev %.8x prev->size %.8x\n", (unsigned int)prev, prev->size); + memset(prev->head, 'G', prev->size); +#endif + //memset(prev->head, 'F', (prev->size > GRAIN_SIZE)? GRAIN_SIZE : prev->size); + prev->size = 0; + AKfree(prev->head); prev->head = NULL; + free(prev); + } + ctx->head = NULL; + if (ctx->prev != NULL) ctx->prev->next = ctx->next; + if (ctx->next != NULL) ctx->next->prev = ctx->prev; + if (ctx == ctx_hash[idx = CTX_HASH_FN(ctxh)]) ctx_hash[idx] = ctx->next; +#ifdef AKBUF_DEBUG + memset(ctx, 'C', sizeof(akbuf_ctx)); +#endif + free(ctx); +} +akbuf_table *akbuf_table_init(akbuf_ctxh ctxh, unsigned int type) +{ + akbuf *tblbuf; + akbuf_table *ret; + + tblbuf = akbuf_init(ctxh, sizeof(akbuf_table)); + AKbuf_dASSERT(tblbuf != NULL); + akbuf_set_idx(tblbuf, sizeof(akbuf_table)); + ret = (akbuf_table *)akbuf_data(tblbuf); + ret->head = ret->tail = NULL; + ret->type = type; + ret->ctxh = ctxh; + return ret; +} +akbuf_table_entry *akbuf_table_entry_add(akbuf_ctxh ctxh, akbuf_table *tbl, unsigned char *key, akbuf *data) +{ + akbuf *entbuf; + akbuf_table_entry *new; + + AKbuf_dASSERT(tbl != NULL && key != NULL && data != NULL); + AKbuf_ASSERT(ctxh == tbl->ctxh); + if (akbuf_find_ctx(ctxh) == NULL) AKbuf_PANIC("akbuf_table_entry_add(): Invalid ctx %.08x", ctxh); + if ((new = akbuf_table_entry_find(tbl, key)) != NULL) + { + akbuf_clone(new->data, data); + return new; + } + entbuf = akbuf_init(ctxh, sizeof(akbuf_table_entry)); + AKbuf_dASSERT(entbuf != NULL); + akbuf_set_idx(entbuf, sizeof(akbuf_table_entry)); + new = (akbuf_table_entry *)akbuf_data(entbuf); + new->key = akbuf_init(ctxh, 0); + akbuf_strcpy(new->key, key); + akbuf_append_byte(new->key, 0); + new->data = akbuf_init(ctxh, akbuf_idx(data)); + akbuf_clone(new->data, data); + new->prev = tbl->tail; + new->next = NULL; + if (tbl->head == NULL) + { + tbl->head = new; + } else + { + tbl->tail->next = new; + } + tbl->tail = new; + return new; +} +akbuf_table_entry *akbuf_table_entry_add_buf(akbuf_ctxh ctxh, akbuf_table *tbl, akbuf *key, akbuf *data) +{ + akbuf *entbuf; + akbuf_table_entry *new; + + AKbuf_dASSERT(tbl != NULL && key != NULL && data != NULL); + AKbuf_ASSERT(tbl->type == AKBUF_TABLE_BIN); + AKbuf_ASSERT(ctxh == tbl->ctxh); + if (akbuf_find_ctx(ctxh) == NULL) AKbuf_PANIC("akbuf_table_entry_add(): Invalid ctx %.08x", ctxh); + entbuf = akbuf_init(ctxh, sizeof(akbuf_table_entry)); + AKbuf_dASSERT(entbuf != NULL); + akbuf_set_idx(entbuf, sizeof(akbuf_table_entry)); + new = (akbuf_table_entry *)akbuf_data(entbuf); + new->key = akbuf_init(ctxh, akbuf_idx(key)); + akbuf_clone(new->key, key); + akbuf_append_byte(new->key, 0); + new->data = akbuf_init(ctxh, akbuf_idx(data)); + akbuf_clone(new->data, data); + new->prev = tbl->tail; + new->next = NULL; + if (tbl->head == NULL) + { + tbl->head = new; + } else + { + tbl->tail->next = new; + } + tbl->tail = new; + return new; +} +akbuf_table_entry *akbuf_table_entry_add_str(akbuf_ctxh ctxh, akbuf_table *tbl, unsigned char *key, unsigned char *data) +{ + akbuf *buf; + akbuf_ctxh ctx; + akbuf_table_entry *ret; + + ctx = akbuf_new_ctx(); + buf = akbuf_init(ctx, 0); + akbuf_strcpy(buf, data); akbuf_append_byte(buf, 0); + ret = akbuf_table_entry_add(ctxh, tbl, key, buf); + akbuf_free_ctx(ctx); + return ret; +} +akbuf_table_entry *akbuf_table_entry_find(akbuf_table *tbl, unsigned char *key) +{ + int (*cmpfn)(); + akbuf_table_entry *ent; + + AKbuf_dASSERT(tbl != NULL && key != NULL); + AKbuf_ASSERT(tbl->type == AKBUF_TABLE_NOCASE || tbl->type == AKBUF_TABLE_CASE); + cmpfn = (tbl->type == AKBUF_TABLE_CASE)? strcmp : strcasecmp; + ent = tbl->head; + while (ent != NULL) + { + if (cmpfn(akbuf_data(ent->key), key) == 0) return ent; + ent = ent->next; + } + return NULL; +} +akbuf *akbuf_table_entry_get(akbuf_table *tbl, unsigned char *key) +{ + akbuf_table_entry *ent; + + if ((ent = akbuf_table_entry_find(tbl, key)) == NULL) return NULL; + return ent->data; +} +unsigned char *akbuf_table_entry_get_str(akbuf_table *tbl, unsigned char *key) +{ + akbuf *buf; + + if ((buf = akbuf_table_entry_get(tbl, key)) == NULL) return NULL; + akbuf_asciiz(buf); + return akbuf_data(buf); +} + +void akbuf_urlencode_data(unsigned char *data, AKsize_t len, akbuf *outbuf) +{ + unsigned int i; + unsigned int c; + + for (i = 0; i < len; i ++) + { + unsigned char hexchars[] = "0123456789ABCDEF"; + + c = data[i]; + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= '<')) + { + akbuf_append_byte(outbuf, c); + } else + { + akbuf_append_byte(outbuf, '%'); + akbuf_append_byte(outbuf, hexchars[(c >> 4) & 0xf]); + akbuf_append_byte(outbuf, hexchars[c & 0xf]); + } + } +} +void akbuf_base64encode_data(unsigned char *data, AKsize_t len, akbuf *outbuf) +{ + AKsize_t rem; + unsigned char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + rem = len; + while (rem > 2) + { + akbuf_append_byte(outbuf, b64chars[data[0] >> 2]); + akbuf_append_byte(outbuf, b64chars[((data[0] & 0x03) << 4) + (data[1] >> 4)]); + akbuf_append_byte(outbuf, b64chars[((data[1] & 0x0f) << 2) + (data[2] >> 6)]); + akbuf_append_byte(outbuf, b64chars[data[2] & 0x3f]); + data += 3; + rem -= 3; + } + if (rem != 0) + { + akbuf_append_byte(outbuf, b64chars[data[0] >> 2]); + if (rem > 1) + { + akbuf_append_byte(outbuf, b64chars[((data[0] & 0x03) << 4) + (data[1] >> 4)]); + akbuf_append_byte(outbuf, b64chars[((data[1] & 0x0f) << 2)]); + akbuf_append_byte(outbuf, '='); + } else + { + akbuf_append_byte(outbuf, b64chars[((data[0] & 0x03) << 4)]); + akbuf_append_byte(outbuf, '='); + akbuf_append_byte(outbuf, '='); + } + } +} + +#ifdef AKBUF_TEST +int main(int argc, char *argv[]) +{ + akbuf_ctxh ctx; + akbuf *buf, *buf2; + int i; + akbuf_table *tbl; + akbuf_table_entry *ent; + + printf("akbuf_new_ctx()\n"); + ctx = akbuf_new_ctx(); + printf(" = %.8x\n", (unsigned int)ctx); + printf("akbuf_new_ctx() second = %.8x\n", (unsigned int)akbuf_new_ctx()); + dump_all_ctxs(); + + buf = akbuf_init(ctx, 0); + akbuf_strcpy(buf, "blutti"); + printf("akbuf_table_init() =\n"); + tbl = akbuf_table_init(ctx, 0); + printf(" %.08x\n", (unsigned int)tbl); + printf("akbuf_table_entry_add() =\n"); + ent = akbuf_table_entry_add(ctx, tbl, "foobar", buf); + printf(" %.08x\n", (unsigned int)ent); + printf("[%s]\n", akbuf_data(ent->key)); + akbuf_strcpy(buf, "fnutti"); + ent = akbuf_table_entry_add(ctx, tbl, "foobar2", buf); + printf(" next = %.08x prev = %.08x head = %.08x tail = %.08x\n", (unsigned int)ent->next, (unsigned int)ent->prev, (unsigned int)tbl->head, (unsigned int)tbl->tail); + printf("akbuf_table_entry_get(..., \"foobar\")\n"); + buf2 = akbuf_table_entry_get(tbl, "foobar"); + if (buf2 != NULL) + { + akbuf_asciiz(buf2); + printf(" [%s]\n", akbuf_data(buf2)); + } + printf("akbuf_table_entry_get(..., \"foobar2\")\n"); + buf2 = akbuf_table_entry_get(tbl, "foobar2"); + if (buf2 != NULL) + { + akbuf_asciiz(buf2); + printf(" [%s]\n", akbuf_data(buf2)); + } + +#if 1 + printf("akbuf_init(0x242)\n"); + buf = akbuf_init(ctx, 0x242); + printf(" = %.8x\n", (unsigned int)buf); + //printf("akbuf_init(0xffffffff)\n"); + //akbuf_init(ctx, 0xffffffff); + akbuf_strcpy(buf, "foobar blutti"); + akbuf_append_str(buf, " fnutti"); + akbuf_asciiz(buf); + printf("[%s]\n", akbuf_data(buf)); + buf2 = akbuf_init(ctx, 0); + akbuf_strcpy(buf2, "1 2 3"); + akbuf_append_str(buf2," 4 5 6"); + akbuf_consume(buf2, 4); + akbuf_asciiz(buf2); + printf("[%s]\n", akbuf_data(buf2)); + buf = akbuf_init(ctx, 0); + akbuf_sprintf(buf, "foobar %u [%s]", 0x242, "foo"); + akbuf_asciiz(buf); + printf("fmt [%s]\n", akbuf_data(buf)); + printf("freeing\n"); + akbuf_free_ctx(ctx); + dump_all_ctxs(); + ctx = akbuf_new_ctx(); + buf = akbuf_init(ctx, 0x666); + akbuf_strcpy(buf, "foo\nbar\nblutti\nfnutti"); + buf2 = akbuf_init(ctx, 0); + while ((i = akbuf_chr(buf, '\n')) != -1) + { + akbuf_split(buf, buf2, i); + akbuf_asciiz(buf2); + printf("split [%s]\n", akbuf_data(buf2)); + } + akbuf_asciiz(buf); + printf("rem [%s]\n", akbuf_data(buf)); + dump_all_ctxs(); + akbuf_free_ctx(ctx); +#endif + akbuf_free_ctx(ctx); + return 0; +} +#endif diff --git a/akbuf/akbuf.h b/akbuf/akbuf.h new file mode 100644 index 0000000..ef05f8c --- /dev/null +++ b/akbuf/akbuf.h @@ -0,0 +1,133 @@ +//#define GRAIN_SIZE 0x1000 /* must be power of 2 */ +//#define AKBUF_CTX_HASH_SIZE 0x400000 /* must be power of 2 */ +#define GRAIN_SIZE 0x100 +#define AKBUF_CTX_HASH_SIZE 0x40000 + +#define AKsize_t size_t +#define AKssize_t ssize_t + +#define AKbuf_PANIC(f...) akbuf_panic(__FILE__, __LINE__, NULL, 0, ##f) +#define AKbuf_PANIC_FROM(f...) akbuf_panic(__FILE__, __LINE__, from_file, from_line, ##f) +#define AKbuf_ASSERT(cond) if (!(cond)) akbuf_panic(__FILE__, __LINE__, NULL, 0, "Assertion (" __STRING(cond) ") failed") +#ifdef AKBUF_DEBUG +#define AKbuf_dASSERT(cond) AKbuf_ASSERT(cond) +#else +#define AKbuf_dASSERT(cond) +#endif + +#define AKfree(ptr) if ((ptr) != NULL) { free(ptr); (ptr) = NULL; } + +typedef unsigned int akbuf_ctxh; + +#define AKbuf_INVALID_CTX ((akbuf_ctxh)-1) + +typedef struct akbuf_s akbuf; +typedef struct akbuf_ctx_s akbuf_ctx; + +struct akbuf_s +{ + unsigned int in_use; + akbuf *prev; + akbuf *next; + unsigned int idx; + AKsize_t size; + unsigned char *head; +}; + +struct akbuf_ctx_s +{ + akbuf_ctxh hnd; + akbuf *head; + akbuf_ctx *prev; + akbuf_ctx *next; +}; + +typedef struct akbuf_table_s akbuf_table; +typedef struct akbuf_table_entry_s akbuf_table_entry; + +struct akbuf_table_s +{ + akbuf_ctxh ctxh; + unsigned int type; + akbuf_table_entry *head; + akbuf_table_entry *tail; +}; + +struct akbuf_table_entry_s +{ + akbuf *key; + akbuf *data; + akbuf_table_entry *prev; + akbuf_table_entry *next; +}; + +enum { AKBUF_TABLE_NOCASE, AKBUF_TABLE_CASE, AKBUF_TABLE_BIN }; + +void akbuf_panic(unsigned char *, unsigned int, unsigned char *, unsigned int, unsigned char *, ...); +akbuf_ctxh akbuf_new_ctx(void); +void akbuf_free(akbuf_ctxh, akbuf *); +void _akbuf_free_ctx(akbuf_ctxh, unsigned char *, unsigned int); +void akbuf_consume(akbuf *, AKsize_t); +unsigned char akbuf_eat_byte(akbuf *); +int akbuf_chr(akbuf *, unsigned char); +void akbuf_asciiz(akbuf *); + +akbuf *akbuf_init(akbuf_ctxh, AKsize_t); +void akbuf_set_size(akbuf *, AKsize_t); +void akbuf_set_data(akbuf *, unsigned char *, AKsize_t); +void akbuf_append_data(akbuf *, unsigned char *, AKsize_t); +void akbuf_append_byte(akbuf *, unsigned char); +void akbuf_vsprintf(akbuf *, unsigned char *, va_list); +void akbuf_sprintf(akbuf *, unsigned char *, ...); +void akbuf_appendf(akbuf *, unsigned char *, ...); + +akbuf_table *akbuf_table_init(akbuf_ctxh, unsigned int); +akbuf_table_entry *akbuf_table_entry_add(akbuf_ctxh, akbuf_table *, unsigned char *, akbuf *); +akbuf_table_entry *akbuf_table_entry_add_buf(akbuf_ctxh, akbuf_table *, akbuf *, akbuf *); +akbuf_table_entry *akbuf_table_entry_add_str(akbuf_ctxh, akbuf_table *, unsigned char *, unsigned char *); +akbuf_table_entry *akbuf_table_entry_find(akbuf_table *, unsigned char *); +akbuf *akbuf_table_entry_get(akbuf_table *, unsigned char *); +unsigned char *akbuf_table_entry_get_str(akbuf_table *, unsigned char *); + +void akbuf_urlencode_data(unsigned char *, AKsize_t, akbuf *); +void akbuf_base64encode_data(unsigned char *, AKsize_t, akbuf *); + +#define akbuf_free_ctx(ctx) _akbuf_free_ctx((ctx), __FILE__, __LINE__) +#define akbuf_idx(buf) ((buf)->idx) +#define akbuf_size(buf) ((buf)->size) +#define akbuf_data(buf) ((buf)->head) +#define akbuf_empty(buf) (akbuf_idx(buf) == 0) + +#define akbuf_strcpy(buf, str) akbuf_set_data((buf), (str), strlen(str)) +#define akbuf_append_str(buf, str) akbuf_append_data((buf), (str), strlen(str)) +#define akbuf_clone(buf, src) akbuf_set_data((buf), akbuf_data(src), akbuf_idx(src)) +#define akbuf_append_buf(buf, src) akbuf_append_data((buf), akbuf_data(src), akbuf_idx(src)) + +#define akbuf_urlencode(buf, out) akbuf_urlencode(akbuf_data(buf), akbuf_idx(buf), (out)) + +#define akbuf_split(buf, dest, idx)\ + {\ + akbuf_set_data((dest), akbuf_data((buf)), (idx));\ + akbuf_consume((buf), (idx) + 1);\ + } + +#define akbuf_set_byte(buf, idx, byte)\ + {\ + AKbuf_ASSERT((unsigned int)(idx) < akbuf_idx(buf) && (unsigned int)(idx) < akbuf_size(buf));\ + *(akbuf_data(buf) + (idx)) = (byte);\ + } +#define akbuf_consume_end(buf, len)\ + {\ + AKbuf_ASSERT((unsigned int)(len) <= akbuf_idx(buf));\ + akbuf_idx(buf) -= (len);\ + } +#define akbuf_set_idx(buf, idx)\ + {\ + AKbuf_ASSERT((unsigned int)(idx) <= akbuf_size(buf));\ + akbuf_idx(buf) = (idx);\ + } +#define akbuf_get_byte(buf, idx, byte)\ + {\ + AKbuf_ASSERT((unsigned int)(idx) < akbuf_idx(buf));\ + (byte) = *(akbuf_data(buf) + (idx));\ + } diff --git a/asio/AKepoll.h b/asio/AKepoll.h new file mode 100644 index 0000000..1af44fd --- /dev/null +++ b/asio/AKepoll.h @@ -0,0 +1,3 @@ +int AKepoll_create(int); +int AKepoll_ctl(int, int, int, struct epoll_event *); +int AKepoll_wait(int, struct epoll_event *, int, int); diff --git a/asio/AKepoll.s b/asio/AKepoll.s new file mode 100644 index 0000000..3441a2d --- /dev/null +++ b/asio/AKepoll.s @@ -0,0 +1,60 @@ +.globl AKepoll_create +.globl AKepoll_ctl +.globl AKepoll_wait +AKepoll_create: + pushl %ebp + movl %esp,%ebp + addl $4,%ebp + pushl %ebx + + movl 4(%ebp),%ebx #size + movl $254,%eax #__NR_epoll_create + int $0x80 + + popl %ebx + popl %ebp + ret +AKepoll_ctl: + pushl %ebp + movl %esp,%ebp + addl $4,%ebp + pushl %ebx + pushl %ecx + pushl %edx + pushl %esi + + movl 4(%ebp),%ebx #epfd + movl 8(%ebp),%ecx #op + movl 12(%ebp),%edx #fd + movl 16(%ebp),%esi #event + movl $255,%eax #__NR_epoll_ctl + int $0x80 + + popl %esi + popl %edx + popl %ecx + popl %ebx + popl %ebp + ret +AKepoll_wait: + pushl %ebp + movl %esp,%ebp + addl $4,%ebp + pushl %ebx + pushl %ecx + pushl %edx + pushl %esi + + movl 4(%ebp),%ebx #epfd + movl 8(%ebp),%ecx #events + movl 12(%ebp),%edx #maxevents + movl 16(%ebp),%esi #timeout + movl $256,%eax #__NR_epoll_wait + int $0x80 + + popl %esi + popl %edx + popl %ecx + popl %ebx + popl %ebp + ret diff --git a/asio/Makefile b/asio/Makefile new file mode 100644 index 0000000..45ed06c --- /dev/null +++ b/asio/Makefile @@ -0,0 +1,6 @@ +CC=gcc +CCFLAGS=-Werror -ggdb #-pg + +all: asio.o +asio.o: asio.c asio.h + $(CC) $(CCFLAGS) -c asio.c diff --git a/asio/asio.c b/asio/asio.c new file mode 100644 index 0000000..c39aa6b --- /dev/null +++ b/asio/asio.c @@ -0,0 +1,352 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "asio.h" + +#ifdef ASIO_USE_SELECT +#include +#endif +#ifdef ASIO_USE_EPOLL +#include +#endif +#ifdef ASIO_USE_POLL +#include +#endif +#ifdef ASIO_USE_RTSIG +#ifndef __USE_GNU +#define __USE_GNU +#endif +#include +#include +#include +#endif + +#if !defined(ASIO_USE_SELECT) && !defined(ASIO_USE_EPOLL) && !defined(ASIO_USE_POLL) && !defined(ASIO_USE_RTSIG) +#error You must define one of ASIO_USE_SELECT, ASIO_USE_EPOLL, ASIO_USE_POLL, and ASIO_USE_RTSIG +#endif + +#if defined(ASIO_USE_FIONBIO) && defined(ASIO_USE_RTSIG) +#error You cannot defined both ASIO_USE_FIONBIO and ASIO_USE_RTSIG +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b))? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) +#endif + +#ifdef ASIO_USE_SELECT +fd_set asio_rfds, asio_wfds; +int highest_rfd, highest_wfd; +#endif + +#ifdef ASIO_USE_EPOLL +int epoll_fd; + + +#ifdef ASIO_USE_AKEPOLL +#include "AKepoll.h" +#else + +#define AKepoll_create epoll_create +#define AKepoll_ctl epoll_ctl +#define AKepoll_wait epoll_wait +#endif +#endif + +#ifdef ASIO_USE_POLL + struct pollfd pollfds[ASIO_MAX_FDS]; + unsigned int num_pollfds; +#endif + +void asio_init(void) +{ +#ifdef ASIO_USE_RTSIG + sigset_t sigs; +#endif + +#ifdef ASIO_USE_SELECT + FD_ZERO(&asio_rfds); + FD_ZERO(&asio_wfds); + highest_rfd = highest_wfd = -1; +#endif +#ifdef ASIO_USE_EPOLL + if ((epoll_fd = AKepoll_create(ASIO_MAX_FDS)) == -1) { perror("epoll_create()"); exit(1); } +#endif +#ifdef ASIO_USE_POLL + num_pollfds = 0; +#endif +#ifdef ASIO_USE_RTSIG + sigemptyset(&sigs); + sigaddset(&sigs, SIGRTMIN + 1); + sigprocmask(SIG_BLOCK, &sigs, NULL); +#endif +} +int asio_add_fd(int fd, asio_event_type events) +{ +#ifdef ASIO_USE_EPOLL + struct epoll_event ev; +#endif +#ifdef ASIO_USE_POLL + unsigned int i; +#endif +#ifdef ASIO_USE_RTSIG + int flags; +#endif +#ifdef ASIO_USE_FIONBIO + int flags; +#endif + if (fd < 0 || fd >= ASIO_MAX_FDS) return -1; +#ifdef ASIO_USE_SELECT + if (events & ASIO_R) + { + FD_SET(fd, &asio_rfds); + if (fd > highest_rfd) highest_rfd = fd; + } + if (events & ASIO_W) + { + FD_SET(fd, &asio_wfds); + if (fd > highest_wfd) highest_wfd = fd; + } +#endif +#ifdef ASIO_USE_EPOLL + ev.data.fd = fd; + ev.events = 0; + if (events & ASIO_R) ev.events |= EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP; + if (events & ASIO_W) ev.events |= EPOLLOUT | EPOLLERR | EPOLLHUP; + if (AKepoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) != 0) { perror("epoll_ctl()"); return -1; } +#endif +#ifdef ASIO_USE_POLL + if (num_pollfds >= ASIO_MAX_FDS) return -1; + for (i = 0; i < num_pollfds; i ++) if (pollfds[i].fd == fd || pollfds[i].events == 0) break; + pollfds[i].fd = fd; + pollfds[i].revents = 0; + pollfds[i].events = 0; + if (events & ASIO_R) pollfds[i].events |= POLLIN | POLLPRI | POLLERR | POLLHUP; + if (events & ASIO_W) pollfds[i].events |= POLLOUT | POLLERR | POLLHUP; + if (i == num_pollfds) num_pollfds ++; +#endif +#ifdef ASIO_USE_RTSIG + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { perror("fcntl() F_GETFL"); return -1; } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_ASYNC | O_RDWR) < 0) { perror("fcntl() F_SETFL"); return -1; } + if (fcntl(fd, F_SETSIG, SIGRTMIN + 1) != 0) { perror("fcntl() F_SETSIG"); return -1; } + if (fcntl(fd, F_SETOWN, getpid()) != 0) { perror("fcntl() F_SETOWN"); return -1; } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_ASYNC | O_RDWR) < 0) { perror("fcntl() F_SETFL 2"); return -1; } +#endif +#ifdef ASIO_USE_FIONBIO + flags = 1; + if (ioctl(fd, FIONBIO, &flags) != 0) { perror("ioctl()"); return -1; } +#endif + return 0; +} +int asio_set_events(int fd, asio_event_type events) +{ +#ifdef ASIO_USE_EPOLL + struct epoll_event ev; +#endif +#ifdef ASIO_USE_POLL + unsigned int i; +#endif + +#ifdef ASIO_USE_SELECT + return asio_add_fd(fd, events); +#endif +#ifdef ASIO_USE_EPOLL + ev.data.fd = fd; + ev.events = 0; + if (events & ASIO_R) ev.events |= EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP; + if (events & ASIO_W) ev.events |= EPOLLOUT | EPOLLERR | EPOLLHUP; + if (AKepoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev) != 0) { perror("epoll_ctl()"); return -1; } + return 0; +#endif +#ifdef ASIO_USE_POLL + for (i = 0; i < num_pollfds; i ++) if (pollfds[i].fd == fd) break; + if (i == num_pollfds) return -1; + pollfds[i].events = 0; + if (events & ASIO_R) pollfds[i].events |= POLLIN | POLLPRI | POLLERR | POLLHUP; + if (events & ASIO_W) pollfds[i].events |= POLLOUT | POLLERR | POLLHUP; + return 0; +#endif +#ifdef ASIO_USE_RTSIG + return 0; /*XXX*/ +#endif +} +int asio_del_fd(int fd, asio_event_type events) +{ +#ifdef ASIO_USE_EPOLL + struct epoll_event ev; +#endif +#ifdef ASIO_USE_POLL + unsigned int i; +#endif + + if (fd < 0 || fd >= ASIO_MAX_FDS) return -1; +#ifdef ASIO_USE_SELECT + if (FD_ISSET(fd, &asio_rfds) && fd == highest_rfd) highest_rfd --; + FD_CLR(fd, &asio_rfds); + if (FD_ISSET(fd, &asio_wfds) && fd == highest_wfd) highest_wfd --; + FD_CLR(fd, &asio_wfds); +#endif +#ifdef ASIO_USE_EPOLL + ev.data.fd = fd; + ev.events = 0; + if (events & ASIO_R) ev.events |= EPOLLIN | EPOLLPRI; + if (events & ASIO_W) ev.events |= EPOLLOUT; + AKepoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev); +#endif +#ifdef ASIO_USE_POLL + for (i = 0; i < num_pollfds; i ++) if (pollfds[i].fd == fd) break; + if (i == num_pollfds) return - 1; + pollfds[i].fd = -1; + pollfds[i].events = 0; + if (num_pollfds - 1 == i) num_pollfds --; +#endif + return 0; +} + +asio_event_list *asio_wait_for_events(void) +{ + static asio_event_list ret; +#ifdef ASIO_USE_SELECT + fd_set event_rfds, event_wfds; + struct timeval tv; + int i; +#endif +#ifdef ASIO_USE_EPOLL + struct epoll_event epoll_events[ASIO_MAX_FDS]; + int i, j; +#endif +#ifdef ASIO_USE_POLL + int i, j; +#endif +#ifdef ASIO_USE_RTSIG + sigset_t sigs; + siginfo_t sigi; + struct timespec ts; + int sig; +#endif + + ret.num_events = 0; +#ifdef ASIO_USE_SELECT + tv.tv_sec = 1; + tv.tv_usec = 0; + event_rfds = asio_rfds; + event_wfds = asio_wfds; + if (select(MAX(highest_rfd, highest_wfd) + 1, &event_rfds, &event_wfds, NULL, &tv) > 0) + { + for (i = 0; i <= highest_rfd; i ++) if (FD_ISSET(i, &event_rfds)) + { + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].event = ASIO_R; + ret.events[ret.num_events].fd = i; + ret.num_events ++; + } + for (i = 0; i <= highest_wfd; i ++) if (FD_ISSET(i, &event_wfds)) + { + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].event = ASIO_W; + ret.events[ret.num_events].fd = i; + ret.num_events ++; + } + } +#endif +#ifdef ASIO_USE_EPOLL + if ((j = AKepoll_wait(epoll_fd, epoll_events, ASIO_MAX_FDS, 1000)) < 0) + { + return &ret; /*XXX*/ + } + if (j > 0) + { + for (i = 0; i < j; i ++) + { + if (epoll_events[i].events & (EPOLLERR | EPOLLHUP)) + { + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].event = ASIO_R | ASIO_W; + ret.events[ret.num_events].fd = epoll_events[i].data.fd; + ret.num_events ++; + } else + { + if (epoll_events[i].events & (EPOLLIN | EPOLLPRI)) + { + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].event = ASIO_R; + ret.events[ret.num_events].fd = epoll_events[i].data.fd; + ret.num_events ++; + } + if (epoll_events[i].events & EPOLLOUT) + { + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].event = ASIO_W; + ret.events[ret.num_events].fd = epoll_events[i].data.fd; + ret.num_events ++; + } + } + } + } +#endif +#ifdef ASIO_USE_POLL + if ((j = poll(pollfds, num_pollfds, 1000)) < 0) + { + if (errno == EINTR || errno == EAGAIN) return &ret; + perror("poll()"); return NULL; + } + for (i = 0; i < num_pollfds; i ++) + { + if (pollfds[i].revents & (POLLERR | POLLHUP) && pollfds[i].events & (POLLIN | POLLOUT)) + { + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].fd = pollfds[i].fd; + ret.events[ret.num_events].event = 0; + if (pollfds[i].events & POLLIN) ret.events[ret.num_events].event |= ASIO_R; + if (pollfds[i].events & POLLOUT) ret.events[ret.num_events].event |= ASIO_W; + ret.num_events ++; + } else + { + if (pollfds[i].revents & (POLLIN | POLLPRI)) + { + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].event = ASIO_R; + ret.events[ret.num_events].fd = pollfds[i].fd; + ret.num_events ++; + } + if (pollfds[i].revents & POLLOUT) + { + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].event = ASIO_W; + ret.events[ret.num_events].fd = pollfds[i].fd; + ret.num_events ++; + } + } + } +#endif +#ifdef ASIO_USE_RTSIG + sigemptyset(&sigs); + sigaddset(&sigs, SIGRTMIN + 1); + memset(&sigi, 0, sizeof(sigi)); + ts.tv_sec = 1; + ts.tv_nsec = 0; + if ((sig = sigtimedwait(&sigs, &sigi, &ts)) == SIGRTMIN + 1) + { + //if (sigi.si_band & POLLIN) printf("fd %d event IN\n", sigi.si_fd); + //if (sigi.si_band & POLLOUT) printf("fd %d event OUT\n", sigi.si_fd); + assert(ret.num_events < ASIO_MAX_FDS); + ret.events[ret.num_events].fd = sigi.si_fd; + ret.events[ret.num_events].event = 0; + if (sigi.si_band & POLLIN) ret.events[ret.num_events].event |= ASIO_R; + if (sigi.si_band & POLLOUT) ret.events[ret.num_events].event |= ASIO_W; + if (sigi.si_band & (POLLERR | POLLHUP)) ret.events[ret.num_events].event |= ASIO_R | ASIO_W; /*XXX*/ + ret.num_events ++; + } +#endif + return &ret; +} diff --git a/asio/asio.h b/asio/asio.h new file mode 100644 index 0000000..5574a7b --- /dev/null +++ b/asio/asio.h @@ -0,0 +1,47 @@ +/* Use vanilla select()? */ +//#define ASIO_USE_SELECT + +/* Use poll()? */ +//#define ASIO_USE_POLL + +/* Use RTSIGs? (experimental, use at your own risk, works somewhat on Linux atleast) */ +//#define ASIO_USE_RTSIG + +/* Use epoll? (Works well (Linux >= 2.6), read README) */ +#define ASIO_USE_EPOLL + +/* Set non-blocking IO using FIONBIO (You will want to define this unless you have ASIO_USE_SIGIO) */ +#define ASIO_USE_FIONBIO + +#ifdef ASIO_USE_SELECT +#define ASIO_MAX_FDS FD_SETSIZE +#else +#define ASIO_MAX_FDS 131072 +#endif + +#define ASIO_R 0x1 +#define ASIO_W 0x2 + +typedef unsigned int asio_event_type; + +struct asio_event_entry_s +{ + asio_event_type event; + int fd; +}; + +typedef struct asio_event_entry_s asio_event_entry; + +struct asio_event_list_s +{ + unsigned int num_events; + asio_event_entry events[ASIO_MAX_FDS]; +}; + +typedef struct asio_event_list_s asio_event_list; + +void asio_init(void); +int asio_add_fd(int, asio_event_type); +int asio_set_events(int, asio_event_type); +int asio_del_fd(int, asio_event_type); +asio_event_list *asio_wait_for_events(void); diff --git a/cfg.c b/cfg.c new file mode 100644 index 0000000..a68c676 --- /dev/null +++ b/cfg.c @@ -0,0 +1,261 @@ +#include "hypercube.h" + +static akbuf_ctxh cfg_ctx; +FILE *cfg_f = NULL; + +hypercube_config cfg; + +enum { CFG_NONE, CFG_BOOL, CFG_INT, CFG_STR, CFG_TABLE, CFG_INADDR, CFG_REGEX_TABLE, CFG_FUNC }; + +int cfg_func_include(unsigned char *, unsigned int, unsigned char **, unsigned int); +int cfg_func_echo(unsigned char *, unsigned int, unsigned char **, unsigned int); + +struct hypercube_config_token +{ + unsigned char *key; + unsigned int type; + void *val; +} hypercube_config_tokens[] = +{ + { "listen_port", CFG_INT, &cfg.listen_port }, + { "listen_addr", CFG_INADDR, &cfg.listen_addr }, + { "default_root", CFG_STR, &cfg.default_root }, + { "log", CFG_BOOL, &cfg.log }, + { "log_level", CFG_STR, &cfg.log_level }, + { "log_file", CFG_STR, &cfg.log_file }, + { "background", CFG_BOOL, &cfg.background }, + { "run_as_uid", CFG_INT, &cfg.run_as_uid }, + { "run_as_gid", CFG_INT, &cfg.run_as_gid }, + { "chroot_dir", CFG_STR, &cfg.chroot_dir }, + { "vhost", CFG_TABLE, &cfg.vhosts }, + { "mime", CFG_TABLE, &cfg.mime_types }, + { "rewrite", CFG_REGEX_TABLE, &cfg.rewrite_rules }, + { "include", CFG_FUNC, cfg_func_include }, + { "echo", CFG_FUNC, cfg_func_echo }, + { "allow", CFG_REGEX_TABLE, &cfg.allow_clients }, + { "deny", CFG_REGEX_TABLE, &cfg.deny_clients }, + + /* tracker */ + { "tracker_interval", CFG_INT, &cfg.tracker.interval }, + { "tracker_init_interval", CFG_INT, &cfg.tracker.init_interval }, + { "tracker_timeout", CFG_INT, &cfg.tracker.timeout }, + { "tracker_stopped_timeout", CFG_INT, &cfg.tracker.stopped_timeout }, + { "tracker_respnum", CFG_INT, &cfg.tracker.respnum }, + { "tracker_period", CFG_INT, &cfg.tracker.period }, + { "tracker_sql_stats", CFG_BOOL, &cfg.tracker.sql_stats }, + { "tracker_sql_host", CFG_STR, &cfg.tracker.sql_host }, + { "tracker_sql_db", CFG_STR, &cfg.tracker.sql_db }, + { "tracker_sql_user", CFG_STR, &cfg.tracker.sql_user }, + { "tracker_sql_pass", CFG_STR, &cfg.tracker.sql_pass }, + { "tracker_statslog", CFG_STR, &cfg.tracker.statslog }, + { "tracker_sync", CFG_BOOL, &cfg.tracker.sync }, + { "tracker_sync_interval", CFG_INT, &cfg.tracker.sync_interval }, + { "tracker_sync_size", CFG_INT, &cfg.tracker.sync_size }, + { "tracker_sync_addr", CFG_INADDR, &cfg.tracker.sync_addr }, + { "tracker_sync_port", CFG_INT, &cfg.tracker.sync_port }, + { "tracker_infohash_file", CFG_STR, &cfg.tracker.infohash_file }, + { "tracker_infohash_interval",CFG_INT, &cfg.tracker.infohash_interval }, + { NULL, CFG_NONE, NULL }, +}; + +#define CFG_ERR_HDR() fprintf(stderr, "Error in configuration '%s' on line %u:\n ", filename, curline); + +int cfg_load(unsigned char *filename, unsigned int level) +{ + FILE *f; + unsigned char readbuf[BUF_SIZE], *p; +#define MAX_CFG_TOKENS 16 + unsigned char *tokenv[MAX_CFG_TOKENS]; + unsigned int tokenc, i; + unsigned int curline; + unsigned int bool_val; + + curline = 0; + + if (level == 0) + { + if (cfg_f == NULL || fseek(cfg_f, 0, SEEK_SET) != 0) + { + if ((f = fopen(filename, "r")) == NULL) { fprintf(stderr, "Unable to open config file '%s': %s\n", filename, strerror(errno)); return -1; } + cfg_f = f; + } else + { + f = cfg_f; + } + } else + { + if ((f = fopen(filename, "r")) == NULL) { fprintf(stderr, "Unable to open config file '%s': %s\n", filename, strerror(errno)); return -1; } + } + while (fgets(readbuf, sizeof(readbuf) - 1, f) != NULL) + { + curline ++; + if ((p = strpbrk(readbuf, "\n\r")) != NULL) *p = 0; + tokenc = 0; + p = readbuf; + while (*p == ' ' || *p == '\t') p ++; + if (*p == '#') continue; + bool_val = 1; + DEBUGF("cfg line %s:%u [%s]", filename, curline, p); + if (*p == 0) continue; + p = strtok(p, " \t"); + while (tokenc < MAX_CFG_TOKENS && p != NULL && *p != '#') + { + /* handle !foo / no foo */ + if (tokenc == 0) + { + if (*p == '!') { bool_val = 0; p ++; } + else if (strcasecmp(p, "no") == 0) { bool_val = 0; p = NULL; } + } + if (p != NULL && *p != 0) tokenv[tokenc ++] = strdup(p); + p = strtok(NULL, " \t"); + } + if (tokenc == 0) continue; + for (i = 0; hypercube_config_tokens[i].key != NULL; i ++) if (strcasecmp(hypercube_config_tokens[i].key, tokenv[0]) == 0) break; + if (hypercube_config_tokens[i].key == NULL) { CFG_ERR_HDR(); fprintf(stderr, "Unknown key '%s'\n", tokenv[0]); return -1; } + if (hypercube_config_tokens[i].type == CFG_BOOL) + { + if (tokenc != 1) { CFG_ERR_HDR(); fprintf(stderr, "Invalid number of arguments (expected [!]%s)\n", tokenv[0]); return -1; } + DEBUGF("bool value '%s' = %u", hypercube_config_tokens[i].key, bool_val); + *(int *)hypercube_config_tokens[i].val = bool_val; + } else if (bool_val != 0) switch (hypercube_config_tokens[i].type) + { + case CFG_NONE: break; + case CFG_INT: + { + if (tokenc != 2) { CFG_ERR_HDR(); fprintf(stderr, "Invalid number of arguments (expected %s )\n", tokenv[0]); return -1; } + *(int *)hypercube_config_tokens[i].val = atoi(tokenv[1]); + break; + } + case CFG_STR: + { + unsigned char **dest; + + if (tokenc != 2) { CFG_ERR_HDR(); fprintf(stderr, "Invalid number of arguments (expected %s )\n", tokenv[0]); return -1; } + dest = hypercube_config_tokens[i].val; + *dest = strdup(tokenv[1]); + break; + } + case CFG_TABLE: + { + akbuf_table *tbl; + unsigned char **dest; + + if (tokenc != 3) { CFG_ERR_HDR(); fprintf(stderr, "Invalid number of arguments (expected %s )\n", tokenv[0]); return -1; } + dest = hypercube_config_tokens[i].val; + tbl = (akbuf_table *)*dest; + akbuf_table_entry_add_str(cfg_ctx, tbl, tokenv[1], tokenv[2]); + break; + } + case CFG_REGEX_TABLE: + { + akbuf_table *tbl; + unsigned char **dest; + akbuf *compbuf, *valbuf; + int ret; + + if (tokenc != 2 && tokenc != 3) { CFG_ERR_HDR(); fprintf(stderr, "Invalid number of arguments (expected %s [arg])\n", tokenv[0]); return -1; } + compbuf = akbuf_init(cfg_ctx, sizeof(regex_t)); + if ((ret = regcomp((regex_t *)akbuf_data(compbuf), tokenv[1], REG_EXTENDED)) != 0) + { + unsigned char errbuf[BUF_SIZE]; + + regerror(ret, (regex_t *)akbuf_data(compbuf), errbuf, sizeof(errbuf)); + fprintf(stderr, "Parsing of regex '%s' failed: %s\n", tokenv[1], errbuf); + return -1; + } + akbuf_set_idx(compbuf, sizeof(regex_t)); + valbuf = akbuf_init(cfg_ctx, 0); + akbuf_strcpy(valbuf, tokenv[2]); + dest = hypercube_config_tokens[i].val; + tbl = (akbuf_table *)*dest; + akbuf_table_entry_add_buf(cfg_ctx, tbl, compbuf, valbuf); + akbuf_free(cfg_ctx, valbuf); + break; + } + case CFG_INADDR: + { + struct in_addr *addr; + + if (tokenc != 2) { CFG_ERR_HDR(); fprintf(stderr, "Invalid number of arguments (expected %s )\n", tokenv[0]); return -1; } + addr = hypercube_config_tokens[i].val; + if ((addr->s_addr = inet_addr(tokenv[1])) == -1) { CFG_ERR_HDR(); fprintf(stderr, "Invalid IP address '%s'\n", tokenv[1]); return -1; } + break; + } + case CFG_FUNC: + { + int (*func)(); + func = hypercube_config_tokens[i].val; + if (func != NULL) if (func(filename, curline, tokenv, tokenc) != 0) return -1; + break; + } + } + } + return 0; +} + +int cfg_func_include(unsigned char *filename, unsigned int curline, unsigned char **tokenv, unsigned int tokenc) +{ + if (tokenc != 2) + { + CFG_ERR_HDR(); + fprintf(stderr, "Invalid number of arguments (expected %s )\n", tokenv[0]); + return -1; + } + return cfg_load(tokenv[1], 1); +} +int cfg_func_echo(unsigned char *filename, unsigned int curline, unsigned char **tokenv, unsigned int tokenc) +{ + unsigned char *msg; + + if (tokenc < 2) msg = ""; else msg = tokenv[1]; + printf("%s:%u: %s\n", filename, curline, msg); + return 0; +} + +void cfg_init(void) +{ + cfg_ctx = akbuf_new_ctx(); + cfg.listen_port = 8000; + cfg.listen_addr.s_addr = INADDR_ANY; + cfg.default_root = "dox/"; + cfg.log = 1; + cfg.log_level = "request"; + cfg.log_file = NULL; + cfg.background = 0; + cfg.run_as_uid = cfg.run_as_gid = -1; + cfg.chroot_dir = NULL; + cfg.tracker.statslog = NULL; + + cfg.vhosts = akbuf_table_init(cfg_ctx, AKBUF_TABLE_NOCASE); + cfg.mime_types = akbuf_table_init(cfg_ctx, AKBUF_TABLE_NOCASE); + cfg.rewrite_rules = akbuf_table_init(cfg_ctx, AKBUF_TABLE_BIN); + cfg.allow_clients = akbuf_table_init(cfg_ctx, AKBUF_TABLE_BIN); + cfg.deny_clients = akbuf_table_init(cfg_ctx, AKBUF_TABLE_BIN); + + /* tracker */ + cfg.tracker.interval = 360; + cfg.tracker.timeout = 360 * 2; + cfg.tracker.stopped_timeout = 300 * 2; + cfg.tracker.respnum = 50; + cfg.tracker.period = 15; + cfg.tracker.sql_stats = 0; + cfg.tracker.sql_host = NULL; + cfg.tracker.sql_db = NULL; + cfg.tracker.sql_user = NULL; + cfg.tracker.sql_pass = NULL; + cfg.tracker.sync = 0; + cfg.tracker.sync_interval = 15; + cfg.tracker.sync_size = 1400; + cfg.tracker.sync_addr.s_addr = INADDR_BROADCAST; + cfg.tracker.sync_port = 4242; + cfg.tracker.infohash_file = NULL; + cfg.tracker.infohash_interval = 30; + + if (cfg_load(CFG_FILE, 0) != 0) exit(1); +} + +void cfg_reload(void) +{ + akbuf_free_ctx(cfg_ctx); + cfg_init(); +} diff --git a/cfg.h b/cfg.h new file mode 100644 index 0000000..f11a47c --- /dev/null +++ b/cfg.h @@ -0,0 +1,49 @@ +typedef struct tracker_config_s tracker_config; +struct tracker_config_s +{ + unsigned int interval; + unsigned int init_interval; + unsigned int timeout; + unsigned int stopped_timeout; + unsigned int respnum; + unsigned int period; + unsigned char *statslog; + unsigned int sql_stats; + unsigned char *sql_host; + unsigned char *sql_db; + unsigned char *sql_user; + unsigned char *sql_pass; + unsigned int sync; + unsigned int sync_interval; + unsigned int sync_size; + struct in_addr sync_addr; + unsigned int sync_port; + unsigned char *infohash_file; + unsigned int infohash_interval; +}; + +typedef struct hypercube_config_s hypercube_config; + +struct hypercube_config_s +{ + int listen_port; + struct in_addr listen_addr; + unsigned char *default_root; + unsigned int log; + unsigned char *log_level; + unsigned char *log_file; + unsigned int background; + int run_as_uid; + int run_as_gid; + unsigned char *chroot_dir; + akbuf_table *vhosts; + akbuf_table *mime_types; + akbuf_table *rewrite_rules; + akbuf_table *allow_clients; + akbuf_table *deny_clients; + tracker_config tracker; +}; + +int cfg_load(unsigned char *, unsigned int); +void cfg_init(void); +void cfg_reload(void); diff --git a/config.h.dist b/config.h.dist new file mode 100644 index 0000000..afe8bff --- /dev/null +++ b/config.h.dist @@ -0,0 +1,21 @@ +//#define DEBUG + +#define CFG_FILE "hypercube.cfg" + +#define LISTEN_BACKLOG 65535 +#define SOCKET_TIMEOUT 15 +#define MAX_LINE_LEN (BUF_SIZE * 2) +#define BUF_SIZE 4096 +#define SEND_BUF_SIZE 16384 +#define HTTP_MAX_HEADERS 16 +#define HTTP_MAX_CONTENT_LEN 32768 +#define CGI_MIME_TYPE "application/x-cgi" +#define CGI_DEFAULT_PATH "/usr/local/bin:/usr/xpg4/bin:/usr/ccs/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb/bin" +#define GZIP_SUFFIX "HCgz" + +/* +#define NUM_RESP_PEERS 50 +#define ANNOUNCE_INTERVAL 420 +#define PEER_TIMEOUT (ANNOUNCE_INTERVAL * 2) +#define TRACKER_PERIOD_INTERVAL 15 +*/ diff --git a/http.c b/http.c new file mode 100644 index 0000000..2225999 --- /dev/null +++ b/http.c @@ -0,0 +1,970 @@ +#include "hypercube.h" + +http_fd_entry http_fds[ASIO_MAX_FDS]; + +static akbuf_ctxh http_ctx; +static akbuf *parsebufs[8]; + +extern hypercube_config cfg; + +void http_init(void) +{ + unsigned int i; + + http_ctx = akbuf_new_ctx(); + for (i = 0; i < sizeof(parsebufs) / sizeof(parsebufs[0]); i ++) parsebufs[i] = akbuf_init(http_ctx, 0); + for (i = 0; i < ASIO_MAX_FDS; i ++) http_fds[i].state = HTTP_FD_UNUSED; +} +void http_unset_fd(int fd) +{ + if (http_fds[fd].state != HTTP_FD_UNUSED) + { + if (http_fds[fd].state != HTTP_FD_CGI) aklogf(LOG_CONNECTION, "Client on socket %d disconnected.", fd); + http_fds[fd].state = HTTP_FD_UNUSED; + } +} +void http_handle_sent(int fd, net_fd_entry *net_ent) +{ + DEBUGF("send on socket %d done", fd); + if (http_fds[fd].keep_alive == 0) + { + net_unset_fd(fd); + } else + { + http_fds[fd].state = HTTP_FD_NEW_REQ; + } +} +unsigned char *http_status_msg(unsigned int code) +{ + switch (code) + { + case HTTP_OK: return "OK"; + case HTTP_PARTIAL_CONTENT: return "Partial content"; + case HTTP_MOVED_PERM: return "Moved permanently"; + case HTTP_BAD_REQUEST: return "Bad request"; + case HTTP_FORBIDDEN: return "Forbidden"; + case HTTP_NOT_FOUND: return "Not found"; + case HTTP_INTERNAL_ERROR: return "Internal error"; + case HTTP_SERVER_TOO_BUSY: return "Server too busy"; + case HTTP_LOG_HOOK: return "Handled by hook"; + case HTTP_FORBIDDEN_BANNED: return "Forbidden (client banned)"; + } + return "Unspecified"; +} +unsigned char *http_method_str(unsigned int method) +{ + switch (method) + { + case HTTP_METHOD_GET: return "GET"; + case HTTP_METHOD_HEAD: return "HEAD"; + case HTTP_METHOD_POST: return "POST"; + } + return "(none)"; +} +void http_log_query(int fd, net_fd_entry *net_ent, unsigned int code) +{ + unsigned char *ua_p, *ref_p, *host_p; + + if ((host_p = akbuf_table_entry_get_str(http_fds[fd].headers, "Host")) == NULL) host_p = "(none)"; + if ((ua_p = akbuf_table_entry_get_str(http_fds[fd].headers, "User-agent")) == NULL) ua_p = "(none)"; + if ((ref_p = akbuf_table_entry_get_str(http_fds[fd].headers, "Referer")) == NULL) ref_p = "(none)"; + akbuf_asciiz(net_ent->peerbuf); + akbuf_asciiz(http_fds[fd].uri); + aklogf(LOG_REQUEST, "HTTP/%u.%u %d %s %s \"%s\" %u \"%s\" \"%s\" \"%s\" \"%s\"", + http_fds[fd].ver_maj, http_fds[fd].ver_min, + fd, + akbuf_data(net_ent->peerbuf), + http_method_str(http_fds[fd].method), akbuf_data(http_fds[fd].uri), + (code == HTTP_LOG_HOOK)? HTTP_OK : code, http_status_msg(code), + host_p, ref_p, ua_p); +} +void http_error(int fd, net_fd_entry *net_ent, unsigned int code) +{ + unsigned char *msg; + + http_log_query(fd, net_ent, code); + msg = http_status_msg(code); + if (code >= 2000) code -= 2000; + http_fds[fd].keep_alive = 0; + akbuf_sprintf(net_ent->sendbuf, + "HTTP/1.1 %u %s\r\n" + "Server: " SERVER_VERSION "\r\n" + "Content-type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + "%u %s

%u %s

\r\n", + code, msg, + code, msg, code, msg); + net_send(fd); +} +unsigned char *get_mime_type(unsigned char *filename) +{ + unsigned int i; + unsigned char *p; + + i = strlen(filename); + while (i >= 1) { if (filename[i - 1] == '.') break; i --; } + if (i > 0) + { + DEBUGF("get_mime_type() extension %s", &filename[i]); + if ((p = akbuf_table_entry_get_str(cfg.mime_types, &filename[i])) != NULL) return p; + } + return "application/octet-stream"; +} +void cgi_handle_output(int fd, net_fd_entry *net_ent, akbuf *buf) +{ + if (http_fds[fd].rpipe_net_ent->type == HTTP_FD_UNUSED) + { + net_unset_fd(fd); + return; + } + akbuf_append_buf(http_fds[fd].rpipe_net_ent->sendbuf, buf); + net_send(http_fds[fd].rpipe_fd); +} +void cgi_handle_sent_content(int fd, net_fd_entry *net_ent, akbuf *buf) +{ + net_unset_fd(fd); +} +int serve_cgi_to_fd(int fd, net_fd_entry *net_ent, unsigned char *cgi_name) +{ + int cgi_rpipe[2], cgi_wpipe[2]; + /* parsebufs: 0,1,2 == env var tmp */ + unsigned int has_content; + + DEBUGF("serving cgi [%s]", cgi_name); + cgi_rpipe[0] = cgi_rpipe[1] = -1; + if (pipe(cgi_rpipe) != 0 || !FD_VALID(cgi_rpipe[0]) || !FD_VALID(cgi_rpipe[1])) + { + if (cgi_rpipe[0] != -1) close(cgi_rpipe[0]); + if (cgi_rpipe[1] != -1) close(cgi_rpipe[1]); + http_error(fd, net_ent, HTTP_SERVER_TOO_BUSY); + return -2; + } + if (http_fds[fd].method == HTTP_METHOD_POST && http_fds[fd].content_len > 0) + { + has_content = 1; + cgi_wpipe[0] = cgi_wpipe[1] = -1; + if (pipe(cgi_wpipe) != 0 || !FD_VALID(cgi_wpipe[0]) || !FD_VALID(cgi_wpipe[1])) + { + if (cgi_wpipe[0] != -1) close(cgi_wpipe[0]); + if (cgi_wpipe[1] != -1) close(cgi_wpipe[1]); + http_error(fd, net_ent, HTTP_SERVER_TOO_BUSY); + return -2; + } + } else + { + has_content = 0; + } + if (fork() == 0) + { + unsigned char *envs[64]; + unsigned int i; + int j; + + if (has_content == 1) + { + close(cgi_wpipe[1]); + dup2(cgi_wpipe[0], 0); + } + close(cgi_rpipe[0]); + dup2(cgi_rpipe[1], 1); + i = 0; + /* We can use strdup() here because the process will end anyway. */ + akbuf_sprintf(parsebufs[0], "GATEWAY_INTERFACE=CGI/1.1"); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + akbuf_asciiz(http_fds[fd].query); + akbuf_sprintf(parsebufs[0], "QUERY_STRING=%s", akbuf_data(http_fds[fd].query)); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + akbuf_sprintf(parsebufs[0], "PATH=" CGI_DEFAULT_PATH); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + akbuf_sprintf(parsebufs[0], "SERVER_SOFTWARE=" SERVER_VERSION); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + akbuf_sprintf(parsebufs[0], "SERVER_PROTOCOL=HTTP/%u.%u", http_fds[fd].ver_maj, http_fds[fd].ver_min); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + akbuf_clone(parsebufs[1], net_ent->peerbuf); + if ((j = akbuf_chr(parsebufs[1], ':')) != -1) + { + akbuf_split(parsebufs[1], parsebufs[2], j); + akbuf_asciiz(parsebufs[1]); + akbuf_sprintf(parsebufs[0], "REMOTE_ADDR=%s", akbuf_data(parsebufs[2])); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + akbuf_sprintf(parsebufs[0], "REMOTE_PORT=%s", akbuf_data(parsebufs[1])); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + } + akbuf_sprintf(parsebufs[0], "SCRIPT_FILENAME=%s", cgi_name); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + akbuf_sprintf(parsebufs[0], "REQUEST_METHOD=%s", http_method_str(http_fds[fd].method)); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + akbuf_asciiz(http_fds[fd].uri); + akbuf_sprintf(parsebufs[0], "REQUEST_URI=%s", akbuf_data(http_fds[fd].uri)); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + if (has_content == 1) + { + akbuf_sprintf(parsebufs[0], "CONTENT_LENGTH=%u", (unsigned int)http_fds[fd].content_len); + akbuf_asciiz(parsebufs[0]); + envs[i ++] = strdup(akbuf_data(parsebufs[0])); + } + envs[i] = NULL; + execle(cgi_name, cgi_name, NULL, envs); + akperror("execve() CGI script"); + exit(1); + } + if (has_content == 1) + { + close(cgi_wpipe[0]); + net_set_fd(cgi_wpipe[1], NET_FD_SEND, NULL, cgi_handle_sent_content, 1); + net_send_buf(cgi_wpipe[1], http_fds[fd].content); + http_fds[fd].wpipe_fd = cgi_wpipe[1]; + } + DEBUGF("wpipe_fd %d", http_fds[fd].wpipe_fd); + close(cgi_rpipe[1]); + net_set_fd(cgi_rpipe[0], NET_FD_READ, cgi_handle_output, NULL, 1); + http_fds[cgi_rpipe[0]].rpipe_fd = fd; + http_fds[cgi_rpipe[0]].rpipe_net_ent = net_ent; + http_fds[cgi_rpipe[0]].ctx = http_fds[fd].ctx; + http_fds[cgi_rpipe[0]].state = HTTP_FD_CGI; + http_fds[cgi_rpipe[0]].method = HTTP_METHOD_NONE; + http_fds[cgi_rpipe[0]].uri = NULL; + http_fds[cgi_rpipe[0]].query = NULL; + http_fds[cgi_rpipe[0]].ver_maj = http_fds[fd].ver_min = 0; + http_fds[cgi_rpipe[0]].keep_alive = 0; + http_fds[cgi_rpipe[0]].num_headers = 0; + http_fds[cgi_rpipe[0]].headers = NULL; + http_fds[cgi_rpipe[0]].num_args = 0; + http_fds[cgi_rpipe[0]].args = NULL; + http_fds[fd].state = HTTP_FD_PIPING; + http_fds[fd].rpipe_fd = cgi_rpipe[0]; + net_set_callbacks(fd, NULL, NULL); + akbuf_sprintf(net_ent->sendbuf, + "HTTP/1.1 %u OK\r\n" + "Server: " SERVER_VERSION "\r\n" + "Connection: close\r\n", + HTTP_OK); + net_send(fd); + return 0; +} +/* return: 0 == all ok; -1 == error, show errorpage; -2 == error, errorpage shown */ +int serve_doc_to_fd(int fd, net_fd_entry *net_ent, unsigned char *orig_doc_name, unsigned char *doc_name, unsigned int doc_off, AKsize_t doc_size, unsigned int add_gzip_header) +{ + unsigned char *mime_type; + unsigned char timebuf[512]; + unsigned int code; + struct stat st; + + DEBUGF("serve_doc_to_fd() orig [%s] real [%s]", orig_doc_name, doc_name); + mime_type = get_mime_type(orig_doc_name); + if (strcasecmp(mime_type, CGI_MIME_TYPE) == 0) + { + return serve_cgi_to_fd(fd, net_ent, orig_doc_name); + } + if (access(doc_name, R_OK) != 0) return -1; + http_log_query(fd, net_ent, HTTP_OK); + if (http_fds[fd].method != HTTP_METHOD_HEAD && (net_ent->send_fd = open(doc_name, O_RDONLY)) < 0) return -1; + if (doc_off >= doc_size) doc_off = (doc_size > 0)? doc_size - 1 : 0; + net_ent->send_fd_off = doc_off; + net_ent->send_fd_len = doc_size - doc_off; + code = (doc_off != 0)? HTTP_PARTIAL_CONTENT : HTTP_OK; + if (http_fds[fd].ver_maj > 0) + { + if (stat(doc_name, &st) == 0) + { + size_t len; + struct tm *tmp; + time_t t; + + t = time(NULL); + tmp = gmtime(&t); + if ((len = strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", tmp)) == 0) timebuf[0] = 0; + + } else + { + timebuf[0] = 0; + } + if (code == HTTP_PARTIAL_CONTENT) + { + akbuf_sprintf(net_ent->sendbuf, + "HTTP/1.1 %u %s\r\n" + "Server: " SERVER_VERSION "\r\n" + "Content-type: %s\r\n" + "Content-length: %u\r\n" + "Content-range: bytes %u-%u/%u\r\n" + "Connection: %s\r\n" + "Last-Modified: %s\r\n" + "\r\n", + code, http_status_msg(code), + get_mime_type(orig_doc_name), + doc_size - doc_off, + doc_off, (doc_size == 0)? 0 : doc_size - 1, doc_size, + (http_fds[fd].keep_alive == 1)? "keep-alive" : "close", + timebuf); + } else + { + if (add_gzip_header == 1) + { + akbuf_sprintf(net_ent->sendbuf, + "HTTP/1.1 %u %s\r\n" + "Server: " SERVER_VERSION "\r\n" + "Content-type: %s\r\n" + "Content-length: %u\r\n" + "Connection: %s\r\n" + "Last-Modified: %s\r\n" + "Content-Encoding: gzip\r\n" + "\r\n", + code, http_status_msg(code), + get_mime_type(orig_doc_name), + doc_size - doc_off, + (http_fds[fd].keep_alive == 1)? "keep-alive" : "close", + timebuf); + + } else + { + akbuf_sprintf(net_ent->sendbuf, + "HTTP/1.1 %u %s\r\n" + "Server: " SERVER_VERSION "\r\n" + "Content-type: %s\r\n" + "Content-length: %u\r\n" + "Connection: %s\r\n" + "Last-Modified: %s\r\n" + "\r\n", + code, http_status_msg(code), + get_mime_type(orig_doc_name), + doc_size - doc_off, + (http_fds[fd].keep_alive == 1)? "keep-alive" : "close", + timebuf); + } + } + } + net_send(fd); + return 0; +} +void http_dir_index(int fd, net_fd_entry *net_ent, unsigned char *dir_path) +{ + DIR *dir; + struct dirent *de; + struct stat st; + /* parsebufs: 4 == generated document, 5 == appended data */ + + if ((dir = opendir(dir_path)) == NULL) + { + http_error(fd, net_ent, (errno == EPERM)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); + return; + } + http_log_query(fd, net_ent, HTTP_OK); + akbuf_asciiz(http_fds[fd].uri); + akbuf_sprintf(parsebufs[4], + "\n" + "Index of %s\n" + "\n" + "

Index of %s

\n", + akbuf_data(http_fds[fd].uri), akbuf_data(http_fds[fd].uri)); + while ((de = readdir(dir)) != NULL) + { + akbuf_sprintf(parsebufs[5], "%s/%s", dir_path, de->d_name); + akbuf_asciiz(parsebufs[5]); + if (stat(akbuf_data(parsebufs[5]), &st) == 0) + { + akbuf_sprintf(parsebufs[5], "%s%s %u
\n", de->d_name, (st.st_mode & S_IFDIR)? "/" : "", de->d_name, (st.st_mode & S_IFDIR)? "/" : "", st.st_size); + akbuf_append_buf(parsebufs[4], parsebufs[5]); + } + } + closedir(dir); /* :-D */ + akbuf_sprintf(parsebufs[5], "\n"); + akbuf_append_buf(parsebufs[4], parsebufs[5]); + if (http_fds[fd].ver_maj > 0) + { + akbuf_sprintf(net_ent->sendbuf, + "HTTP/1.1 %u OK\r\n" + "Server: " SERVER_VERSION "\r\n" + "Content-type: text/html\r\n" + "Content-length: %u\r\n" + "Connection: %s\r\n" + "\r\n", + HTTP_OK, + akbuf_idx(parsebufs[4]), + (http_fds[fd].keep_alive == 1)? "keep-alive" : "close"); + } + akbuf_append_buf(net_ent->sendbuf, parsebufs[4]); +} +unsigned int http_has_access(akbuf *peerbuf, akbuf *uri) +{ + akbuf_table_entry *ent; + akbuf *addrbuf; + akbuf_ctxh ctx; + int i; + + ctx = akbuf_new_ctx(); + addrbuf = akbuf_init(ctx, 0); + akbuf_clone(addrbuf, peerbuf); + if ((i = akbuf_chr(addrbuf, ':')) != -1) akbuf_set_byte(addrbuf, i, 0); + ent = cfg.allow_clients->head; + while (ent != NULL) + { + if (regexec((regex_t *)akbuf_data(ent->key), akbuf_data(addrbuf), 0, NULL, 0) == 0) + { + akbuf_asciiz(ent->data); + akbuf_asciiz(uri); + if (strncmp(akbuf_data(uri), akbuf_data(ent->data), akbuf_idx(ent->data)) == 0) + { + DEBUGF("check_access match [%s] [%s]", akbuf_data(addrbuf), akbuf_data(uri)); + break; + } + } + ent = ent->next; + } + if (ent != NULL) return 1; + ent = cfg.deny_clients->head; + while (ent != NULL) + { + if (regexec((regex_t *)akbuf_data(ent->key), akbuf_data(addrbuf), 0, NULL, 0) == 0) + { + akbuf_asciiz(ent->data); + akbuf_asciiz(uri); + if (strncmp(akbuf_data(uri), akbuf_data(ent->data), akbuf_idx(ent->data)) == 0) + { + DEBUGF("check_access match [%s] [%s]", akbuf_data(addrbuf), akbuf_data(uri)); + break; + } + } + ent = ent->next; + } + akbuf_free_ctx(ctx); + return (ent == NULL)? 1 : 0; +} +void http_do_rewrite(akbuf *rbuf) +{ + akbuf_table_entry *ent; + unsigned int i, c, rem; + regmatch_t matches[10]; + /* parsebufs: 6 == saved original rbuf */ + + akbuf_asciiz(rbuf); + DEBUGF("doing rewrite on [%s]", akbuf_data(rbuf)); + ent = cfg.rewrite_rules->head; + while (ent != NULL) + { + if (regexec((regex_t *)akbuf_data(ent->key), akbuf_data(rbuf), sizeof(matches) / sizeof(matches[0]), matches, 0) == 0) + { + DEBUGF("rewrite regexec() match"); + akbuf_clone(parsebufs[6], rbuf); + akbuf_set_idx(rbuf, 0); + i = 0; + rem = akbuf_idx(ent->data); + while (rem > 0) + { + akbuf_get_byte(ent->data, i, c); i ++; rem --; + switch (c) + { + case '\\': + if (rem >= 1) + { + akbuf_get_byte(ent->data, i, c); i ++; rem --; + akbuf_append_byte(rbuf, c); + } else + { + aklogf(LOG_ERROR, "Error in rewrite rule: Stray \\ at end of line."); + } + break; + case '$': + if (rem >= 1) + { + akbuf_get_byte(ent->data, i, c) i ++; rem --; + c -= '0'; + if (c >= sizeof(matches) / sizeof(matches[0]) || matches[c].rm_so == -1) + { + aklogf(LOG_ERROR, "Erorr in rewrite rule: Unknown variable $%u.", c); + break; + } + AKassert(matches[c].rm_eo >= matches[c].rm_so); + akbuf_append_data(rbuf, akbuf_data(parsebufs[6]) + matches[c].rm_so, matches[c].rm_eo - matches[c].rm_so); + } + break; + default: + akbuf_append_byte(rbuf, c); + break; + } + } + return; + } else + { + DEBUGF("rewrite regexec() nomatch"); + } + ent = ent->next; + } +} +void http_index(int fd, net_fd_entry *net_ent, unsigned char *dir_path, unsigned int doc_off) +{ + unsigned int diridx; + unsigned int i; + struct stat st; + unsigned char *indexdocs[] = { "index.html", "index.htm", NULL }; + /* parsebufs: 3 == full path to try */ + + akbuf_strcpy(parsebufs[3], dir_path); + akbuf_append_byte(parsebufs[3], '/'); + diridx = akbuf_idx(parsebufs[3]); + i = 0; + while (indexdocs[i] != NULL) + { + akbuf_append_str(parsebufs[3], indexdocs[i]); + akbuf_asciiz(parsebufs[3]); + DEBUGF("trying index '%s'", akbuf_data(parsebufs[3])); + if (stat(akbuf_data(parsebufs[3]), &st) == 0 && st.st_mode & S_IFREG) + { + if (serve_doc_to_fd(fd, net_ent, akbuf_data(parsebufs[3]), akbuf_data(parsebufs[3]), doc_off, st.st_size, 0) == 0) break; + } + akbuf_set_idx(parsebufs[3], diridx); + i ++; + } + if (indexdocs[i] == NULL) http_dir_index(fd, net_ent, dir_path); +} +unsigned int http_parse_range(akbuf *range) +{ + int i; + unsigned int cur_val, c; + + /* XXX support for ranges other than bytes=###- */ + if ((i = akbuf_chr(range, '=')) == -1 || ++ i >= akbuf_idx(range)) return 0; + cur_val = 0; + for (; i < akbuf_idx(range); i ++) + { + akbuf_get_byte(range, i, c); + if (c > '9' || c < '0') break; + c -= '0'; + cur_val *= 10; + cur_val += c; + } + DEBUGF("got range %u", cur_val); + return cur_val; +} +int http_serve_hook(int fd, net_fd_entry *net_ent) +{ + struct serve_hook_entry + { + unsigned char *uri; + void (*serve_fn)(); + } serve_hooks[] = + { + { "announce", tracker_serve_announce }, + { "announce.php", tracker_serve_announce }, + { "announcephp", tracker_serve_announce }, + { "scrape", tracker_serve_scrape }, + { "scrape.php", tracker_serve_scrape }, + { "scrapephp", tracker_serve_scrape }, + { "status", tracker_serve_status }, + { "peers", tracker_serve_peers }, + { NULL, NULL }, + }; + unsigned int i; + akbuf_ctxh ctx; + akbuf *uribuf; + akbuf_asciiz(http_fds[fd].uri); + + ctx = akbuf_new_ctx(); + uribuf = akbuf_init(ctx, 0); + akbuf_clone(uribuf, http_fds[fd].uri); + while (akbuf_idx(uribuf) > 1 && akbuf_data(uribuf)[0] == '/') akbuf_eat_byte(uribuf); + akbuf_asciiz(uribuf); + i = 0; + while (serve_hooks[i].uri != NULL) + { + if (strcmp(akbuf_data(uribuf), serve_hooks[i].uri) == 0) + { + http_log_query(fd, net_ent, HTTP_LOG_HOOK); + serve_hooks[i].serve_fn(fd, &http_fds[fd], net_ent); + akbuf_free_ctx(ctx); + return i; + } + i ++; + } + akbuf_free_ctx(ctx); + return -1; +} +void http_serve_req(int fd, net_fd_entry *net_ent) +{ + unsigned char *root_dir, *host_p, *p; + akbuf_ctxh ctx; + akbuf *host_buf, *buf; + unsigned char resolved_path[PATH_MAX + 1], resolved_root[PATH_MAX + 1]; + struct stat st, st2; + int i; + unsigned int range_start; + /* parsebufs: 0 == req path 1 == req path */ + +#define SERVE_ERR(code)\ + {\ + http_error(fd, net_ent, (code));\ + akbuf_free_ctx(ctx);\ + return;\ + } + + DEBUGF("serving req"); + ctx = akbuf_new_ctx(); + if ((p = akbuf_table_entry_get_str(http_fds[fd].headers, "Connection")) != NULL) + { + DEBUGF("header connection = %s", p); + if (strcasecmp(p, "keep-alive") == 0) http_fds[fd].keep_alive = 1; + else if (strcasecmp(p, "close") == 0) http_fds[fd].keep_alive = 0; + } + if ((buf = akbuf_table_entry_get(http_fds[fd].headers, "Range")) != NULL) + { + range_start = http_parse_range(buf); + } else + { + range_start = 0; + } + if ((buf = akbuf_table_entry_get(http_fds[fd].headers, "Host")) != NULL) + { + host_buf = akbuf_init(ctx, 0); + akbuf_clone(host_buf, buf); akbuf_asciiz(host_buf); + root_dir = akbuf_table_entry_get_str(cfg.vhosts, akbuf_data(host_buf)); + if (root_dir == NULL && (i = akbuf_chr(buf, ':')) != -1) + { + akbuf_set_idx(buf, i); + akbuf_asciiz(buf); + root_dir = akbuf_table_entry_get_str(cfg.vhosts, akbuf_data(buf)); + } + if (root_dir == NULL) root_dir = cfg.default_root; + host_p = akbuf_data(host_buf); + } else + { + if (http_fds[fd].ver_maj > 0 && (http_fds[fd].ver_maj != 1 || http_fds[fd].ver_min != 0)) SERVE_ERR(HTTP_BAD_REQUEST); + akbuf_asciiz(net_ent->sockbuf); + host_p = akbuf_data(net_ent->sockbuf); + root_dir = cfg.default_root; + } + DEBUGF("host [%s] root [%s]", host_p, root_dir); + http_do_rewrite(http_fds[fd].uri); + akbuf_asciiz(http_fds[fd].uri); + DEBUGF("rewrote => [%s]", akbuf_data(http_fds[fd].uri)); + if (http_has_access(net_ent->peerbuf, http_fds[fd].uri) == 0) SERVE_ERR(HTTP_FORBIDDEN_BANNED); + if (http_serve_hook(fd, net_ent) >= 0) { akbuf_free_ctx(ctx); return; } + if (realpath(root_dir, resolved_root) == NULL) SERVE_ERR((errno == EPERM)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); + resolved_root[sizeof(resolved_root) - 1] = 0; + akbuf_strcpy(parsebufs[0], resolved_root); + akbuf_append_buf(parsebufs[0], http_fds[fd].uri); + akbuf_asciiz(parsebufs[0]); + DEBUGF("req path [%s]", akbuf_data(parsebufs[0])); + if (realpath(akbuf_data(parsebufs[0]), resolved_path) == NULL) SERVE_ERR((errno == EPERM)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); + resolved_path[sizeof(resolved_path) - 1] = 0; + DEBUGF("resolved path [%s] root %s", resolved_path, resolved_root); + if (strlen(resolved_path) < strlen(resolved_root) || strncmp(resolved_path, resolved_root, strlen(resolved_root)) != 0) SERVE_ERR(HTTP_NOT_FOUND); + + if (stat(resolved_path, &st) != 0) SERVE_ERR((errno == EPERM)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); + if (st.st_mode & S_IFDIR) + { + unsigned int c; + + if (akbuf_empty(http_fds[fd].uri)) SERVE_ERR(HTTP_BAD_REQUEST); + if (access(resolved_path, X_OK) != 0) SERVE_ERR((errno == EPERM || errno == EACCES)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); + akbuf_get_byte(http_fds[fd].uri, akbuf_idx(http_fds[fd].uri) - 1, c); + if (c == '/') + { + http_index(fd, net_ent, resolved_path, range_start); + } else + { + http_log_query(fd, net_ent, HTTP_MOVED_PERM); + akbuf_asciiz(http_fds[fd].uri); + buf = akbuf_init(ctx, 0); + akbuf_sprintf(buf, + "\r\n" + "The document has moved\r\n" + "New address http://%s%s/\r\n" + "\r\n\r\n", + host_p, akbuf_data(http_fds[fd].uri), + host_p, akbuf_data(http_fds[fd].uri)); + akbuf_sprintf(net_ent->sendbuf, + "HTTP/1.1 %u Moved permanently\r\n" + "Server: " SERVER_VERSION "\r\n" + "Content-type: text/html\r\n" + "Content-length: %u\r\n" + "Connection: %s\r\n" + "Location: http://%s%s/\r\n" + "\r\n", + HTTP_MOVED_PERM, + akbuf_idx(buf), + (http_fds[fd].keep_alive == 1)? "keep-alive" : "close", + host_p, akbuf_data(http_fds[fd].uri)); + akbuf_append_buf(net_ent->sendbuf, buf); + } + net_send(fd); + akbuf_free_ctx(ctx); + return; + } + akbuf_strcpy(parsebufs[0], resolved_path); + akbuf_append_byte(parsebufs[0], '.'); + akbuf_append_str(parsebufs[0], GZIP_SUFFIX); + akbuf_asciiz(parsebufs[0]); + if (akbuf_table_entry_get(http_fds[fd].headers, "Accept-Encoding") != NULL && stat(akbuf_data(parsebufs[0]), &st2) == 0 && !(st2.st_mode & S_IFDIR)) + { + if (serve_doc_to_fd(fd, net_ent, resolved_path, akbuf_data(parsebufs[0]), range_start, st2.st_size, 1) == -1) SERVE_ERR((errno == EPERM || errno == EACCES)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); + } else + { + if (serve_doc_to_fd(fd, net_ent, resolved_path, resolved_path, range_start, st.st_size, 0) == -1) SERVE_ERR((errno == EPERM || errno == EACCES)? HTTP_FORBIDDEN : HTTP_NOT_FOUND); + } + akbuf_free_ctx(ctx); +} +AKssize_t http_unescape(akbuf *inbuf, akbuf *outbuf) +{ + unsigned int rem, i, c, d; + akbuf_ctxh ctx; + akbuf *buf; + + ctx = AKbuf_INVALID_CTX; + if (outbuf == NULL) + { + ctx = akbuf_new_ctx(); + buf = akbuf_init(ctx, akbuf_idx(inbuf)); + } else + { + buf = outbuf; + } + i = 0; + rem = akbuf_idx(inbuf); + while (rem > 0) + { + akbuf_get_byte(inbuf, i, d); i ++; rem --; + if (d == '%' && rem >= 2) + { + akbuf_get_byte(inbuf, i, c); i ++; rem --; + if (c >= 'a') c -= 'a' - 0xa; + else if (c >= 'A') c -= 'A' - 0xa; + else if (c >= '0') c -= '0'; + else break; + d = c << 4; + akbuf_get_byte(inbuf, i, c); i ++; rem --; + if (c >= 'a') c -= 'a' - 0xa; + else if (c >= 'A') c -= 'A' - 0xa; + else if (c >= '0') c -= '0'; + else break; + d |= c; + if (d > 0xff) break; + } + akbuf_append_byte(buf, d); + } + if (rem > 0) { if (ctx != AKbuf_INVALID_CTX) akbuf_free_ctx(ctx); return -rem; } + if (outbuf == NULL) akbuf_clone(inbuf, buf); + if (ctx != AKbuf_INVALID_CTX) akbuf_free_ctx(ctx); + return i; +} +void http_parse_arg(akbuf *arg, akbuf *var, akbuf *val) +{ + int i; + + akbuf_set_idx(var, 0); + akbuf_set_idx(val, 0); + if ((i = akbuf_chr(arg, '=')) != -1) + { + akbuf_split(arg, var, i); + akbuf_clone(val, arg); + } else + { + akbuf_clone(var, arg); + } + http_unescape(var, NULL); + http_unescape(val, NULL); +} +void http_parse_args(int fd, net_fd_entry *net_ent, akbuf *args) +{ + akbuf_ctxh ctx; + akbuf *splitargs, *splitarg; + akbuf *var, *val; + int i; + + ctx = akbuf_new_ctx(); + splitargs = akbuf_init(ctx, 0); + splitarg = akbuf_init(ctx, 0); + var = akbuf_init(ctx, 0); + val = akbuf_init(ctx, 0); + akbuf_clone(splitargs, args); + while ((i = akbuf_chr(splitargs, '&')) != -1) + { + akbuf_split(splitargs, splitarg, i); + http_parse_arg(splitarg, var, val); + akbuf_asciiz(var); +#ifdef DEBUG + akbuf_asciiz(val); + DEBUGF("http_parse_args(): var [%s] val [%s]\n", akbuf_data(var), akbuf_data(val)); +#endif + akbuf_table_entry_add(http_fds[fd].ctx, http_fds[fd].args, akbuf_data(var), val); + } + if (akbuf_idx(splitargs) > 0) + { + http_parse_arg(splitargs, var, val); + akbuf_asciiz(var); +#ifdef DEBUG + akbuf_asciiz(val); + DEBUGF("http_parse_args(): last var [%s] val [%s]\n", akbuf_data(var), akbuf_data(val)); +#endif + akbuf_table_entry_add(http_fds[fd].ctx, http_fds[fd].args, akbuf_data(var), val); + } + akbuf_free_ctx(ctx); +} +void http_handle_action(int fd, net_fd_entry *net_ent, akbuf *linebuf) +{ + int i; + unsigned int c; + /* parsebufs: 0 == method, 1 == uri */ + +#define ACTION_ERR()\ + {\ + http_error(fd, net_ent, HTTP_BAD_REQUEST);\ + return;\ + } + + DEBUGF("http action"); + if ((i = akbuf_chr(linebuf, ' ')) == -1) ACTION_ERR(); + akbuf_split(linebuf, parsebufs[0], i); + akbuf_asciiz(parsebufs[0]); + if (strcasecmp(akbuf_data(parsebufs[0]), "GET") == 0) http_fds[fd].method = HTTP_METHOD_GET; + else if (strcasecmp(akbuf_data(parsebufs[0]), "HEAD") == 0) http_fds[fd].method = HTTP_METHOD_HEAD; + else if (strcasecmp(akbuf_data(parsebufs[0]), "POST") == 0) http_fds[fd].method = HTTP_METHOD_POST; + else ACTION_ERR(); + if ((i = akbuf_chr(linebuf, ' ')) != -1) + { + unsigned int cur_ver, seen_dot; + + akbuf_split(linebuf, parsebufs[1], i); + if ((i = akbuf_chr(linebuf, '/')) == -1) ACTION_ERR(); + if (++ i >= akbuf_idx(linebuf)) ACTION_ERR(); + cur_ver = seen_dot = 0; + for (; i < akbuf_idx(linebuf); i ++) + { + akbuf_get_byte(linebuf, i, c); + if (c == '.') + { + if (seen_dot == 0) http_fds[fd].ver_maj = cur_ver; + else if (seen_dot == 1) http_fds[fd].ver_min = cur_ver; + seen_dot ++; + cur_ver = 0; + } else + { + if (c > '9' || c < '0') ACTION_ERR(); + cur_ver *= 10; + cur_ver += c - '0'; + } + } + if (seen_dot == 0) http_fds[fd].ver_maj = cur_ver; + else if (seen_dot == 1) http_fds[fd].ver_min = cur_ver; + } else + { + akbuf_clone(parsebufs[1], linebuf); + http_fds[fd].ver_maj = 0; + http_fds[fd].ver_min = 9; + } + if ((i = akbuf_chr(parsebufs[1], '?')) != -1) + { + if (i == 0) ACTION_ERR(); + akbuf_set_data(http_fds[fd].query, akbuf_data(parsebufs[1]) + i + 1, akbuf_idx(parsebufs[1]) - i - 1); + akbuf_set_idx(parsebufs[1], i); + akbuf_asciiz(http_fds[fd].query); akbuf_asciiz(parsebufs[1]); + DEBUGF("got query [%s] rem [%s]", akbuf_data(http_fds[fd].query), akbuf_data(parsebufs[1])); + http_parse_args(fd, net_ent, http_fds[fd].query); + } else + { + akbuf_set_idx(http_fds[fd].query, 0); + } + if (akbuf_empty(parsebufs[1])) ACTION_ERR(); + http_unescape(parsebufs[1], http_fds[fd].uri); + if (akbuf_empty(http_fds[fd].uri)) ACTION_ERR(); + akbuf_asciiz(http_fds[fd].uri); + DEBUGF("got http version %u.%u", http_fds[fd].ver_maj, http_fds[fd].ver_min); + akbuf_asciiz(net_ent->peerbuf); + aklogf(LOG_DEBUG, "%d: %s HTTP/%u.%u \"%s %s\"", fd, akbuf_data(net_ent->peerbuf), http_fds[fd].ver_maj, http_fds[fd].ver_min, akbuf_data(parsebufs[0]), akbuf_data(http_fds[fd].uri)); + if (http_fds[fd].ver_maj > 0) http_fds[fd].state = HTTP_FD_HEADERS; else http_serve_req(fd, net_ent); +} +void http_handle_content(int fd, net_fd_entry *net_ent, akbuf *buf) +{ + unsigned int rem; + + rem = http_fds[fd].content_len - akbuf_idx(http_fds[fd].content); + akbuf_append_data(http_fds[fd].content, akbuf_data(buf), (akbuf_idx(buf) > rem)? rem : akbuf_idx(buf)); + if (akbuf_idx(http_fds[fd].content) >= http_fds[fd].content_len) + { + http_serve_req(fd, net_ent); + return; + } +} +void http_handle_headers(int fd, net_fd_entry *net_ent, akbuf *linebuf) +{ + int i; + /* parsebufs: 0 == header name */ + +#define HEADERS_ERR()\ + {\ + http_error(fd, net_ent, HTTP_BAD_REQUEST);\ + return;\ + } + + if (akbuf_empty(linebuf) || (i = akbuf_chr(linebuf, ':')) == -1) + { + DEBUGF("content-len %u", http_fds[fd].content_len); + if (http_fds[fd].content_len != 0) + { + http_fds[fd].state = HTTP_FD_CONTENT; + net_set_callbacks(fd, http_handle_content, http_handle_sent); + net_set_type(fd, NET_FD_READ); + } else + { + http_serve_req(fd, net_ent); + } + return; + } + if (http_fds[fd].num_headers >= HTTP_MAX_HEADERS) HEADERS_ERR(); + akbuf_split(linebuf, parsebufs[0], i); + for (i = 0; i < akbuf_idx(linebuf); i ++) if (akbuf_data(linebuf)[i] != ' ' && akbuf_data(linebuf)[i] != '\t') break; + akbuf_consume(linebuf, i); + akbuf_asciiz(parsebufs[0]); akbuf_asciiz(linebuf); + DEBUGF("header [%s] : [%s]", akbuf_data(parsebufs[0]), akbuf_data(linebuf)); + if (strcasecmp(akbuf_data(parsebufs[0]), "Content-length") == 0) + { + http_fds[fd].content_len = atoi(akbuf_data(linebuf)); + if (http_fds[fd].content_len > HTTP_MAX_CONTENT_LEN) HEADERS_ERR(); + } + akbuf_table_entry_add(http_fds[fd].ctx, http_fds[fd].headers, akbuf_data(parsebufs[0]), linebuf); + return; +} +void http_handle_action_and_headers(int fd, net_fd_entry *net_ent, akbuf *linebuf) +{ + unsigned int i; + + for (i = 0; i < sizeof(parsebufs) / sizeof(parsebufs[0]); i ++) if (akbuf_size(parsebufs[i]) > BUF_SIZE) akbuf_set_size(parsebufs[i], BUF_SIZE); + switch (http_fds[fd].state) + { + case HTTP_FD_UNUSED: + case HTTP_FD_NEW_REQ: + http_fds[fd].ctx = net_ent->ctx; + http_fds[fd].state = HTTP_FD_ACTION; + http_fds[fd].method = HTTP_METHOD_NONE; + http_fds[fd].uri = akbuf_init(net_ent->ctx, 0); + http_fds[fd].query = akbuf_init(net_ent->ctx, 0); + http_fds[fd].content = akbuf_init(net_ent->ctx, 0); + http_fds[fd].content_len = 0; + http_fds[fd].ver_maj = http_fds[fd].ver_min = 0; + http_fds[fd].keep_alive = 0; + http_fds[fd].num_headers = 0; + http_fds[fd].headers = akbuf_table_init(net_ent->ctx, AKBUF_TABLE_NOCASE); + http_fds[fd].num_args = 0; + http_fds[fd].args = akbuf_table_init(net_ent->ctx, AKBUF_TABLE_NOCASE); + http_fds[fd].rpipe_fd = -1; + http_fds[fd].rpipe_net_ent = NULL; + http_fds[fd].wpipe_fd = -1; + http_fds[fd].wpipe_net_ent = NULL; + /* fallthrough */ + case HTTP_FD_ACTION: + http_handle_action(fd, net_ent, linebuf); + break; + case HTTP_FD_HEADERS: + http_handle_headers(fd, net_ent, linebuf); + break; + } +} diff --git a/http.h b/http.h new file mode 100644 index 0000000..d814663 --- /dev/null +++ b/http.h @@ -0,0 +1,47 @@ +enum { HTTP_FD_UNUSED, HTTP_FD_NEW_REQ, HTTP_FD_ACTION, HTTP_FD_HEADERS, HTTP_FD_CONTENT, HTTP_FD_PIPING, HTTP_FD_CGI, HTTP_FD_DIR }; +enum { HTTP_METHOD_NONE, HTTP_METHOD_GET, HTTP_METHOD_HEAD, HTTP_METHOD_POST }; + +#define HTTP_OK 200 +#define HTTP_PARTIAL_CONTENT 206 +#define HTTP_MOVED_PERM 301 +#define HTTP_BAD_REQUEST 400 +#define HTTP_FORBIDDEN 403 +#define HTTP_NOT_FOUND 404 +#define HTTP_INTERNAL_ERROR 500 +#define HTTP_SERVER_TOO_BUSY 503 +#define HTTP_FORBIDDEN_BANNED 2403 + +#define HTTP_LOG_HOOK 1000 + +typedef unsigned int http_fd_state; + +struct http_fd_entry_s +{ + http_fd_state state; + akbuf_ctxh ctx; + unsigned int method; + akbuf *uri; + akbuf *query; + akbuf *content; + unsigned int num_args; + akbuf_table *args; + AKsize_t content_len; + unsigned int ver_maj, ver_min; + unsigned int keep_alive; + unsigned int num_headers; + akbuf_table *headers; + int rpipe_fd; + net_fd_entry *rpipe_net_ent; + int wpipe_fd; + net_fd_entry *wpipe_net_ent; + DIR *listdir; + akbuf *dirpath; +}; + +typedef struct http_fd_entry_s http_fd_entry; + +void http_init(void); +void http_unset_fd(int); +void http_handle_sent(int, net_fd_entry *); +unsigned char *http_status_msg(unsigned int); +void http_handle_action_and_headers(int, net_fd_entry *, akbuf *); diff --git a/hypercube.cfg.dist b/hypercube.cfg.dist new file mode 100644 index 0000000..583784e --- /dev/null +++ b/hypercube.cfg.dist @@ -0,0 +1,38 @@ +listen_port 8000 +listen_addr 127.0.0.1 + +default_root /2/anakata/ + +log +log_level all +#log_file log + +no background + +#chroot_dir rootdir/ +#run_as_uid 242 +#run_as_gid 242 + +rewrite ^/~([^/]+)/?(.*) /bar/$1/$2 + +#vhost localhost /2/anakata/btdl +vhost wintermute dox2/ #foo +vhost bla dox3/ +vhost bla:4242 dox4/ + +mime html text/html +mime htm text/html +mime txt text/plain +mime jpeg image/jpeg +mime jpg image/jpeg +mime gif image/gif +mime png image/png +mime bmp image/bmp +mime wav audio/x-wav +mime torrent application/x-bittorrent +mime cgi application/x-cgi + +no mime foo bar + +include tracker.cfg +include access.cfg diff --git a/hypercube.h b/hypercube.h new file mode 100644 index 0000000..8382ef1 --- /dev/null +++ b/hypercube.h @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_MYSQL +#include +#endif + +#include "dist.h" +#include "config.h" +#include "akbuf/akbuf.h" +#include "asio/asio.h" + +#ifdef USE_LINUX_SENDFILE +#include +#endif + +#include "log.h" +#include "net.h" +#include "http.h" +#include "cfg.h" +#include "tracker.h" + +#ifdef DEBUG +#define DEBUGF(v...) aklogf(LOG_DEBUG, "DEBUG: " v); +#define AKdassert(cond) AKassert(cond) +#else +#define DEBUGF(v...) +#define AKdassert(cond) +#endif + +#define AKassert(c) if (!(c)) { aklogf(LOG_ERROR, "Assertion (" __STRING(c) ") failed @ " __FILE__ ":%u", __LINE__); exit(1); } +#define AKstrcpy(dest, src) { strncpy((dest), (src), sizeof(dest) - 1); (dest)[sizeof(dest) - 1] = 0; } + +#ifdef SHORT_SERVER_VERSION +#define SERVER_VERSION_STR "hypercube" +#else +#define SERVER_VERSION_STR "hypercube/1.1alpha tracker/0.1alpha (" SERVER_DIST ") by anakata [anakata-hc@prq.se]" +#endif + +#ifdef DEBUG +#define SERVER_VERSION SERVER_VERSION_STR " [debug build]" +#else +#define SERVER_VERSION SERVER_VERSION_STR +#endif diff --git a/hypercube.includes b/hypercube.includes new file mode 100644 index 0000000..ef5e2a4 --- /dev/null +++ b/hypercube.includes @@ -0,0 +1,3 @@ +. +akbuf +asio diff --git a/hypercube.pro b/hypercube.pro new file mode 100644 index 0000000..b8e911d --- /dev/null +++ b/hypercube.pro @@ -0,0 +1,36 @@ +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG -= qt + +DEFINES += USE_LINUX_SENDFILE +#DEFINES += ASIO_USE_POLL +DEFINES += SHORT_SERVER_VERSION +#DEFINES += DEBUG +#DEFINES += AKBUF_DEBUG + +# AKbuf sources +SOURCES += akbuf/akbuf.c +HEADERS += akbuf/akbuf.h + +# ASIO sources +SOURCES += asio/asio.c +HEADERS += asio/asio.h + +# Hypercube sources +HEADERS += config.h +HEADERS += dist.h +HEADERS += hypercube.h +SOURCES += cfg.c +HEADERS += cfg.h +SOURCES += log.c +HEADERS += log.h +SOURCES += net.c +HEADERS += net.h +SOURCES += http.c +HEADERS += http.h +SOURCES += tracker.c +HEADERS += tracker.h +SOURCES += main.c + + diff --git a/log.c b/log.c new file mode 100644 index 0000000..bacac50 --- /dev/null +++ b/log.c @@ -0,0 +1,88 @@ +#include "hypercube.h" + +static akbuf_ctxh log_ctx; +static akbuf *log_buf, *date_buf; +static FILE *log_file; +static unsigned int log_level_val; + +unsigned int log_initialized = 0; + +extern hypercube_config cfg; + +void log_init(void) +{ + log_ctx = akbuf_new_ctx(); + log_buf = akbuf_init(log_ctx, 0); + date_buf = akbuf_init(log_ctx, 32); + log_level_val = LOG_NONE; + if (cfg.log == 1) + { + switch (cfg.log_level[0]) + { + case 'e': case 'E': log_level_val = LOG_ERROR; break; + case 'i': case 'I': log_level_val = LOG_INFO; break; + case 'r': case 'R': log_level_val = LOG_REQUEST; break; + case 'c': case 'C': log_level_val = LOG_CONNECTION; break; + case 'd': case 'D': log_level_val = LOG_DEBUG; break; + case 'a': case 'A': log_level_val = LOG_ALL; break; + default: + fprintf(stderr, "Invalid log level '%s'.\n", cfg.log_level); + exit(1); + break; + } + if (cfg.log_file != NULL) + { + if ((log_file = fopen(cfg.log_file, "a+")) == NULL) { fprintf(stderr, "Couldn't open log file (%s): %s\n", cfg.log_file, strerror(errno)); exit(1); } + } else + { + log_file = stdout; + } + } + log_initialized = 1; +} +unsigned char *get_date_str(time_t t) +{ + akbuf_strcpy(date_buf, ctime(&t)); + if (akbuf_idx(date_buf) > 0) akbuf_consume_end(date_buf, 1); + akbuf_asciiz(date_buf); + return akbuf_data(date_buf); +} +unsigned char *get_now_date_str(void) +{ + return get_date_str(time(NULL)); +} +void aklogf(unsigned int level, unsigned char *fmt, ...) +{ + va_list va; + unsigned int i, c; + + if (cfg.log == 0) return; + if (level > log_level_val) return; + va_start(va, fmt); + if (log_initialized == 0) + { + vprintf(fmt, va); + printf("\n"); + va_end(va); + return; + } + akbuf_vsprintf(log_buf, fmt, va); + va_end(va); + for (i = 0; i < akbuf_idx(log_buf); i ++) + { + akbuf_get_byte(log_buf, i, c); + if (c < 0x20 || c > 0x7f) akbuf_set_byte(log_buf, i, '.'); + } + akbuf_append_byte(log_buf, '\n'); + akbuf_asciiz(log_buf); + fprintf(log_file, "Log: %s %s", get_now_date_str(), akbuf_data(log_buf)); + fflush(log_file); + if (log_file != stdout && level == LOG_ERROR) fprintf(stderr, "Log: %s %s\n", get_now_date_str(), akbuf_data(log_buf)); + if (akbuf_size(log_buf) > BUF_SIZE) akbuf_set_size(log_buf, BUF_SIZE); +} +void akperror(unsigned char *msg) +{ + if (cfg.log == 0) return; + if (msg == NULL || msg[0] == 0) msg = "(none)"; + aklogf(LOG_ERROR, "Error: %s %s", msg, strerror(errno)); +} diff --git a/log.h b/log.h new file mode 100644 index 0000000..e319657 --- /dev/null +++ b/log.h @@ -0,0 +1,7 @@ +enum { LOG_NONE, LOG_ERROR, LOG_INFO, LOG_REQUEST, LOG_CONNECTION, LOG_DEBUG, LOG_ALL }; + +void log_init(void); +void aklogf(unsigned int, unsigned char *, ...); +void akperror(unsigned char *); +unsigned char *get_date_str(time_t); +unsigned char *get_now_date_str(void); diff --git a/main.c b/main.c new file mode 100644 index 0000000..01003ab --- /dev/null +++ b/main.c @@ -0,0 +1,68 @@ +#include "hypercube.h" + +extern hypercube_config cfg; + +void mainloop(void) +{ + DEBUGF("entering mainloop"); + while (1) + { + net_wait_for_events(); +#ifdef SOCKET_TIMEOUT + net_periodic(); +#endif + tracker_periodic(); + } +} +void handle_fatal_sig(int sig) +{ + aklogf(LOG_ERROR, "Exiting on signal %d.", sig); + exit(sig); +} +void handle_reload_sig(int sig) +{ + aklogf(LOG_INFO, "Got signal %d, reloading config...", sig); + cfg_reload(); +} +void final_init(void) +{ + struct stat ost, st; + + if (stat("/", &ost) != 0) { perror("Couldn't stat /"); exit(1); } + if (cfg.chroot_dir != NULL && (chroot(cfg.chroot_dir) != 0 || chdir("/") != 0)) { perror("Changing root directory"); exit(1); } + if (stat(cfg.default_root, &st) == 0) + { + if (st.st_ino == ost.st_ino) + { + fprintf(stderr, "Refusing to start with filesystem root as default root.\n"); + exit(1); + } + } else + { + fprintf(stderr, "Warning: Couldn't access default root (%s): %s\n", cfg.default_root, strerror(errno)); + } + if (cfg.run_as_gid != -1 && setgid(cfg.run_as_gid) != 0) { perror("Changing GID"); exit(1); } + if (cfg.run_as_uid != -1 && setuid(cfg.run_as_uid) != 0) { perror("Changing UID"); exit(1); } + if (cfg.background == 1) + { + fclose(stdin); fclose(stdout); fclose(stderr); + if (fork() != 0) exit(0); + } + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + signal(SIGTERM, handle_fatal_sig); + signal(SIGINT, handle_fatal_sig); + signal(SIGHUP, handle_reload_sig); +} +int main(int argc, char *argv[]) +{ + cfg_init(); + log_init(); + net_init(); + http_init(); + tracker_init(); + net_start_listen(); + final_init(); + mainloop(); + return 0; +} diff --git a/net.c b/net.c new file mode 100644 index 0000000..fc7f99f --- /dev/null +++ b/net.c @@ -0,0 +1,298 @@ +#include "hypercube.h" + +net_fd_entry net_fds[ASIO_MAX_FDS]; + +extern hypercube_config cfg; + +time_t period_time; + +void net_init(void) +{ + unsigned int i; + + period_time = time(NULL); + for (i = 0; i < ASIO_MAX_FDS; i ++) net_fds[i].type = NET_FD_UNUSED; + asio_init(); + DEBUGF("network initialized"); +} +void net_set_fd(int fd, net_fd_type type, void (*data_callback)(), void (*sent_callback)(), unsigned int alloc_buf) +{ + AKassert(FD_VALID(fd)); + net_fds[fd].type = type; + net_fds[fd].data_callback = data_callback; + net_fds[fd].sent_callback = sent_callback; + net_fds[fd].active_time = time(NULL); + net_fds[fd].ctx = akbuf_new_ctx(); + if (alloc_buf == 1) + { + net_fds[fd].readbuf = akbuf_init(net_fds[fd].ctx, 0); + net_fds[fd].linebuf = akbuf_init(net_fds[fd].ctx, 0); + net_fds[fd].sendbuf = akbuf_init(net_fds[fd].ctx, 0); + net_fds[fd].peerbuf = akbuf_init(net_fds[fd].ctx, 0); + net_fds[fd].sockbuf = akbuf_init(net_fds[fd].ctx, 0); + } else + { + net_fds[fd].readbuf = net_fds[fd].linebuf = net_fds[fd].sendbuf = net_fds[fd].peerbuf = net_fds[fd].sockbuf = NULL; + } + net_fds[fd].send_fd = -1; + switch (type) + { + case NET_FD_LISTEN: case NET_FD_READ: case NET_FD_READLINE: case NET_FD_RAW: + if (asio_add_fd(fd, ASIO_R) != 0) { akperror("asio_add_fd()"); exit(1); } + break; + case NET_FD_SEND: + if (asio_add_fd(fd, ASIO_W) != 0) { akperror("asio_add_fd()"); exit(1); } + break; + } +} +void net_set_callbacks(int fd, void (*data_callback)(), void (*sent_callback)()) +{ + AKassert(FD_VALID(fd)); + net_fds[fd].data_callback = data_callback; + net_fds[fd].sent_callback = sent_callback; +} +void net_set_type(int fd, net_fd_type type) +{ + AKassert(FD_VALID(fd)); + net_fds[fd].type = type; +} +void net_send_buf(int fd, akbuf *buf) +{ + AKassert(FD_VALID(fd)); + akbuf_append_buf(net_fds[fd].sendbuf, buf); +} +void net_unset_fd(int fd) +{ + AKassert(FD_VALID(fd)); + DEBUGF("unsetting fd %d", fd); + net_unset_fd_callback(fd); + if (net_fds[fd].type != NET_FD_UNUSED) + { + akbuf_free_ctx(net_fds[fd].ctx); + net_fds[fd].ctx = (akbuf_ctxh)0; + if (net_fds[fd].send_fd != -1) close(net_fds[fd].send_fd); + net_fds[fd].type = NET_FD_UNUSED; + } + asio_del_fd(fd, ASIO_R | ASIO_W); + close(fd); +} +void net_start_listen(void) +{ + int sock; + unsigned int i; + struct sockaddr_in sin; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 || sock >= ASIO_MAX_FDS) { akperror("socket()"); exit(1); } + i = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&i, sizeof(i)) != 0) { akperror("setsockopt()"); exit(1); } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = cfg.listen_addr.s_addr; + sin.sin_port = htons(cfg.listen_port); + if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) != 0) { akperror("bind()"); exit(1); } + if (listen(sock, LISTEN_BACKLOG) != 0) { akperror("listen() (decrease LISTEN_BACKLOG?)"); exit(1); } + net_set_fd(sock, NET_FD_LISTEN, NULL, NULL, 0); + aklogf(LOG_INFO, "Listening on port %d.", cfg.listen_port); +} +void test_readline(int fd, net_fd_entry *net_ent, akbuf *line) +{ + akbuf_asciiz(line); + printf("TEST: readline [%s]\n", akbuf_data(line)); +} +void net_accept_connection(int fd) +{ + struct sockaddr_in sin; + size_t sin_len; + int newfd; + + sin_len = sizeof(sin); + memset(&sin, 0, sizeof(sin)); + if ((newfd = accept(fd, (struct sockaddr *)&sin, &sin_len)) < 0) { akperror("accept()"); return; } + if (!FD_VALID(newfd)) + { + aklogf(LOG_INFO, "Client limit reached. Increase ASIO_MAX_FDS?"); + close(newfd); + return; + } + net_set_fd(newfd, NET_FD_INIT_TYPE, net_fd_init_data_callback, net_fd_init_sent_callback, 1); + akbuf_sprintf(net_fds[newfd].peerbuf, "%s:%u", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + sin_len = sizeof(sin); + memset(&sin, 0, sizeof(sin)); + if (getsockname(newfd, (struct sockaddr *)&sin, &sin_len) == 0) + { + akbuf_sprintf(net_fds[newfd].sockbuf, "%s:%u", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + } else + { + akbuf_strcpy(net_fds[newfd].sockbuf, ""); + } + aklogf(LOG_CONNECTION, "%d: Connection from %s", newfd, akbuf_data(net_fds[newfd].peerbuf)); +} +void net_send(int fd) +{ + DEBUGF("send on fd %d", fd); + asio_set_events(fd, ASIO_W); + net_fds[fd].type = NET_FD_SEND; +} +void net_sent(int fd) +{ + DEBUGF("sent on fd %d", fd); + asio_set_events(fd, ASIO_R); + net_fds[fd].type = NET_FD_INIT_TYPE; + if (net_fds[fd].send_fd != -1) { close(net_fds[fd].send_fd); net_fds[fd].send_fd = -1; } + if (net_fds[fd].sent_callback != NULL) net_fds[fd].sent_callback(fd, &net_fds[fd]); +} +void net_handle_send(int fd) +{ + AKssize_t n, m; + + if (!akbuf_empty(net_fds[fd].sendbuf)) + { + if ((n = write(fd, akbuf_data(net_fds[fd].sendbuf), akbuf_idx(net_fds[fd].sendbuf))) <= 0) + { + if (errno != EINTR && errno != EAGAIN) net_unset_fd(fd); + return; + } + akbuf_consume(net_fds[fd].sendbuf, n); + if (akbuf_empty(net_fds[fd].sendbuf) && net_fds[fd].send_fd == -1) + { + net_sent(fd); + return; + } + } else if (net_fds[fd].send_fd != -1) + { +#ifdef USE_LINUX_SENDFILE + if ((m = sendfile(fd, net_fds[fd].send_fd, &net_fds[fd].send_fd_off, (net_fds[fd].send_fd_len >= SEND_BUF_SIZE)? SEND_BUF_SIZE : net_fds[fd].send_fd_len)) < 0) + { + if (errno != EAGAIN && errno != EINTR) { akperror("sendfile()"); net_unset_fd(fd); } + return; + } + net_fds[fd].send_fd_len -= m; + if (net_fds[fd].send_fd_len == 0) net_sent(fd); +#else + unsigned char readbuf[SEND_BUF_SIZE]; + + if (net_fds[fd].send_fd_off != 0) + { + if (lseek(fd, net_fds[fd].send_fd_off, SEEK_SET) == (off_t)-1) akperror("lseek()"); + net_fds[fd].send_fd_off = 0; + } + if ((n = read(net_fds[fd].send_fd, readbuf, sizeof(readbuf))) <= 0) + { + if (errno != EINTR && errno != EAGAIN) net_sent(fd); + return; + } + if ((m = write(fd, readbuf, n)) <= 0) + { + if (errno != EINTR && errno != EAGAIN) net_unset_fd(fd); + return; + } + if (m < n) akbuf_append_data(net_fds[fd].sendbuf, &readbuf[m], n - m); +#endif + } else + { + net_sent(fd); + } +} +void net_handle_readline(int fd) +{ + AKssize_t n; + unsigned char readbuf[BUF_SIZE]; + int i, j; + + if ((n = read(fd, readbuf, sizeof(readbuf))) <= 0) + { + if (errno != EINTR && errno != EAGAIN) net_unset_fd(fd); + return; + } + DEBUGF("read %d bytes", n); + akbuf_append_data(net_fds[fd].readbuf, readbuf, n); + if (akbuf_idx(net_fds[fd].readbuf) > MAX_LINE_LEN) + { + net_unset_fd(fd); + return; + } + while ((i = akbuf_chr(net_fds[fd].readbuf, '\n')) >= 0 && net_fds[fd].type == NET_FD_READLINE) + { + akbuf_split(net_fds[fd].readbuf, net_fds[fd].linebuf, i); + if ((j = akbuf_chr(net_fds[fd].linebuf, '\r')) >= 0) akbuf_set_idx(net_fds[fd].linebuf, j); + if (net_fds[fd].data_callback != NULL) net_fds[fd].data_callback(fd, &net_fds[fd], net_fds[fd].linebuf); + } + if (net_fds[fd].type == NET_FD_READ && akbuf_idx(net_fds[fd].readbuf) > 0) + { + /* State changed with data remaining in buffer, so handle it. */ + if (net_fds[fd].data_callback != NULL) net_fds[fd].data_callback(fd, &net_fds[fd], net_fds[fd].readbuf); + akbuf_set_idx(net_fds[fd].readbuf, 0); + } +} +void net_handle_read(int fd) +{ + AKssize_t n; + unsigned char readbuf[BUF_SIZE]; + + if ((n = read(fd, readbuf, sizeof(readbuf))) <= 0) + { + if (errno != EINTR && errno != EAGAIN) net_unset_fd(fd); + return; + } + DEBUGF("read %d bytes", n); + akbuf_set_data(net_fds[fd].readbuf, readbuf, n); + if (net_fds[fd].data_callback != NULL) net_fds[fd].data_callback(fd, &net_fds[fd], net_fds[fd].readbuf); +} +void net_handle_raw(int fd) +{ + DEBUGF("raw event on fd %d", fd); + if (net_fds[fd].data_callback != NULL) net_fds[fd].data_callback(fd, &net_fds[fd], NULL); +} +void net_wait_for_events(void) +{ + asio_event_list *evs; + unsigned int i; + int fd; + + evs = asio_wait_for_events(); + AKassert(evs != NULL); + //DEBUGF("got %u events", evs->num_events); + for (i = 0; i < evs->num_events; i ++) + { + fd = evs->events[i].fd; + AKassert(FD_VALID(fd)); + DEBUGF("event on fd %d", fd); + net_fds[fd].active_time = time(NULL); + if (evs->events[i].event & ASIO_R) + { + switch (net_fds[fd].type) + { + case NET_FD_LISTEN: net_accept_connection(fd); break; + case NET_FD_READLINE: net_handle_readline(fd); break; + case NET_FD_READ: net_handle_read(fd); break; + case NET_FD_RAW: net_handle_raw(fd); break; + default: DEBUGF("Unknown revent type %u on fd %d", net_fds[fd].type, fd); + } + } + if (evs->events[i].event & ASIO_W) + { + switch (net_fds[fd].type) + { + case NET_FD_SEND: net_handle_send(fd); break; + default: DEBUGF("Unknown wevent type %u on fd %d", net_fds[fd].type, fd); + } + } + } +} +void net_periodic(void) +{ +#ifdef SOCKET_TIMEOUT + unsigned int i; + time_t t; + + if (time(NULL) - period_time >= SOCKET_TIMEOUT / 2) + { + t = time(NULL); + for (i = 0; i < ASIO_MAX_FDS; i ++) if (net_fds[i].type != NET_FD_UNUSED && net_fds[i].type != NET_FD_LISTEN && net_fds[i].type != NET_FD_RAW && t - net_fds[i].active_time >= SOCKET_TIMEOUT) + { + net_unset_fd(i); + } + period_time = time(NULL); + } +#endif +} diff --git a/net.h b/net.h new file mode 100644 index 0000000..d034587 --- /dev/null +++ b/net.h @@ -0,0 +1,38 @@ +#define NET_FD_INIT_TYPE NET_FD_READLINE +#define net_fd_init_data_callback http_handle_action_and_headers +#define net_fd_init_sent_callback http_handle_sent +#define net_unset_fd_callback http_unset_fd + +#define FD_VALID(fd) ((unsigned int)(fd) < ASIO_MAX_FDS) + +enum { NET_FD_UNUSED, NET_FD_LISTEN, NET_FD_READ, NET_FD_READLINE, NET_FD_SEND, NET_FD_RAW }; +typedef unsigned int net_fd_type; + +struct net_fd_entry_s +{ + net_fd_type type; + void (*data_callback)(); + void (*sent_callback)(); + time_t active_time; + akbuf_ctxh ctx; + akbuf *peerbuf; + akbuf *sockbuf; + akbuf *readbuf, *linebuf; + akbuf *sendbuf; + int send_fd; + AKsize_t send_fd_len; + off_t send_fd_off; +}; + +typedef struct net_fd_entry_s net_fd_entry; + +void net_init(void); +void net_start_listen(void); +void net_set_fd(int, net_fd_type, void (*)(), void (*)(), unsigned int); +void net_set_callbacks(int, void (*)(), void (*)()); +void net_set_type(int, net_fd_type); +void net_send_buf(int, akbuf *); +void net_unset_fd(int); +void net_send(int); +void net_wait_for_events(void); +void net_periodic(void); diff --git a/setdist.sh b/setdist.sh new file mode 100755 index 0000000..6e07a2b --- /dev/null +++ b/setdist.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo "#define SERVER_DIST \"`date` `whoami`@`hostname`\"" > dist.h diff --git a/tracker.c b/tracker.c new file mode 100644 index 0000000..dd2244d --- /dev/null +++ b/tracker.c @@ -0,0 +1,1095 @@ +#include "hypercube.h" + +extern hypercube_config cfg; + +peer_entry *torrent_hash[PEER_HASH_SIZE]; +peer_entry *peer_hash[PEER_HASH_SIZE]; + +#ifdef INFOHASH_RESTRICTION +unsigned int infohash_allowed[PEER_HASH_SIZE]; +static time_t last_infohash_period; +#endif + +unsigned int num_peers, num_torrents, num_seeders, num_leechers; +unsigned int peers_mem_size; + +unsigned int announce_count, scrape_count, status_count, peers_count; + +static time_t last_period; + +static akbuf_ctxh tracker_ctx; +akbuf *syncbuf; + + +void benc_str(akbuf *bencbuf, unsigned char *str) +{ + akbuf_appendf(bencbuf, "%u:%s", strlen(str), str); +} +void benc_int(akbuf *bencbuf, int i) +{ + akbuf_appendf(bencbuf, "i%de", i); +} +void benc_raw(akbuf *bencbuf, unsigned char *data, AKsize_t len) +{ + akbuf_appendf(bencbuf, "%u:", len); + akbuf_append_data(bencbuf, data, len); +} +void benc_buf(akbuf *bencbuf, akbuf *buf) +{ + benc_raw(bencbuf, akbuf_data(buf), akbuf_idx(buf)); +} +void benc_key_raw(akbuf *bencbuf, unsigned char *key, unsigned char *val, AKsize_t val_len) +{ + benc_str(bencbuf, key); + benc_raw(bencbuf, val, val_len); +} +void benc_key_buf(akbuf *bencbuf, unsigned char *key, akbuf *valbuf) +{ + benc_str(bencbuf, key); + benc_buf(bencbuf, valbuf); +} +void benc_key_int(akbuf *bencbuf, unsigned char *key, int val) +{ + benc_str(bencbuf, key); + benc_int(bencbuf, val); +} +void benc_key(akbuf *bencbuf, unsigned char *key, unsigned char *val) +{ + benc_str(bencbuf, key); + benc_str(bencbuf, val); +} +void benc_out_dict(akbuf *bencbuf, akbuf *outbuf) +{ + akbuf_append_byte(outbuf, 'd'); + akbuf_append_buf(outbuf, bencbuf); + akbuf_append_byte(outbuf, 'e'); +} +void benc_out_list(akbuf *bencbuf, akbuf *outbuf) +{ + akbuf_append_byte(outbuf, 'l'); + akbuf_append_buf(outbuf, bencbuf); + akbuf_append_byte(outbuf, 'e'); +} + +void tracker_http_response(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent, unsigned int code, unsigned char *content_type) +{ + net_send(fd); + http_ent->keep_alive = 0; + if (http_ent->ver_maj == 0) return; + akbuf_sprintf(net_ent->sendbuf, + "HTTP/%u.%u %u %s\r\n" + "Server: " SERVER_VERSION "\r\n" + "Content-type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "\r\n", + http_ent->ver_maj, http_ent->ver_min, + code, http_status_msg(code), + content_type); +} +void tracker_benc_response(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent, akbuf *bencbuf) +{ + tracker_http_response(fd, http_ent, net_ent, HTTP_OK, "text/plain"); + benc_out_dict(bencbuf, net_ent->sendbuf); +#ifdef DEBUG + { + unsigned int i, c; + + for (i = 0; i < akbuf_idx(bencbuf); i ++) + { + akbuf_get_byte(bencbuf, i, c); + if (c == 0) akbuf_set_byte(bencbuf, i, '.'); + } + akbuf_asciiz(bencbuf); + DEBUGF("sending bencoded response (%u byte(s)): [%s]\n", akbuf_idx(bencbuf), akbuf_data(bencbuf)); + } +#endif +} + +unsigned int hash_buf(akbuf *buf) +{ + unsigned int i, c; + unsigned char curhash[4]; + + memcpy(curhash, "\xf0\x0f\xc7\xc8", 4); + for (i = 0; i < akbuf_idx(buf); i ++) + { + akbuf_get_byte(buf, i, c); + curhash[i & 3] ^= c; + } + return (curhash[0] << 24) | (curhash[1] << 16) | (curhash[2] << 8) | curhash[3]; +} + +peer_entry *peer_get(akbuf *peer_id, akbuf *info_hash, unsigned int alloc_new) +{ + peer_entry *peer, *cur; + unsigned int idx, pidx, i, free_idx; + + AKdassert(akbuf_idx(peer_id) == ID_LEN && akbuf_idx(info_hash) == ID_LEN); + pidx = PEER_HASH_FN(peer_id); + if (peer_hash[pidx] != NULL && + memcmp(peer_hash[pidx]->peer_id, akbuf_data(peer_id), ID_LEN) == 0 && + memcmp(peer_hash[pidx]->info_hash, akbuf_data(info_hash), ID_LEN) == 0) + { + DEBUGF("found peer in peer_hash"); + return peer_hash[pidx]; + } + free_idx = (unsigned int)-1; + idx = PEER_HASH_FN(info_hash); + peer = NULL; + for (i = 0; i < PEER_HASH_SEARCH_DELTA; i ++) + { + if ((cur = torrent_hash[idx]) == NULL && free_idx == (unsigned int)-1) free_idx = idx; + if (cur != NULL && memcmp(cur->info_hash, akbuf_data(info_hash), ID_LEN) == 0) + { + peer = cur; + break; + } + idx ++; + if (idx >= PEER_HASH_SIZE) idx = 0; + } + if (i == PEER_HASH_SEARCH_DELTA) + { + if (free_idx == (unsigned int)-1) return NULL; + idx = free_idx; + } else + { + while (peer != NULL) + { + if (memcmp(peer->peer_id, akbuf_data(peer_id), ID_LEN) == 0) break; + peer = peer->next; + } + } + if (peer == NULL && alloc_new == 1) + { + peer = malloc(sizeof(peer_entry)); + peers_mem_size += sizeof(peer_entry); + num_peers ++; + peer->num_hits = peer->num_seeders = peer->num_leechers = peer->times_completed = 0; + peer->uploaded = peer->downloaded = 0; + peer->prev_uploaded = peer->prev_downloaded = 0; + peer->lastevent = 0; + peer->last_active = peer->prev_active = (time_t)0; + peer->is_complete = 0; + memcpy(peer->peer_id, akbuf_data(peer_id), ID_LEN); + memcpy(peer->info_hash, akbuf_data(info_hash), ID_LEN); + peer->hash_idx = idx; + peer->peer_hash_idx = pidx; + peer->prev = NULL; + if ((peer->next = torrent_hash[idx]) != NULL) peer->next->prev = peer; + torrent_hash[idx] = peer; + peer_hash[pidx] = peer; + } + return peer; +} +void peer_del(peer_entry *peer) +{ + if (peer_hash[peer->peer_hash_idx] == peer) peer_hash[peer->peer_hash_idx] = NULL; + if (peer->prev != NULL) peer->prev->next = peer->next; + if (peer->next != NULL) peer->next->prev = peer->prev; + if (peer == torrent_hash[peer->hash_idx]) + { + if (peer->next != NULL) + { + peer->next->num_seeders = peer->num_seeders; + peer->next->num_leechers = peer->num_leechers; + peer->next->times_completed = peer->times_completed; + } + torrent_hash[peer->hash_idx] = peer->next; + } + num_peers --; + free(peer); + peers_mem_size -= sizeof(peer_entry); +} +void scramble_peers(void) +{ + unsigned int i, j, newpeerscount; + peer_entry *peer, *prevpeer, *headpeer; + + DEBUGF("scrambling peers\n"); + for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = headpeer = torrent_hash[i]) != NULL) + { + if ((headpeer->num_seeders + headpeer->num_leechers) > 1) // cfg.tracker.respnum) + { + newpeerscount = 0; + peer = headpeer; + j = rand() % ((headpeer->num_seeders + headpeer->num_leechers) >> 1); + while (j > 0 && peer != NULL) { peer = peer->next; j --; } + if (peer == NULL || peer == headpeer) continue; + while (peer != NULL && newpeerscount < cfg.tracker.respnum) + { + prevpeer = peer; + peer = peer->next; + if ((rand() & 7) == 3) + { + if (prevpeer->prev != NULL) prevpeer->prev->next = prevpeer->next; + if (prevpeer->next != NULL) prevpeer->next->prev = prevpeer->prev; + prevpeer->next = headpeer->next; + prevpeer->prev = headpeer; + headpeer->next = prevpeer; + newpeerscount ++; + } + } + } + } +} +void send_peers(akbuf *outbuf, peer_entry *curpeer, unsigned int send_seeders, unsigned int do_compact) +{ + peer_entry *peer, *headpeer, *sendpeers[cfg.tracker.respnum]; + unsigned int num_sendpeers, i, j; + akbuf_ctxh ctx; + akbuf *buf; + + for (i = 0; i < cfg.tracker.respnum; i ++) sendpeers[i] = NULL; + num_sendpeers = 0; + peer = headpeer = torrent_hash[curpeer->hash_idx]; + if (headpeer != NULL && (j = headpeer->num_seeders + headpeer->num_leechers) >= cfg.tracker.respnum * 2) + { + for (i = rand() % (j - cfg.tracker.respnum); i > 0; i --) + { + if ((peer = peer->next) == NULL) peer = headpeer; + } + } + while (num_sendpeers < cfg.tracker.respnum && peer != NULL) + { + if (peer != curpeer) + { + for (i = 0; i < cfg.tracker.respnum; i ++) if (sendpeers[i] == peer) break; + if (i == cfg.tracker.respnum && (send_seeders == 1 || peer->is_seeder == 0)) + { + j = rand() & (cfg.tracker.respnum - 1); + if (sendpeers[j] == NULL) + { + sendpeers[j] = peer; + num_sendpeers ++; + peer->num_hits ++; + } + } + } + peer = peer->next; + } + ctx = akbuf_new_ctx(); + buf = akbuf_init(ctx, 0); + for (i = 0; i < cfg.tracker.respnum; i ++) + { + if (sendpeers[i] != NULL) + { + akbuf_set_idx(buf, 0); + if (do_compact == 1) + { + akbuf_append_data(outbuf, sendpeers[i]->ipraw, sizeof(sendpeers[i]->ipraw)); + akbuf_append_byte(outbuf, (sendpeers[i]->port >> 8) & 0xff); + akbuf_append_byte(outbuf, sendpeers[i]->port & 0xff); + } else + { + benc_key(buf, "ip", sendpeers[i]->ipstr); + benc_key_raw(buf, "peer id", sendpeers[i]->peer_id, ID_LEN); + benc_key_int(buf, "port", sendpeers[i]->port); + benc_out_dict(buf, outbuf); + } + sendpeers[i] = NULL; + } + } + akbuf_free_ctx(ctx); +} + +/* + * synchronization packet: + * each entry: + * [magic 'AKhc' 4 bytes] + * [info hash 20 bytes][peer id 20 bytes] + * [ip addr 4 bytes][port 2 bytes] + * [is seeder 1 byte] + * [padding 1 byte] + * total entry len: 48 bytes + */ + +#define SYNC_ENTRY_LEN (2 + ID_LEN * 2 + 4 + 2 + 1 + 1) +#define SYNC_ENTRY_MAGIC "AKhc" +#define SYNC_ENTRY_MAGIC_LEN 4 + +void handle_sync_packet(int fd, net_fd_entry *net_ent, void *dummy) +{ + ssize_t n; + struct sockaddr_in fromsin; + socklen_t sin_len; + akbuf_ctxh ctx; + akbuf *buf; + akbuf *peer_id, *info_hash; + unsigned char ipraw[4]; + unsigned int ipnum, port; + unsigned int is_seeder; + peer_entry *peer; + struct in_addr in; + + DEBUGF("got sync packet"); + ctx = akbuf_new_ctx(); + buf = akbuf_init(ctx, cfg.tracker.sync_size + 100); + sin_len = sizeof(fromsin); + if ((n = recvfrom(fd, akbuf_data(buf), akbuf_size(buf), 0, (struct sockaddr *)&fromsin, &sin_len)) <= 0) + { + akperror("tracker.c:handle_sync_packet():recvfrom()"); + akbuf_free_ctx(ctx); + return; + } + akbuf_set_idx(buf, n); + DEBUGF("got sync packet from %s:%u", inet_ntoa(fromsin.sin_addr), ntohs(fromsin.sin_port)); + DEBUGF("sync packet is %u bytes", akbuf_idx(buf)); + peer_id = akbuf_init(ctx, ID_LEN); + info_hash = akbuf_init(ctx, ID_LEN); + while (akbuf_idx(buf) >= SYNC_ENTRY_LEN) + { + if (memcmp(akbuf_data(buf), SYNC_ENTRY_MAGIC, SYNC_ENTRY_MAGIC_LEN) != 0) + { + DEBUGF("malformed sync entry, invalid magic %.2x%.2x%.2x%.2x", akbuf_data(buf)[0], akbuf_data(buf)[1], akbuf_data(buf)[2], akbuf_data(buf)[3]); + break; + } + akbuf_consume(buf, SYNC_ENTRY_MAGIC_LEN); + akbuf_set_data(info_hash, akbuf_data(buf), ID_LEN); akbuf_consume(buf, ID_LEN); + akbuf_set_data(peer_id, akbuf_data(buf), ID_LEN); akbuf_consume(buf, ID_LEN); + ipraw[0] = akbuf_eat_byte(buf); + ipraw[1] = akbuf_eat_byte(buf); + ipraw[2] = akbuf_eat_byte(buf); + ipraw[3] = akbuf_eat_byte(buf); + port = (akbuf_eat_byte(buf) << 8) | akbuf_eat_byte(buf); + is_seeder = akbuf_eat_byte(buf); + akbuf_eat_byte(buf); /* eat padding */ + DEBUGF("sync entry: ipraw %.2x%.2x%.2x%.2x port %x is_seeder %u", ipraw[0], ipraw[1], ipraw[2], ipraw[3], port, is_seeder); + if ((peer = peer_get(peer_id, info_hash, 1)) == NULL) + { + aklogf(LOG_ERROR, "tracker.c:handle_sync_packet(): peer_get() failed!"); + } else + { + peer->last_active = time(NULL); + peer->lastevent = 0; + peer->is_local = 0; + peer->is_seeder = is_seeder; + memcpy(peer->ipraw, ipraw, 4); + peer->port = port; + ipnum = (ipraw[0] << 24) | (ipraw[1] << 16) | (ipraw[2] << 8) | ipraw[3]; + in.s_addr = peer->ipnum = htonl(ipnum); + AKstrcpy(peer->ipstr, inet_ntoa(in)); + DEBUGF("peer->ipstr = '%s' peer->port = %u", peer->ipstr, peer->port); + DEBUGF("torrent hash idx %u peer hash idx %u", peer->hash_idx, peer->peer_hash_idx); + } + } + if (akbuf_idx(buf) > 0) + { + DEBUGF("malformed sync entry, %u byte(s) left", akbuf_idx(buf)); + } + akbuf_free_ctx(ctx); +} + +#ifdef INFOHASH_RESTRICTION +void read_infohash_file(void) +{ + unsigned int i; + int fd; + akbuf *readbuf; + akbuf_ctxh ctx; + + for (i = 0; i < PEER_HASH_SIZE; i ++) infohash_allowed[i] = 0; + if (cfg.tracker.infohash_file == NULL || (fd = open(cfg.tracker.infohash_file, O_RDONLY)) == -1) + { + for (i = 0; i < PEER_HASH_SIZE; i ++) infohash_allowed[i] = 1; + return; + } + ctx = akbuf_new_ctx(); + readbuf = akbuf_init(ctx, ID_LEN); + akbuf_set_idx(readbuf, ID_LEN); + while (read(fd, akbuf_data(readbuf), ID_LEN) == ID_LEN) + { + infohash_allowed[PEER_HASH_FN(readbuf)] = 1; + } + close(fd); + akbuf_free_ctx(ctx); +} +void init_infohash_restriction(void) +{ + unsigned int i; + + last_infohash_period = 0; + for (i = 0; i < PEER_HASH_SIZE; i ++) infohash_allowed[i] = 1; + //read_infohash_file(); +} +#endif + +void init_sync(void) +{ + int s; + struct sockaddr_in sin; + + syncbuf = akbuf_init(tracker_ctx, cfg.tracker.sync_size); + if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0 || s >= ASIO_MAX_FDS) + { + akperror("tracker.c:init_sync():socket()"); + return; + } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(cfg.tracker.sync_port); + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0) + { + akperror("tracker.c:init_sync():bind()"); + return; + } + net_set_fd(s, NET_FD_RAW, handle_sync_packet, NULL, 0); + aklogf(LOG_INFO, "Tracker sync listening on UDP port %u", cfg.tracker.sync_port); +} +void do_sync(void) +{ + int s; + struct sockaddr_in sin; + + if (akbuf_idx(syncbuf) == 0) return; + if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + { + akperror("tracker.c:do_sync():socket()"); + return; + } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = 0; + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0) + { + akperror("tracker.c:do_sync():bind()"); + close(s); + return; + } + sin.sin_addr.s_addr = cfg.tracker.sync_addr.s_addr; + sin.sin_port = htons(cfg.tracker.sync_port); + DEBUGF("syncing %u bytes", akbuf_idx(syncbuf)); + if (sendto(s, akbuf_data(syncbuf), akbuf_idx(syncbuf), 0, (struct sockaddr *)&sin, sizeof(sin)) != akbuf_idx(syncbuf)) + { + akperror("tracker.c:do_sync():sendto()"); + close(s); + return; + } + akbuf_set_idx(syncbuf, 0); + DEBUGF("sent sync packet"); +} +void sync_peer(peer_entry *peer) +{ + akbuf_append_str(syncbuf, SYNC_ENTRY_MAGIC); + akbuf_append_data(syncbuf, peer->info_hash, ID_LEN); + akbuf_append_data(syncbuf, peer->peer_id, ID_LEN); + akbuf_append_data(syncbuf, peer->ipraw, sizeof(peer->ipraw)); + akbuf_append_byte(syncbuf, (peer->port >> 8) & 0xff); + akbuf_append_byte(syncbuf, peer->port & 0xff); + akbuf_append_byte(syncbuf, peer->is_seeder & 0xff); + akbuf_append_byte(syncbuf, 42); + if (akbuf_idx(syncbuf) >= cfg.tracker.sync_size) do_sync(); +} + +void tracker_init(void) +{ + unsigned int i; + + tracker_ctx = akbuf_new_ctx(); + num_peers = num_torrents = num_seeders = num_leechers = 0; + peers_mem_size = 0; + last_period = time(NULL); + announce_count = scrape_count = status_count = peers_count = 0; + for (i = 0; i < PEER_HASH_SIZE; i ++) torrent_hash[i] = peer_hash[i] = NULL; + if (cfg.tracker.sync == 1) init_sync(); +#ifdef INFOHASH_RESTRICTION + init_infohash_restriction(); +#endif +} +void refresh_peer(peer_entry *peer) +{ + peer_entry *headpeer; + + headpeer = peer; + headpeer->num_seeders = headpeer->num_leechers = 0; + while (peer != NULL) + { + if (peer->is_seeder == 1) + { + num_seeders ++; + headpeer->num_seeders ++; + } else + { + num_leechers ++; + headpeer->num_leechers ++; + } + peer = peer->next; + } +} +void refresh_peers(void) +{ + unsigned int i; + peer_entry *peer, *headpeer, *prev; + + num_seeders = num_leechers = 0; + num_torrents = 0; + for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = headpeer = torrent_hash[i]) != NULL) + { + num_torrents ++; + headpeer->num_seeders = headpeer->num_leechers = 0; + while (peer != NULL) + { + time_t t; + + if (peer->is_seeder == 1) + { + num_seeders ++; + headpeer->num_seeders ++; + } else + { + num_leechers ++; + headpeer->num_leechers ++; + } + prev = peer; + peer = peer->next; + t = time(NULL) - prev->last_active; + if ((prev->lastevent != EVENT_STOPPED && t >= cfg.tracker.timeout) || (prev->lastevent == EVENT_STOPPED && t >= cfg.tracker.stopped_timeout)) + { + peer_del(prev); + } + } + } +} + +/* + * GET /peers?[reset] + * reset: reset ul/dl + */ +void tracker_serve_peers(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) +{ + unsigned int i; + peer_entry *peer; + unsigned int do_reset, only_stopped; + + peers_count ++; + + /* + * [info hash]:[ip]:[uploaded]:[downloaded]:[last event]\n + */ + do_reset = (akbuf_table_entry_get(http_ent->args, "reset") != NULL)? 1 : 0; + only_stopped = (akbuf_table_entry_get(http_ent->args, "stopped") != NULL)? 1 : 0; + tracker_http_response(fd, http_ent, net_ent, HTTP_OK, "text/plain"); + for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) + { + while (peer != NULL) + { + if (only_stopped == 0 || peer->lastevent == EVENT_STOPPED) + { + akbuf_urlencode_data(peer->info_hash, ID_LEN, net_ent->sendbuf); + akbuf_append_byte(net_ent->sendbuf, ':'); + akbuf_appendf(net_ent->sendbuf, "%s:%llu:%llu:", peer->ipstr, peer->uploaded, peer->downloaded); + akbuf_appendf(net_ent->sendbuf, "%u", peer->lastevent); + akbuf_append_byte(net_ent->sendbuf, '\n'); + if (do_reset == 1) peer->uploaded = peer->downloaded = peer->prev_uploaded = peer->prev_downloaded = 0; + } + peer = peer->next; + } + } +} +void serve_status_raw(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) +{ + unsigned int i, do_reset; + peer_entry *peer; + + /* + * [info hash]:[num seeders]:[num leechers]:[times completed]:[unixtime of last activity]\n + * ...repeat ad nauseam... + * the info hash is ID_LEN bytes raw data, the rest are unsigned base10 ints + */ + tracker_http_response(fd, http_ent, net_ent, HTTP_OK, "text/plain"); + do_reset = (akbuf_table_entry_get(http_ent->args, "reset") != NULL)? 1 : 0; + for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) + { + akbuf_urlencode_data(peer->info_hash, ID_LEN, net_ent->sendbuf); + akbuf_append_byte(net_ent->sendbuf, ':'); + akbuf_appendf(net_ent->sendbuf, "%u:%u:%u:", peer->num_seeders, peer->num_leechers, peer->times_completed); + if (do_reset == 1) peer->times_completed = 0; + akbuf_appendf(net_ent->sendbuf, "%u", (unsigned int)peer->last_active); + akbuf_append_byte(net_ent->sendbuf, '\n'); + } +} +void serve_status_html(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) +{ + tracker_http_response(fd, http_ent, net_ent, HTTP_OK, "text/html"); + akbuf_appendf(net_ent->sendbuf, + "\n" + "Hypercube tracker status\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n", + num_peers, num_seeders, num_leechers, + num_torrents, + announce_count, scrape_count, status_count, peers_count, (unsigned int)(time(NULL) - last_period), + PEER_HASH_SIZE, sizeof(torrent_hash), + peers_mem_size); + if (akbuf_table_entry_get(http_ent->args, "rate") != NULL) + { + unsigned long long total_rate, cur_rate; + unsigned int i; + peer_entry *peer; + + total_rate = 0; + for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) + { + while (peer != NULL) + { + unsigned long long delta; + + if (peer->lastevent != EVENT_STARTED && peer->lastevent != EVENT_STOPPED && peer->lastevent != EVENT_COMPLETED && peer->prev_active != (time_t)0 && peer->last_active != (time_t)0) + { + delta = peer->last_active - peer->prev_active; + if (delta != 0 && peer->uploaded != 0 && peer->prev_uploaded != 0 && peer->downloaded != 0 && peer->prev_downloaded != 0 && peer->uploaded > peer->prev_uploaded && peer->downloaded > peer->prev_downloaded) + { + cur_rate = (peer->uploaded - peer->prev_uploaded + peer->downloaded - peer->prev_downloaded) / delta; + if (cur_rate < 10485760) total_rate += cur_rate; + } + } + peer = peer->next; + } + } + total_rate /= 1048576; + akbuf_appendf(net_ent->sendbuf, "\n", total_rate); + } + if (akbuf_table_entry_get(http_ent->args, "show_torrents") != NULL) + { + akbuf_ctxh ctx; + akbuf *buf; + unsigned int i; + peer_entry *peer; + + ctx = akbuf_new_ctx(); + buf = akbuf_init(ctx, 0); + + akbuf_appendf(net_ent->sendbuf, + "\n"); + for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) + { + akbuf_set_idx(buf, 0); + akbuf_urlencode_data(peer->info_hash, ID_LEN, buf); + akbuf_asciiz(buf); + DEBUGF("html urlenc: last byte %.2x str [%s]", peer->info_hash[ID_LEN - 1], akbuf_data(buf)); + akbuf_appendf(net_ent->sendbuf, + "\n" + "\n" + "\n" + "\n" + "\n", + akbuf_data(buf), + peer->num_seeders, peer->num_leechers, peer->times_completed, + get_date_str(peer->last_active), + peer->hash_idx); + } + akbuf_free_ctx(ctx); + } else + { + akbuf_appendf(net_ent->sendbuf, "\n"); + } + akbuf_appendf(net_ent->sendbuf, "
Statistics  
 Number of peers/seeders/leechers%u/%u/%u
 Number of torrents%u
 Number of announce/scrape/status/peers%u/%u/%u/%u in %u sec(s)
Tracker info  
 Version" SERVER_VERSION "
 Peer hash table buckets/size0x%x/%u byte(s)
 Size of peers in memory%u byte(s)
 Total rate (MB/sec)%llu
TorrentsHide 
 Info hash%s
 Seeders/Leechers/Completed%u/%u/%u
 Last activity%s
 Peer hash bucket #0x%x
 ------------------------------- 
TorrentsShow 
\n\n\n"); + +} +/* + * GET /status?[rate]|[[norefresh]&[raw]&[reset]] + * norefresh: don't refresh peers list + * raw: raw format + * reset: reset completed + */ +void tracker_serve_status(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) +{ + + status_count ++; + + if (cfg.tracker.sync == 1) do_sync(); + + if (akbuf_table_entry_get(http_ent->args, "norefresh") == NULL) refresh_peers(); + if (akbuf_table_entry_get(http_ent->args, "raw") != NULL) + { + serve_status_raw(fd, http_ent, net_ent); + } else + { + serve_status_html(fd, http_ent, net_ent); + } +} +void scrape_out(peer_entry *peer, akbuf *outbuf) +{ + akbuf_ctxh ctx; + akbuf *buf; + + refresh_peer(peer); + ctx = akbuf_new_ctx(); + buf = akbuf_init(ctx, 0); + benc_raw(outbuf, peer->info_hash, ID_LEN); + benc_key_int(buf, "complete", peer->num_seeders); + benc_key_int(buf, "downloaded", peer->times_completed); + benc_key_int(buf, "incomplete", peer->num_leechers); + benc_out_dict(buf, outbuf); + akbuf_free_ctx(ctx); +} +void tracker_serve_scrape(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) +{ + akbuf_ctxh ctx; + akbuf *info_hash; + akbuf *outbuf, *filesbuf, *buf; + peer_entry *peer; + unsigned int i; + +#define SCRAPE_ERR(msg)\ + {\ + benc_key(outbuf, "failure reason", (msg));\ + tracker_benc_response(fd, http_ent, net_ent, outbuf);\ + akbuf_free_ctx(ctx);\ + return;\ + } + + scrape_count ++; + + ctx = akbuf_new_ctx(); + outbuf = akbuf_init(ctx, 0); + filesbuf = akbuf_init(ctx, 0); + buf = akbuf_init(ctx, 0); + if ((info_hash = akbuf_table_entry_get(http_ent->args, "info_hash")) == NULL) + { + for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = torrent_hash[i]) != NULL) + { + akbuf_set_idx(buf, 0); + scrape_out(peer, filesbuf); + peer = peer->next; + } + } else + { + if (akbuf_idx(info_hash) != ID_LEN) SCRAPE_ERR("invalid info_hash"); + if ((peer = torrent_hash[PEER_HASH_FN(info_hash)]) != NULL) + { + /* XXX hash colls */ + akbuf_set_idx(buf, 0); + scrape_out(peer, filesbuf); + } + } + benc_str(outbuf, "files"); + benc_out_dict(filesbuf, outbuf); + tracker_benc_response(fd, http_ent, net_ent, outbuf); + akbuf_free_ctx(ctx); +} +void tracker_serve_announce(int fd, http_fd_entry *http_ent, net_fd_entry *net_ent) +{ + akbuf_ctxh ctx; + akbuf *outbuf, *peerbuf, *peersbuf; + akbuf *info_hash, *peer_id, *ipbuf, *portbuf, *ulbuf, *dlbuf, *leftbuf, *eventbuf; + akbuf *uabuf; + int i; + unsigned int j, c; + unsigned int curevent, do_compact; + peer_entry *curpeer; + struct in_addr ipaddr; + +#define AN_ERR(msg)\ + {\ + akbuf_set_idx(outbuf, 0);\ + benc_key(outbuf, "failure reason", (msg));\ + tracker_benc_response(fd, http_ent, net_ent, outbuf);\ + akbuf_free_ctx(ctx);\ + return;\ + } + + announce_count ++; + + DEBUGF("tracker: doing announce"); + ctx = akbuf_new_ctx(); + outbuf = akbuf_init(ctx, 0); + //benc_key_int(outbuf, "interval", cfg.tracker.interval); + + if ((info_hash = akbuf_table_entry_get(http_ent->args, "info_hash")) == NULL) AN_ERR("need info_hash"); +#ifdef INFOHASH_RESTRICTION + if (infohash_allowed[PEER_HASH_FN(info_hash)] == 0) AN_ERR("this tracker is for torrents on TPB only"); +#endif + if ((peer_id = akbuf_table_entry_get(http_ent->args, "peer_id")) == NULL) AN_ERR("need peer_id"); + if ((portbuf = akbuf_table_entry_get(http_ent->args, "port")) == NULL) AN_ERR("need port"); + if ((ulbuf = akbuf_table_entry_get(http_ent->args, "uploaded")) == NULL) AN_ERR("need uploaded"); + if ((dlbuf = akbuf_table_entry_get(http_ent->args, "downloaded")) == NULL) AN_ERR("need downloaded"); + if ((leftbuf = akbuf_table_entry_get(http_ent->args, "left")) == NULL) AN_ERR("need left"); + ipbuf = akbuf_table_entry_get(http_ent->args, "ip"); + + eventbuf = akbuf_table_entry_get(http_ent->args, "event"); + curevent = EVENT_NONE; + if (eventbuf != NULL && akbuf_idx(eventbuf) > 0) + { + akbuf_asciiz(eventbuf); + if (strncasecmp(akbuf_data(eventbuf), "sta", 3) == 0) curevent = EVENT_STARTED; + else if (strncasecmp(akbuf_data(eventbuf), "com", 3) == 0) curevent = EVENT_COMPLETED; + else if (strncasecmp(akbuf_data(eventbuf), "sto", 3) == 0) curevent = EVENT_STOPPED; + } + if (curevent != EVENT_NONE) DEBUGF("got event %u", curevent); + if (akbuf_idx(info_hash) != ID_LEN || akbuf_idx(peer_id) != ID_LEN) AN_ERR("invalid info_hash and/or peer_id"); + + do_compact = (akbuf_table_entry_get(http_ent->args, "compact") != NULL)? 1 : 0; + + if (curevent == EVENT_STOPPED || curevent == EVENT_COMPLETED) + { + if ((curpeer = peer_get(peer_id, info_hash, 0)) == NULL) + { + DEBUGF("event %u for unknown peer, ignoring", curevent); + benc_str(outbuf, "peers"); + akbuf_appendf(outbuf, "le"); + tracker_benc_response(fd, http_ent, net_ent, outbuf); + akbuf_free_ctx(ctx); + return; + } + } else + { + if ((curpeer = peer_get(peer_id, info_hash, 1)) == NULL) AN_ERR("too many peers"); + } + if (curevent == EVENT_STARTED || curpeer->lastevent == EVENT_STARTED) + { + benc_key_int(outbuf, "interval", cfg.tracker.init_interval); + } else + { + benc_key_int(outbuf, "interval", cfg.tracker.interval); + } + curpeer->is_local = 1; + j = 0; + for (i = 0; i < akbuf_idx(portbuf); i ++) + { + akbuf_get_byte(portbuf, i, c); + if (c < '0' || c > '9') break; + j *= 10; + j += c - '0'; + } + /* causes problems for some clients if (j > 0xffff || j < 0x400) AN_ERR("invalid port"); */ + curpeer->port = j; + + if (ipbuf != NULL && akbuf_idx(ipbuf) > 1) + { + akbuf_asciiz(ipbuf); + if ((ipaddr.s_addr = inet_addr(akbuf_data(ipbuf))) == INADDR_NONE) + { + ipbuf = NULL; + } else + { + j = ntohl(ipaddr.s_addr); + if ((j >= 0x0a000000 && j <= 0x0affffff) || + (j >= 0xac100000 && j <= 0xac1fffff) || + (j >= 0xc0a80000 && j <= 0xc0a8ffff)) ipbuf = NULL; + } + } + if (ipbuf == NULL || akbuf_idx(ipbuf) <= 1) + { + ipbuf = akbuf_init(ctx, 0); + akbuf_clone(ipbuf, net_ent->peerbuf); + if ((i = akbuf_chr(ipbuf, ':')) != -1) akbuf_set_idx(ipbuf, i); + } + akbuf_asciiz(ipbuf); + AKstrcpy(curpeer->ipstr, akbuf_data(ipbuf)); + j = curpeer->ipnum = ntohl(inet_addr(curpeer->ipstr)); + curpeer->ipraw[0] = (j >> 24) & 0xff; + curpeer->ipraw[1] = (j >> 16) & 0xff; + curpeer->ipraw[2] = (j >> 8) & 0xff; + curpeer->ipraw[3] = j & 0xff; + + curpeer->prev_active = curpeer->last_active; + curpeer->last_active = time(NULL); + +#if 0 + if (num_peers == 1000000) + { + FILE *f; + + if (cfg.tracker.statslog != NULL && (f = fopen(cfg.tracker.statslog, "a+")) != NULL) + { + fprintf(f, "!!! Reached 1 million peers @ %s: %s %u\n", get_now_date_str(), curpeer->ipstr, curpeer->port); + fflush(f); + fclose(f); + } + } +#endif + + j = 0; + for (i = 0; i < akbuf_idx(leftbuf); i ++) + { + akbuf_get_byte(leftbuf, i, c); + if (c < '0' || c > '9') break; + if (j >= 0x19999999) { j = (unsigned int)-1; break; } + j *= 10; + j += c - '0'; + } + DEBUGF("got left %u", j); + curpeer->is_seeder = (j == 0)? 1 : 0; + + curpeer->prev_uploaded = curpeer->uploaded; + curpeer->uploaded = 0; + for (i = 0; i < akbuf_idx(ulbuf); i ++) + { + akbuf_get_byte(ulbuf, i, c); + if (c < '0' || c > '9') break; + curpeer->uploaded *= 10; + curpeer->uploaded += c - '0'; + } + curpeer->prev_downloaded = curpeer->downloaded; + curpeer->downloaded = 0; + for (i = 0; i < akbuf_idx(dlbuf); i ++) + { + akbuf_get_byte(dlbuf, i, c); + if (c < '0' || c > '9') break; + curpeer->downloaded *= 10; + curpeer->downloaded += c - '0'; + } + DEBUGF("ul %llu dl %llu", curpeer->uploaded, curpeer->downloaded); + + curpeer->lastevent = curevent; + + if ((uabuf = akbuf_table_entry_get(http_ent->headers, "User-Agent")) != NULL) + { + if (akbuf_idx(uabuf) >= 12 && memcmp(akbuf_data(uabuf), "Ratio Fucker", 12) == 0) + { + akbuf_asciiz(net_ent->peerbuf); + aklogf(LOG_INFO, "Cheater: Ratio Fucker request from %s", akbuf_data(net_ent->peerbuf)); + curpeer->uploaded = 0; + curpeer->downloaded = 0; + } + } + + i = 0; + peerbuf = akbuf_init(ctx, 0); + peersbuf = akbuf_init(ctx, 0); + + if (curevent == EVENT_COMPLETED) + { + torrent_hash[curpeer->hash_idx]->times_completed ++; + curpeer->is_complete = 1; + } + send_peers(peersbuf, curpeer, (curpeer->is_seeder == 1)? 0 : 1, do_compact); + if (do_compact == 1) + { + benc_key_buf(outbuf, "peers", peersbuf); + } else + { + benc_str(outbuf, "peers"); + benc_out_list(peersbuf, outbuf); + } + tracker_benc_response(fd, http_ent, net_ent, outbuf); + if (curevent != EVENT_STOPPED && cfg.tracker.sync == 1) sync_peer(curpeer); + + akbuf_free_ctx(ctx); +} +void update_stats(void) +{ +#ifdef WITH_MYSQL + MYSQL sql_conn; + akbuf *buf, *sqlbuf; + akbuf_ctxh ctx; + unsigned int i; + peer_entry *peer, *headpeer; + + if (cfg.tracker.sql_stats == 0) return; + DEBUGF("doing update_stats()"); + if (fork() != 0) return; + + signal(SIGALRM, exit); + alarm(cfg.tracker.period); + DEBUGF("in update_stats() child"); + //while (1) sleep(1); + mysql_init(&sql_conn); + if (mysql_real_connect(&sql_conn, cfg.tracker.sql_host, cfg.tracker.sql_user, cfg.tracker.sql_pass, cfg.tracker.sql_db, 0, NULL, 0) == NULL) + { + aklogf(LOG_ERROR, "Couldn't connect to MySQL server: %s", mysql_error(&sql_conn)); + exit(1); + } + ctx = akbuf_new_ctx(); + buf = akbuf_init(ctx, 0); + sqlbuf = akbuf_init(ctx, 0); + akbuf_sprintf(sqlbuf, "UPDATE hc_stats SET seeders='%u', leechers='%u', num_torrents='%u'", num_seeders, num_leechers, num_torrents); + akbuf_asciiz(sqlbuf); + DEBUGF("sql query (hc_stats) [%s]", akbuf_data(sqlbuf)); + if (mysql_real_query(&sql_conn, akbuf_data(sqlbuf), akbuf_idx(sqlbuf)) != 0) + { + DEBUGF("SQL UPDATE query failed: %s", mysql_error(&sql_conn)); + } + for (i = 0; i < PEER_HASH_SIZE; i ++) if ((peer = headpeer = torrent_hash[i]) != NULL) + { + akbuf_set_idx(buf, 0); + akbuf_set_idx(sqlbuf, 0); + akbuf_base64encode_data(peer->info_hash, ID_LEN, buf); + akbuf_asciiz(buf); + akbuf_sprintf(sqlbuf, "UPDATE torrents SET seeders='%u', leechers='%u', last_active='%u' WHERE info_hash='%s'", peer->num_seeders, peer->num_leechers, peer->last_active, akbuf_data(buf)); + akbuf_asciiz(sqlbuf); + DEBUGF("sql query (torrents) [%s]", akbuf_data(sqlbuf)); + if (mysql_real_query(&sql_conn, akbuf_data(sqlbuf), akbuf_idx(sqlbuf)) != 0) + { + DEBUGF("SQL UPDATE query failed: %s", mysql_error(&sql_conn)); + } + while (peer != NULL) + { + if (peer->is_complete == 1) + { + peer->is_complete = 0; + akbuf_set_idx(buf, 0); + akbuf_set_idx(sqlbuf, 0); + akbuf_base64encode_data(peer->info_hash, ID_LEN, buf); + akbuf_asciiz(buf); + akbuf_sprintf(sqlbuf, "UPDATE torrents SET num_downloads=num_downloads+1 WHERE info_hash='%s'", akbuf_data(buf)); + akbuf_asciiz(sqlbuf); + DEBUGF("sql query (torrents complete) [%s]", akbuf_data(sqlbuf)); + if (mysql_real_query(&sql_conn, akbuf_data(sqlbuf), akbuf_idx(sqlbuf)) != 0) + { + DEBUGF("SQL UPDATE query failed: %s", mysql_error(&sql_conn)); + } + } else if (peer->lastevent == EVENT_STOPPED) + { + akbuf_set_idx(buf, 0); + akbuf_set_idx(sqlbuf, 0); + akbuf_base64encode_data(peer->info_hash, ID_LEN, buf); + akbuf_asciiz(buf); + akbuf_sprintf(sqlbuf, "UPDATE users SET uploaded=uploaded+'%llu', downloaded=downloaded+'%llu' WHERE ip='%u'", peer->uploaded, peer->downloaded, peer->ipnum); + akbuf_asciiz(sqlbuf); + DEBUGF("sql query (peers) [%s]", akbuf_data(sqlbuf)); + if (mysql_real_query(&sql_conn, akbuf_data(sqlbuf), akbuf_idx(sqlbuf)) != 0) + { + DEBUGF("SQL UPDATE query failed: %s", mysql_error(&sql_conn)); + } else + { + peer->uploaded = peer->downloaded = 0; + peer->prev_uploaded = peer->prev_downloaded = 0; + } + peer->uploaded = peer->downloaded = 0; + peer->prev_uploaded = peer->prev_downloaded = 0; + + } + peer = peer->next; + } + } + akbuf_free_ctx(ctx); + exit(0); +#endif +} +void tracker_periodic(void) +{ + time_t t; + FILE *f; + + //scramble_peers(); +#ifdef INFOHASH_RESTRICTION + if ((t = time(NULL) - last_infohash_period) >= cfg.tracker.infohash_interval) + { + read_infohash_file(); + last_infohash_period = time(NULL); + } +#endif + if ((t = time(NULL) - last_period) < cfg.tracker.period) return; + if (cfg.tracker.statslog != NULL && (f = fopen(cfg.tracker.statslog, "a+")) != NULL) + { + fprintf(f, "%s %u %u %u %u / %u\n", get_now_date_str(), announce_count, scrape_count, status_count, peers_count, (unsigned int)t); + fflush(f); + fclose(f); + } + announce_count = scrape_count = status_count = peers_count = 0; + refresh_peers(); + update_stats(); + last_period = time(NULL); +} diff --git a/tracker.cfg.dist b/tracker.cfg.dist new file mode 100644 index 0000000..1eee18e --- /dev/null +++ b/tracker.cfg.dist @@ -0,0 +1,33 @@ + +# Announce interval. +tracker_interval 420 +# Initial announce interval +tracker_init_interval 120 + +# Timeout if no activity from peer. +tracker_timeout 666 + +# Maximum number of peers in an announce response. +tracker_respnum 64 + +# Tracker period (peer refresh) interval. +tracker_period 600 + +# Tracker statistics log file +tracker_statslog statslog + +no tracker_sql_stats +tracker_sql_host localhost +tracker_sql_db tracker +tracker_sql_user tracker +tracker_sql_pass trawk123 + +# Synchronize with other trackers? +tracker_sync +# Synchronization interval +tracker_sync_interval 15 +# Max size of a synchronization packet +tracker_sync_size 1400 +# Address to send synchronization packets to +tracker_sync_addr 192.168.42.42 +#tracker_sync_addr 255.255.255.255 \ No newline at end of file diff --git a/tracker.h b/tracker.h new file mode 100644 index 0000000..f6a8920 --- /dev/null +++ b/tracker.h @@ -0,0 +1,48 @@ +#define ID_LEN 20 + +#define PEER_HASH_SIZE 0x100000 +#define PEER_HASH_SEARCH_DELTA 0x4 +#define PEER_HASH_FN(info_hash) (hash_buf(info_hash) & (PEER_HASH_SIZE - 1)) + +enum { EVENT_NONE, EVENT_STARTED, EVENT_COMPLETED, EVENT_STOPPED }; + +struct ipmask_entry_s +{ + unsigned char *ipaddr; + unsigned char *netmask; +}; +typedef struct ipmask_entry_s ipmask_entry; + +struct peer_entry_s +{ + unsigned int hash_idx; + unsigned int peer_hash_idx; + unsigned int num_hits; + unsigned int num_seeders, num_leechers, times_completed; + unsigned int is_seeder; + unsigned char info_hash[ID_LEN]; + unsigned char peer_id[ID_LEN]; + time_t prev_active; + time_t last_active; + unsigned char ipstr[64]; + unsigned int ipnum; + unsigned char ipraw[4]; + unsigned int port; + unsigned long long uploaded; + unsigned long long downloaded; + unsigned long long prev_uploaded; + unsigned long long prev_downloaded; + unsigned int lastevent; + unsigned int is_local; + unsigned int is_complete; + struct peer_entry_s *next; + struct peer_entry_s *prev; +}; +typedef struct peer_entry_s peer_entry; + +void tracker_init(void); +void tracker_periodic(void); +void tracker_serve_announce(int, http_fd_entry *, net_fd_entry *); +void tracker_serve_scrape(int, http_fd_entry *, net_fd_entry *); +void tracker_serve_status(int, http_fd_entry *, net_fd_entry *); +void tracker_serve_peers(int, http_fd_entry *, net_fd_entry *);