From ad541c67761b8aa6da1566802c347cac4018d0c2 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Mon, 2 Nov 2026 20:50:24 +0100 Subject: Move the build sources into src/ --- ot_http.c | 810 -------------------------------------------------------------- 1 file changed, 810 deletions(-) delete mode 100644 ot_http.c (limited to 'ot_http.c') diff --git a/ot_http.c b/ot_http.c deleted file mode 100644 index 96b8f4e..0000000 --- a/ot_http.c +++ /dev/null @@ -1,810 +0,0 @@ -/* This software was written by Dirk Engling - It is considered beerware. Prost. Skol. Cheers or whatever. */ - -/* System */ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Opentracker */ -#include "ot_accesslist.h" -#include "ot_fullscrape.h" -#include "ot_http.h" -#include "ot_iovec.h" -#include "ot_ip.h" -#include "ot_mutex.h" -#include "ot_workqueue.h" -#include "ot_scan.h" -#include "ot_event.h" -#include "ot_fdstate.h" -#include "ot_vector.h" -#include "ot_fmt.h" -#include "ot_stats.h" -#include "ot_urlencoded.h" -#include "trackerlogic.h" - -#define OT_MAXMULTISCRAPE_COUNT 64 -extern const char *g_redirecturl; - -const char *g_stats_path; -ssize_t g_stats_path_len; - -enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; - -struct http_data *http_adopt(const int64_t sock) { - struct http_data *cookie = fdstate_cookie(sock), *heap; - int interest; - - if (!cookie || !(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) - return cookie; - - if (!(heap = malloc(sizeof(*heap)))) - return NULL; - - *heap = *cookie; - heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK; - - /* Publishing the cookie is what lets another thread reach this connection, - so nothing may touch it afterwards, not even to read what it asked for */ - interest = heap->interest; - fdstate_set_cookie((int)sock, heap); - - if (event_add((int)sock, interest, fdstate_generation((int)sock)) == -1) { - fdstate_set_cookie((int)sock, cookie); - free(heap); - return NULL; - } - return heap; -} - -/* Give up a connection: the one place that knows everything hanging off it. */ -void http_close(const int64_t sock) { - struct http_data *cookie = fdstate_cookie(sock); - - fdstate_close((int)sock); - - if (cookie) { - if (cookie->batch) - batch_reset(cookie->batch); - free(cookie->batch); - buf_reset(&cookie->request); - if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) - workqueue_canceltask(sock); - if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) - free(cookie); - } - - event_close((int)sock); -} - -static void http_senddata(const int64_t sock, struct ot_workstruct *ws) { - struct http_data *cookie = fdstate_cookie(sock); - ssize_t written_size; - - if (!cookie) { - http_close(sock); - return; - } - - /* whoever sends data is not interested in its input buffer */ - if (ws->keep_alive && ws->header_size != ws->request_size) { - size_t rest = ws->request_size - ws->header_size; - if (cookie->request.data) { - memmove(cookie->request.data, ws->request + ws->header_size, rest); - buf_truncate(&cookie->request, rest); - } else if (buf_cat(&cookie->request, ws->request + ws->header_size, rest)) { - /* Without room for what the client pipelined behind this request, answer - this one and let the connection close, rather than keeping it open - around a request we have silently dropped */ - buf_reset(&cookie->request); - ws->keep_alive = 0; - } - } else - buf_reset(&cookie->request); - - written_size = write(sock, ws->reply, ws->reply_size); - /* A send buffer with no room in it is not a failure: nothing went out, so - the whole reply goes into the batch below and leaves when the socket - drains. Only a real error ends the connection here. */ - if (written_size < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) - written_size = 0; - - if ((written_size < 0) || ((written_size == ws->reply_size) && !ws->keep_alive)) { - http_close(sock); - return; - } - - if (written_size < ws->reply_size) { - char *outbuf; - - if (!(outbuf = malloc(ws->reply_size - written_size))) { - http_close(sock); - return; - } - - memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size); - if (!cookie->batch) { - cookie->batch = calloc(1, sizeof(ot_batch)); - if (!cookie->batch) { - free(outbuf); - http_close(sock); - return; - } - } - - batch_add(cookie->batch, outbuf, ws->reply_size - written_size); - - /* writeable short data sockets just have a tcp timeout */ - if (!ws->keep_alive) - fdstate_set_expiry(sock, 0); - http_interest_set(sock, cookie, EVENT_OUT); - } -} - -#define HTTPERROR_302 return http_issue_error(sock, ws, CODE_HTTPERROR_302) -#define HTTPERROR_400 return http_issue_error(sock, ws, CODE_HTTPERROR_400) -#define HTTPERROR_400_PARAM return http_issue_error(sock, ws, CODE_HTTPERROR_400_PARAM) -#define HTTPERROR_400_COMPACT return http_issue_error(sock, ws, CODE_HTTPERROR_400_COMPACT) -#define HTTPERROR_400_DOUBLEHASH return http_issue_error(sock, ws, CODE_HTTPERROR_400_PARAM) -#define HTTPERROR_402_NOTMODEST return http_issue_error(sock, ws, CODE_HTTPERROR_402_NOTMODEST) -#define HTTPERROR_403_IP return http_issue_error(sock, ws, CODE_HTTPERROR_403_IP) -#define HTTPERROR_404 return http_issue_error(sock, ws, CODE_HTTPERROR_404) -#define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500) -/* A connection is either being read or being written, never both, so what it - waits for is set outright rather than added to and taken away again. The - event layer only hears about it when the answer changes. */ -void http_interest_set(const int64_t sock, struct http_data *cookie, int what) { - if (cookie->interest == what) - return; - cookie->interest = what; - /* Still on a stack, so not in the event loop yet: whoever adopts this - connection registers whatever it asked for last */ - if (cookie->flag & STRUCT_HTTP_FLAG_ONSTACK) - return; - event_modify((int)sock, what, fdstate_generation((int)sock)); -} - -ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { - const char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required", - "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"}; - const char *title = error_code[code]; - - ws->reply = ws->outbuf; - if (code == CODE_HTTPERROR_302) - ws->reply_size = snprintf(ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 302 Found\r\nContent-Length: 0\r\nLocation: %s\r\n\r\n", g_redirecturl); - else - ws->reply_size = snprintf(ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 %s\r\nContent-Type: text/html\r\nContent-Length: %zd\r\n\r\n%s\n", title, - strlen(title) + 16 - 4, title + 4); - -#ifdef _DEBUG_HTTPERROR - fprintf(stderr, "DEBUG: invalid request was: %s\n", ws->debugbuf); -#endif - stats_issue_event(ws, EVENT_FAILED, FLAG_TCP, code); - http_senddata(sock, ws); - return ws->reply_size = -2; -} - -ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) { - struct http_data *cookie = fdstate_cookie(sock); - char *header; - const char *encoding = ""; - size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector); - - /* No cookie? Bad socket. Leave. */ - if (!cookie) { - iovec_free(&iovec_entries, &iovector); - HTTPERROR_500; - } - - /* Descriptor numbers get recycled, so a result can only belong to this - connection if this connection is in fact waiting for one. */ - if (!(cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))) { - iovec_free(&iovec_entries, &iovector); - return 0; - } - - /* If this socket collected request in a buffer, free it now */ - buf_reset(&cookie->request); - - /* If we came here, wait for the answer is over */ - if (cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK) { - http_interest_set(sock, cookie, 0); - cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK; - } - - if (iovec_entries) { - - if (cookie->flag & STRUCT_HTTP_FLAG_ZSTD) - encoding = "Content-Encoding: zstd\r\n"; - else if (cookie->flag & STRUCT_HTTP_FLAG_GZIP) - encoding = "Content-Encoding: gzip\r\n"; - else if (cookie->flag & STRUCT_HTTP_FLAG_BZIP2) - encoding = "Content-Encoding: bzip2\r\n"; - - if (!(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED)) - header_size = asprintf(&header, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n%sContent-Length: %zd\r\n\r\n", encoding, size); - else { - if (!(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) { - header_size = - asprintf(&header, "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\n%sTransfer-Encoding: chunked\r\n\r\n%zx\r\n", encoding, size); - cookie->flag |= STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; - } else - header_size = asprintf(&header, "%zx\r\n", size); - } - if (!header) { - iovec_free(&iovec_entries, &iovector); - HTTPERROR_500; - } - - if (!cookie->batch) { - cookie->batch = calloc(1, sizeof(ot_batch)); - if (!cookie->batch) { - free(header); - iovec_free(&iovec_entries, &iovector); - HTTPERROR_500; - } - } - - batch_add(cookie->batch, header, header_size); - batch_add_vector(cookie->batch, iovector, iovec_entries); - free(iovector); - - if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) - batch_add_copy(cookie->batch, "\r\n", 2); - } - - if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) { - batch_add_copy(cookie->batch, "0\r\n\r\n", 5); - cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; - } - - fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT_SEND)); - http_interest_set(sock, cookie, EVENT_OUT); - return 0; -} - -static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { - static const ot_keywords keywords_main[] = {{"mode", 1}, {"format", 2}, {"info_hash", 3}, {NULL, -3}}; - static const ot_keywords keywords_mode[] = {{"peer", TASK_STATS_PEERS}, - {"conn", TASK_STATS_CONNS}, - {"scrp", TASK_STATS_SCRAPE}, - {"udp4", TASK_STATS_UDP}, - {"tcp4", TASK_STATS_TCP}, - {"torr", TASK_STATS_TORRENTS}, - {"fscr", TASK_STATS_FULLSCRAPE}, - {"s24s", TASK_STATS_SLASH24S}, - {"tpbs", TASK_STATS_TPB}, - {"herr", TASK_STATS_HTTPERRORS}, - {"completed", TASK_STATS_COMPLETED}, - {"top100", TASK_STATS_TOP100}, - {"top10", TASK_STATS_TOP10}, - {"renew", TASK_STATS_RENEW}, - {"syncs", TASK_STATS_SYNCS}, - {"everything", TASK_STATS_EVERYTHING}, - {"statedump", TASK_FULLSCRAPE_TRACKERSTATE}, - {"woodpeckers", TASK_STATS_WOODPECKERS}, -#ifdef WANT_LOG_NUMWANT - {"numwants", TASK_STATS_NUMWANTS}, -#endif - {NULL, -3}}; - static const ot_keywords keywords_format[] = {{"bin", TASK_FULLSCRAPE_TPB_BINARY}, {"ben", TASK_FULLSCRAPE}, - {"url", TASK_FULLSCRAPE_TPB_URLENCODED}, {"txt", TASK_FULLSCRAPE_TPB_ASCII}, - {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}}; - - struct http_data *cookie = fdstate_cookie(sock); - char *reply; - int mode = TASK_STATS_PEERS, scanon = 1, format = 0; - - /* A socket without one is a dead socket, which is what http_sendiovecdata - already answers 500 to. */ - if (!cookie) - HTTPERROR_500; - -#ifdef WANT_RESTRICT_STATS - if (!accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_STAT)) - HTTPERROR_403_IP; -#endif - - while (scanon) { - switch (urlencoded_match_keyword(keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM)) { - case -2: - scanon = 0; - break; /* TERMINATOR */ - case -1: - HTTPERROR_400_PARAM; /* PARSE ERROR */ - case -3: - urlencoded_skip_value(&read_ptr); - break; - case 1: /* matched "mode" */ - if ((mode = urlencoded_match_keyword(keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) - HTTPERROR_400_PARAM; - break; - case 2: /* matched "format" */ - if ((format = urlencoded_match_keyword(keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) - HTTPERROR_400_PARAM; - break; - case 3: - HTTPERROR_400_PARAM; /* If the stats URL was mistakenly added as announce URL, return a 400 */ - } - } - -#ifdef WANT_FULLSCRAPE - if (mode == TASK_FULLSCRAPE_TRACKERSTATE) { - format = mode; - mode = TASK_STATS_TPB; - } - - if (mode == TASK_STATS_TPB) { -#ifdef WANT_COMPRESSION_GZIP - ws->request[ws->request_size - 1] = 0; -#ifndef WANT_COMPRESSION_GZIP_ALWAYS - if (strstr(read_ptr - 1, "gzip")) { -#endif - cookie->flag |= STRUCT_HTTP_FLAG_GZIP; - format |= TASK_FLAG_GZIP; -#ifndef WANT_COMPRESSION_GZIP_ALWAYS - } -#endif -#endif - cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; - - /* Clients waiting for us should not easily timeout */ - fdstate_set_expiry(sock, 0); - http_interest_set(sock, cookie, 0); - - /* Everything this connection needs has been written, so it can be given - a home of its own and only then be made reachable by a worker thread */ - if (!http_adopt(sock)) - HTTPERROR_500; - fullscrape_deliver(sock, format); - return ws->reply_size = -2; - } -#endif - - /* default format for now */ - if ((mode & TASK_CLASS_MASK) == TASK_STATS) { - - cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; - - /* Complex stats also include expensive memory debugging tools */ - fdstate_set_expiry(sock, 0); - - if (!http_adopt(sock)) - HTTPERROR_500; - stats_deliver(sock, mode); - return ws->reply_size = -2; - } - - /* Simple stats can be answerred immediately */ - reply = ws->reply; - return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0); - return ws->reply_size = reply - ws->reply; -} - -#ifdef WANT_MODEST_FULLSCRAPES -static pthread_mutex_t g_modest_fullscrape_mutex = PTHREAD_MUTEX_INITIALIZER; -static ot_vector g_modest_fullscrape_timeouts; -typedef struct { - ot_ip6 ip; - ot_time last_fullscrape; -} ot_scrape_log; -#endif - -#ifdef WANT_FULLSCRAPE -static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) { - struct http_data *cookie = fdstate_cookie(sock); - int format = 0; - -#ifdef WANT_MODEST_FULLSCRAPES - { - ot_scrape_log this_peer, *new_peer; - int exactmatch; - memcpy(this_peer.ip, cookie->ip, sizeof(ot_ip6)); - this_peer.last_fullscrape = time_now_seconds(); - pthread_mutex_lock(&g_modest_fullscrape_mutex); - new_peer = vector_find_or_insert(&g_modest_fullscrape_timeouts, &this_peer, sizeof(ot_scrape_log), sizeof(ot_ip6), &exactmatch); - if (!new_peer) { - pthread_mutex_unlock(&g_modest_fullscrape_mutex); - HTTPERROR_500; - } - if (exactmatch && (this_peer.last_fullscrape - new_peer->last_fullscrape) < OT_MODEST_PEER_TIMEOUT) { - pthread_mutex_unlock(&g_modest_fullscrape_mutex); - HTTPERROR_402_NOTMODEST; - } - memcpy(new_peer, &this_peer, sizeof(ot_scrape_log)); - pthread_mutex_unlock(&g_modest_fullscrape_mutex); - } -#endif - - -#if defined(WANT_COMPRESSION_GZIP) || defined(WANT_COMPRESSION_ZSTD) - ws->request[ws->request_size - 1] = 0; -#ifdef WANT_COMPRESSION_GZIP - if (strstr(ws->request, "gzip")) { - cookie->flag |= STRUCT_HTTP_FLAG_GZIP; - format |= TASK_FLAG_GZIP; - } -#endif -#ifdef WANT_COMPRESSION_ZSTD - if (strstr(ws->request, "zstd")) { - cookie->flag |= STRUCT_HTTP_FLAG_ZSTD; - format |= TASK_FLAG_ZSTD; - } -#endif - -#if defined(WANT_COMPRESSION_ZSTD) && defined(WANT_COMPRESSION_ZSTD_ALWAYS) - cookie->flag |= STRUCT_HTTP_FLAG_ZSTD; - format |= TASK_FLAG_ZSTD; -#endif - -#if defined(WANT_COMPRESSION_GZIP) && defined(WANT_COMPRESSION_GZIP_ALWAYS) - cookie->flag |= STRUCT_HTTP_FLAG_GZIP; - format |= TASK_FLAG_GZIP; -#endif -#endif - - stats_issue_event(ws, EVENT_FULLSCRAPE_REQUEST, 0, (uintptr_t)cookie->ip); - -#ifdef _DEBUG_HTTPERROR - fprintf(stderr, "%s", ws->debugbuf); -#endif - - /* Pass this task to the worker thread */ - cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; - /* Clients waiting for us should not easily timeout */ - fdstate_set_expiry(sock, 0); - http_interest_set(sock, cookie, 0); - - if (!http_adopt(sock)) - HTTPERROR_500; - fullscrape_deliver(sock, format); - return ws->reply_size = -2; -} -#endif - -static int compare_hash(const void *left, const void *right) { return memcmp(left, right, sizeof(ot_hash)); } - -static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { - static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}}; - - ot_hash *multiscrape_buf = (ot_hash *)ws->request; - char *reply = ws->reply; - int scanon = 1; - size_t i, unique, numwant = 0; - - /* This is to hack around stupid clients that send "scrape ?info_hash" */ - if (read_ptr[-1] != '?') { - while ((*read_ptr != '?') && (*read_ptr != '\n')) - ++read_ptr; - if (*read_ptr == '\n') - HTTPERROR_400_PARAM; - ++read_ptr; - } - - while (scanon) { - switch (urlencoded_match_keyword(keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM)) { - case -2: - scanon = 0; - break; /* TERMINATOR */ - default: - HTTPERROR_400_PARAM; /* PARSE ERROR */ - case -3: - urlencoded_skip_value(&read_ptr); - break; - case 1: /* matched "info_hash" */ - /* ignore this, when we have less than 20 bytes */ - if (urlencoded_parse(&read_ptr, (char *)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE) != (ssize_t)sizeof(ot_hash)) - HTTPERROR_400_PARAM; - break; - } - } - - /* No info_hash found? Inform user */ - if (!numwant) - HTTPERROR_400_PARAM; - - /* Limit number of hashes to process */ - if (numwant > OT_MAXMULTISCRAPE_COUNT) - numwant = OT_MAXMULTISCRAPE_COUNT; - - /* A bencoded dictionary has its keys in order and names each one once, so - the answer cannot just follow the order the client asked in */ - qsort(multiscrape_buf, numwant, sizeof(ot_hash), compare_hash); - for (i = unique = 1; i < numwant; ++i) - if (memcmp(multiscrape_buf[i], multiscrape_buf[unique - 1], sizeof(ot_hash))) - memcpy(multiscrape_buf[unique++], multiscrape_buf[i], sizeof(ot_hash)); - numwant = unique; - - /* Enough for http header + whole scrape string */ - return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE); - ws->reply_size = reply - ws->reply; - stats_issue_event(ws, EVENT_SCRAPE, FLAG_TCP, ws->reply_size); - return ws->reply_size; -} - -#ifdef WANT_LOG_NUMWANT -unsigned long long numwants[201]; -#endif - -#if defined(WANT_KEEPALIVE) || defined(WANT_IP_FROM_PROXY) -static char *http_header(char *data, size_t byte_count, const char *header) { - size_t i; - long sl = strlen(header); - for (i = 0; i + sl + 2 < byte_count; ++i) { - if (data[i] != '\n' || data[i + sl + 1] != ':') - continue; - if (strncasecmp(data + i + 1, header, sl)) - continue; - data += i + sl + 2; - while (*data == ' ' || *data == '\t') - ++data; - return data; - } - return 0; -} -#endif - -static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event", 3}, {"numwant", 4}, {"compact", 5}, {"compact6", 5}, {"info_hash", 6}, -#ifdef WANT_IP_FROM_QUERY_STRING - {"ip", 7}, -#endif - {"peer_id", 8}, {NULL, -3}}; -static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; - -/* For the replies that are one fixed string and nothing else, where the only - thing the caller wants back is how long it turned out */ -static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) { - char *reply = ws->reply; - - fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text)); - return reply - ws->reply; -} - -static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { - size_t numwant; - int64_t tmp; - int scanon; - unsigned short port = 0; - char *write_ptr; - ssize_t len; - struct http_data *cookie = fdstate_cookie(sock); - - /* This is to hack around stupid clients that send "announce ?info_hash" */ - if (read_ptr[-1] != '?') { - while ((*read_ptr != '?') && (*read_ptr != '\n')) - ++read_ptr; - if (*read_ptr == '\n') - HTTPERROR_400_PARAM; - ++read_ptr; - } - -#ifdef WANT_IP_FROM_PROXY - if (accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_PROXY)) { - ot_ip6 proxied_ip; - char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for"); - if (fwd && ip_parse(fwd, proxied_ip)) { - OT_SETIP(ws->peer, proxied_ip); - } else - OT_SETIP(ws->peer, cookie->ip); - } else -#endif - OT_SETIP(ws->peer, cookie->ip); - - ws->peer_id = NULL; - ws->hash = NULL; - - OT_SETPORT(ws->peer, &port); - OT_PEERFLAG(ws->peer) = 0; - numwant = 50; - scanon = 1; - - while (scanon) { - switch (urlencoded_match_keyword(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM)) { - case -2: - scanon = 0; - break; /* TERMINATOR */ - case -1: - HTTPERROR_400_PARAM; /* PARSE ERROR */ - case -3: - urlencoded_skip_value(&read_ptr); - break; - case 1: /* matched "port" */ - len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); - if ((len <= 0) || scan_int(write_ptr, len, &tmp) || (tmp > 0xffff)) - HTTPERROR_400_PARAM; - port = htons((uint16_t)tmp); - OT_SETPORT(&ws->peer, &port); - break; - case 2: /* matched "left" */ - if ((len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) - HTTPERROR_400_PARAM; - if (scan_int(write_ptr, len, &tmp)) - tmp = 0; - if (!tmp) - OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING; - break; - case 3: /* matched "event" */ - switch (urlencoded_match_keyword(keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE)) { - case -1: - HTTPERROR_400_PARAM; - case 1: /* matched "completed" */ - OT_PEERFLAG(&ws->peer) |= PEER_FLAG_COMPLETED; - break; - case 2: /* matched "stopped" */ - OT_PEERFLAG(&ws->peer) |= PEER_FLAG_STOPPED; - break; - default: - break; - } - break; - case 4: /* matched "numwant" */ - len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); - if ((len <= 0) || scan_int(write_ptr, len, &tmp)) - HTTPERROR_400_PARAM; - if (tmp < 0) - tmp = 50; - if (tmp > 200) - tmp = 200; - numwant = (size_t)tmp; - break; - case 5: /* matched "compact" */ - len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); - if ((len <= 0) || scan_int(write_ptr, len, &tmp)) - HTTPERROR_400_PARAM; - if (!tmp) - HTTPERROR_400_COMPACT; - break; - case 6: /* matched "info_hash" */ - if (ws->hash) - HTTPERROR_400_DOUBLEHASH; - /* ignore this, when we have less than 20 bytes */ - if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20) - HTTPERROR_400_PARAM; - ws->hash = (ot_hash *)write_ptr; - break; -#ifdef WANT_IP_FROM_QUERY_STRING - case 7: /* matched "ip" */ - { - char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16; - len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE); - tmp_buf2[len] = 0; - if ((len <= 0) || ip_parse(tmp_buf2, tmp_buf1) != (size_t)len) - HTTPERROR_400_PARAM; - OT_SETIP(&ws->peer, tmp_buf1); - } break; -#endif - case 8: /* matched "peer_id" */ - /* ignore this, when we have less than 20 bytes */ - if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20) - HTTPERROR_400_PARAM; - ws->peer_id = write_ptr; - break; - } - } - -#ifdef WANT_LOG_NUMWANT - numwants[numwant]++; -#endif - - /* XXX DEBUG - stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ws->reply ); - */ - - /* Scanned whole query string */ - if (!ws->hash) - return ws->reply_size = reply_literal(ws, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e"); - - if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED) - ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws); - else - ws->reply_size = add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, numwant); - - stats_issue_event(ws, EVENT_ANNOUNCE, FLAG_TCP, ws->reply_size); - return ws->reply_size; -} - -ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) { - ssize_t reply_off, len; - char *read_ptr = ws->request, *write_ptr; - -#ifdef _DEBUG_HTTPERROR - reply_off = ws->request_size; - if (ws->request_size >= G_DEBUGBUF_SIZE) - reply_off = G_DEBUGBUF_SIZE - 1; - memcpy(ws->debugbuf, ws->request, reply_off); - ws->debugbuf[reply_off] = 0; -#endif - - /* Tell subroutines where to put reply data */ - ws->reply = ws->outbuf + SUCCESS_HTTP_HEADER_LENGTH; - - /* Find out if the client wants to keep this connection alive, before any - subroutine gets to answer: an error reply finishes with the connection - on its way out, and ws outlives the connection it was serving */ - ws->keep_alive = 0; -#ifdef WANT_KEEPALIVE - if ((write_ptr = http_header(ws->request, ws->header_size, "connection")) && (*write_ptr == 'K' || *write_ptr == 'k')) - ws->keep_alive = 1; -#endif - { - struct http_data *connection = fdstate_cookie(sock); - if (connection) { - if (ws->keep_alive) - connection->flag |= STRUCT_HTTP_FLAG_KEEPALIVE; - else - connection->flag &= ~STRUCT_HTTP_FLAG_KEEPALIVE; - } - } - - /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */ - if (memcmp(read_ptr, "GET /", 5)) - HTTPERROR_400; - - /* Skip leading '/' */ - for (read_ptr += 4; *read_ptr == '/'; ++read_ptr) - ; - - /* Try to parse the request. - In reality we abandoned requiring the url to be correct. This now - only decodes url encoded characters, we check for announces and - scrapes by looking for "a*" or "sc" */ - len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_PATH); - - /* If parsing returned an error, leave with not found */ - if (g_redirecturl && (len == -2)) - HTTPERROR_302; - if (len <= 0) - HTTPERROR_404; - - /* This is the hardcore match for announce*/ - if ((*write_ptr == 'a') || (*write_ptr == '?')) - http_handle_announce(sock, ws, read_ptr); -#ifdef WANT_FULLSCRAPE - else if (!memcmp(write_ptr, "scrape HTTP/", 12)) - http_handle_fullscrape(sock, ws); -#endif - /* This is the hardcore match for scrape */ - else if (!memcmp(write_ptr, "sc", 2)) - http_handle_scrape(sock, ws, read_ptr); - /* All the rest is matched the standard way */ - else if (len == g_stats_path_len && !memcmp(write_ptr, g_stats_path, len)) - http_handle_stats(sock, ws, read_ptr); - else - HTTPERROR_404; - - /* If routines handled sending themselves, just return */ - if (ws->reply_size == -2) - return 0; - /* If routine failed, let http error take over */ - if (ws->reply_size <= 0) - HTTPERROR_500; - - /* This one is rather ugly, so I take you step by step through it. - - 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to - write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our work buffer, which is enough for the static string - plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate - the space NOT needed to expand in reply_off - */ - reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size); - ws->reply = ws->outbuf + reply_off; - - /* 2. Now we write the header so that its terminating '\0' lands exactly one byte before the content starts. The bound is the - space step 1 reserved, so the '\0' falls where step 3 expects it. Complete packet size is increased by size of header plus - one byte '\n', we will copy over '\0' in next step */ - ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off), - "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size); - - /* 3. Finally we join both blocks neatly */ - ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n'; - - http_senddata(sock, ws); - return ws->reply_size; -} -- cgit v1.3