commit 79732cf0d63d87c4d403e022d51f8aba73d257c2 Author: Darren VanBuren Date: Mon Sep 12 22:08:46 2016 -0700 Initial Commit of hypercube 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 *);