summaryrefslogtreecommitdiff
path: root/src/ot_http.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_http.c')
-rw-r--r--src/ot_http.c810
1 files changed, 810 insertions, 0 deletions
diff --git a/src/ot_http.c b/src/ot_http.c
new file mode 100644
index 0000000..96b8f4e
--- /dev/null
+++ b/src/ot_http.c
@@ -0,0 +1,810 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#define _GNU_SOURCE
6#include <arpa/inet.h>
7#include <errno.h>
8#include <pthread.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <strings.h>
13#include <sys/types.h>
14#include <unistd.h>
15
16/* Opentracker */
17#include "ot_accesslist.h"
18#include "ot_fullscrape.h"
19#include "ot_http.h"
20#include "ot_iovec.h"
21#include "ot_ip.h"
22#include "ot_mutex.h"
23#include "ot_workqueue.h"
24#include "ot_scan.h"
25#include "ot_event.h"
26#include "ot_fdstate.h"
27#include "ot_vector.h"
28#include "ot_fmt.h"
29#include "ot_stats.h"
30#include "ot_urlencoded.h"
31#include "trackerlogic.h"
32
33#define OT_MAXMULTISCRAPE_COUNT 64
34extern const char *g_redirecturl;
35
36const char *g_stats_path;
37ssize_t g_stats_path_len;
38
39enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 };
40
41struct http_data *http_adopt(const int64_t sock) {
42 struct http_data *cookie = fdstate_cookie(sock), *heap;
43 int interest;
44
45 if (!cookie || !(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK))
46 return cookie;
47
48 if (!(heap = malloc(sizeof(*heap))))
49 return NULL;
50
51 *heap = *cookie;
52 heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK;
53
54 /* Publishing the cookie is what lets another thread reach this connection,
55 so nothing may touch it afterwards, not even to read what it asked for */
56 interest = heap->interest;
57 fdstate_set_cookie((int)sock, heap);
58
59 if (event_add((int)sock, interest, fdstate_generation((int)sock)) == -1) {
60 fdstate_set_cookie((int)sock, cookie);
61 free(heap);
62 return NULL;
63 }
64 return heap;
65}
66
67/* Give up a connection: the one place that knows everything hanging off it. */
68void http_close(const int64_t sock) {
69 struct http_data *cookie = fdstate_cookie(sock);
70
71 fdstate_close((int)sock);
72
73 if (cookie) {
74 if (cookie->batch)
75 batch_reset(cookie->batch);
76 free(cookie->batch);
77 buf_reset(&cookie->request);
78 if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))
79 workqueue_canceltask(sock);
80 if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK))
81 free(cookie);
82 }
83
84 event_close((int)sock);
85}
86
87static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
88 struct http_data *cookie = fdstate_cookie(sock);
89 ssize_t written_size;
90
91 if (!cookie) {
92 http_close(sock);
93 return;
94 }
95
96 /* whoever sends data is not interested in its input buffer */
97 if (ws->keep_alive && ws->header_size != ws->request_size) {
98 size_t rest = ws->request_size - ws->header_size;
99 if (cookie->request.data) {
100 memmove(cookie->request.data, ws->request + ws->header_size, rest);
101 buf_truncate(&cookie->request, rest);
102 } else if (buf_cat(&cookie->request, ws->request + ws->header_size, rest)) {
103 /* Without room for what the client pipelined behind this request, answer
104 this one and let the connection close, rather than keeping it open
105 around a request we have silently dropped */
106 buf_reset(&cookie->request);
107 ws->keep_alive = 0;
108 }
109 } else
110 buf_reset(&cookie->request);
111
112 written_size = write(sock, ws->reply, ws->reply_size);
113 /* A send buffer with no room in it is not a failure: nothing went out, so
114 the whole reply goes into the batch below and leaves when the socket
115 drains. Only a real error ends the connection here. */
116 if (written_size < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
117 written_size = 0;
118
119 if ((written_size < 0) || ((written_size == ws->reply_size) && !ws->keep_alive)) {
120 http_close(sock);
121 return;
122 }
123
124 if (written_size < ws->reply_size) {
125 char *outbuf;
126
127 if (!(outbuf = malloc(ws->reply_size - written_size))) {
128 http_close(sock);
129 return;
130 }
131
132 memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size);
133 if (!cookie->batch) {
134 cookie->batch = calloc(1, sizeof(ot_batch));
135 if (!cookie->batch) {
136 free(outbuf);
137 http_close(sock);
138 return;
139 }
140 }
141
142 batch_add(cookie->batch, outbuf, ws->reply_size - written_size);
143
144 /* writeable short data sockets just have a tcp timeout */
145 if (!ws->keep_alive)
146 fdstate_set_expiry(sock, 0);
147 http_interest_set(sock, cookie, EVENT_OUT);
148 }
149}
150
151#define HTTPERROR_302 return http_issue_error(sock, ws, CODE_HTTPERROR_302)
152#define HTTPERROR_400 return http_issue_error(sock, ws, CODE_HTTPERROR_400)
153#define HTTPERROR_400_PARAM return http_issue_error(sock, ws, CODE_HTTPERROR_400_PARAM)
154#define HTTPERROR_400_COMPACT return http_issue_error(sock, ws, CODE_HTTPERROR_400_COMPACT)
155#define HTTPERROR_400_DOUBLEHASH return http_issue_error(sock, ws, CODE_HTTPERROR_400_PARAM)
156#define HTTPERROR_402_NOTMODEST return http_issue_error(sock, ws, CODE_HTTPERROR_402_NOTMODEST)
157#define HTTPERROR_403_IP return http_issue_error(sock, ws, CODE_HTTPERROR_403_IP)
158#define HTTPERROR_404 return http_issue_error(sock, ws, CODE_HTTPERROR_404)
159#define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500)
160/* A connection is either being read or being written, never both, so what it
161 waits for is set outright rather than added to and taken away again. The
162 event layer only hears about it when the answer changes. */
163void http_interest_set(const int64_t sock, struct http_data *cookie, int what) {
164 if (cookie->interest == what)
165 return;
166 cookie->interest = what;
167 /* Still on a stack, so not in the event loop yet: whoever adopts this
168 connection registers whatever it asked for last */
169 if (cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)
170 return;
171 event_modify((int)sock, what, fdstate_generation((int)sock));
172}
173
174ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) {
175 const char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required",
176 "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"};
177 const char *title = error_code[code];
178
179 ws->reply = ws->outbuf;
180 if (code == CODE_HTTPERROR_302)
181 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);
182 else
183 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<title>%s</title>\n", title,
184 strlen(title) + 16 - 4, title + 4);
185
186#ifdef _DEBUG_HTTPERROR
187 fprintf(stderr, "DEBUG: invalid request was: %s\n", ws->debugbuf);
188#endif
189 stats_issue_event(ws, EVENT_FAILED, FLAG_TCP, code);
190 http_senddata(sock, ws);
191 return ws->reply_size = -2;
192}
193
194ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) {
195 struct http_data *cookie = fdstate_cookie(sock);
196 char *header;
197 const char *encoding = "";
198 size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector);
199
200 /* No cookie? Bad socket. Leave. */
201 if (!cookie) {
202 iovec_free(&iovec_entries, &iovector);
203 HTTPERROR_500;
204 }
205
206 /* Descriptor numbers get recycled, so a result can only belong to this
207 connection if this connection is in fact waiting for one. */
208 if (!(cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))) {
209 iovec_free(&iovec_entries, &iovector);
210 return 0;
211 }
212
213 /* If this socket collected request in a buffer, free it now */
214 buf_reset(&cookie->request);
215
216 /* If we came here, wait for the answer is over */
217 if (cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK) {
218 http_interest_set(sock, cookie, 0);
219 cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK;
220 }
221
222 if (iovec_entries) {
223
224 if (cookie->flag & STRUCT_HTTP_FLAG_ZSTD)
225 encoding = "Content-Encoding: zstd\r\n";
226 else if (cookie->flag & STRUCT_HTTP_FLAG_GZIP)
227 encoding = "Content-Encoding: gzip\r\n";
228 else if (cookie->flag & STRUCT_HTTP_FLAG_BZIP2)
229 encoding = "Content-Encoding: bzip2\r\n";
230
231 if (!(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED))
232 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);
233 else {
234 if (!(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) {
235 header_size =
236 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);
237 cookie->flag |= STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
238 } else
239 header_size = asprintf(&header, "%zx\r\n", size);
240 }
241 if (!header) {
242 iovec_free(&iovec_entries, &iovector);
243 HTTPERROR_500;
244 }
245
246 if (!cookie->batch) {
247 cookie->batch = calloc(1, sizeof(ot_batch));
248 if (!cookie->batch) {
249 free(header);
250 iovec_free(&iovec_entries, &iovector);
251 HTTPERROR_500;
252 }
253 }
254
255 batch_add(cookie->batch, header, header_size);
256 batch_add_vector(cookie->batch, iovector, iovec_entries);
257 free(iovector);
258
259 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)
260 batch_add_copy(cookie->batch, "\r\n", 2);
261 }
262
263 if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) {
264 batch_add_copy(cookie->batch, "0\r\n\r\n", 5);
265 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
266 }
267
268 fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT_SEND));
269 http_interest_set(sock, cookie, EVENT_OUT);
270 return 0;
271}
272
273static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
274 static const ot_keywords keywords_main[] = {{"mode", 1}, {"format", 2}, {"info_hash", 3}, {NULL, -3}};
275 static const ot_keywords keywords_mode[] = {{"peer", TASK_STATS_PEERS},
276 {"conn", TASK_STATS_CONNS},
277 {"scrp", TASK_STATS_SCRAPE},
278 {"udp4", TASK_STATS_UDP},
279 {"tcp4", TASK_STATS_TCP},
280 {"torr", TASK_STATS_TORRENTS},
281 {"fscr", TASK_STATS_FULLSCRAPE},
282 {"s24s", TASK_STATS_SLASH24S},
283 {"tpbs", TASK_STATS_TPB},
284 {"herr", TASK_STATS_HTTPERRORS},
285 {"completed", TASK_STATS_COMPLETED},
286 {"top100", TASK_STATS_TOP100},
287 {"top10", TASK_STATS_TOP10},
288 {"renew", TASK_STATS_RENEW},
289 {"syncs", TASK_STATS_SYNCS},
290 {"everything", TASK_STATS_EVERYTHING},
291 {"statedump", TASK_FULLSCRAPE_TRACKERSTATE},
292 {"woodpeckers", TASK_STATS_WOODPECKERS},
293#ifdef WANT_LOG_NUMWANT
294 {"numwants", TASK_STATS_NUMWANTS},
295#endif
296 {NULL, -3}};
297 static const ot_keywords keywords_format[] = {{"bin", TASK_FULLSCRAPE_TPB_BINARY}, {"ben", TASK_FULLSCRAPE},
298 {"url", TASK_FULLSCRAPE_TPB_URLENCODED}, {"txt", TASK_FULLSCRAPE_TPB_ASCII},
299 {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}};
300
301 struct http_data *cookie = fdstate_cookie(sock);
302 char *reply;
303 int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
304
305 /* A socket without one is a dead socket, which is what http_sendiovecdata
306 already answers 500 to. */
307 if (!cookie)
308 HTTPERROR_500;
309
310#ifdef WANT_RESTRICT_STATS
311 if (!accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_STAT))
312 HTTPERROR_403_IP;
313#endif
314
315 while (scanon) {
316 switch (urlencoded_match_keyword(keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
317 case -2:
318 scanon = 0;
319 break; /* TERMINATOR */
320 case -1:
321 HTTPERROR_400_PARAM; /* PARSE ERROR */
322 case -3:
323 urlencoded_skip_value(&read_ptr);
324 break;
325 case 1: /* matched "mode" */
326 if ((mode = urlencoded_match_keyword(keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
327 HTTPERROR_400_PARAM;
328 break;
329 case 2: /* matched "format" */
330 if ((format = urlencoded_match_keyword(keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
331 HTTPERROR_400_PARAM;
332 break;
333 case 3:
334 HTTPERROR_400_PARAM; /* If the stats URL was mistakenly added as announce URL, return a 400 */
335 }
336 }
337
338#ifdef WANT_FULLSCRAPE
339 if (mode == TASK_FULLSCRAPE_TRACKERSTATE) {
340 format = mode;
341 mode = TASK_STATS_TPB;
342 }
343
344 if (mode == TASK_STATS_TPB) {
345#ifdef WANT_COMPRESSION_GZIP
346 ws->request[ws->request_size - 1] = 0;
347#ifndef WANT_COMPRESSION_GZIP_ALWAYS
348 if (strstr(read_ptr - 1, "gzip")) {
349#endif
350 cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
351 format |= TASK_FLAG_GZIP;
352#ifndef WANT_COMPRESSION_GZIP_ALWAYS
353 }
354#endif
355#endif
356 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
357
358 /* Clients waiting for us should not easily timeout */
359 fdstate_set_expiry(sock, 0);
360 http_interest_set(sock, cookie, 0);
361
362 /* Everything this connection needs has been written, so it can be given
363 a home of its own and only then be made reachable by a worker thread */
364 if (!http_adopt(sock))
365 HTTPERROR_500;
366 fullscrape_deliver(sock, format);
367 return ws->reply_size = -2;
368 }
369#endif
370
371 /* default format for now */
372 if ((mode & TASK_CLASS_MASK) == TASK_STATS) {
373
374 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
375
376 /* Complex stats also include expensive memory debugging tools */
377 fdstate_set_expiry(sock, 0);
378
379 if (!http_adopt(sock))
380 HTTPERROR_500;
381 stats_deliver(sock, mode);
382 return ws->reply_size = -2;
383 }
384
385 /* Simple stats can be answerred immediately */
386 reply = ws->reply;
387 return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0);
388 return ws->reply_size = reply - ws->reply;
389}
390
391#ifdef WANT_MODEST_FULLSCRAPES
392static pthread_mutex_t g_modest_fullscrape_mutex = PTHREAD_MUTEX_INITIALIZER;
393static ot_vector g_modest_fullscrape_timeouts;
394typedef struct {
395 ot_ip6 ip;
396 ot_time last_fullscrape;
397} ot_scrape_log;
398#endif
399
400#ifdef WANT_FULLSCRAPE
401static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) {
402 struct http_data *cookie = fdstate_cookie(sock);
403 int format = 0;
404
405#ifdef WANT_MODEST_FULLSCRAPES
406 {
407 ot_scrape_log this_peer, *new_peer;
408 int exactmatch;
409 memcpy(this_peer.ip, cookie->ip, sizeof(ot_ip6));
410 this_peer.last_fullscrape = time_now_seconds();
411 pthread_mutex_lock(&g_modest_fullscrape_mutex);
412 new_peer = vector_find_or_insert(&g_modest_fullscrape_timeouts, &this_peer, sizeof(ot_scrape_log), sizeof(ot_ip6), &exactmatch);
413 if (!new_peer) {
414 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
415 HTTPERROR_500;
416 }
417 if (exactmatch && (this_peer.last_fullscrape - new_peer->last_fullscrape) < OT_MODEST_PEER_TIMEOUT) {
418 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
419 HTTPERROR_402_NOTMODEST;
420 }
421 memcpy(new_peer, &this_peer, sizeof(ot_scrape_log));
422 pthread_mutex_unlock(&g_modest_fullscrape_mutex);
423 }
424#endif
425
426
427#if defined(WANT_COMPRESSION_GZIP) || defined(WANT_COMPRESSION_ZSTD)
428 ws->request[ws->request_size - 1] = 0;
429#ifdef WANT_COMPRESSION_GZIP
430 if (strstr(ws->request, "gzip")) {
431 cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
432 format |= TASK_FLAG_GZIP;
433 }
434#endif
435#ifdef WANT_COMPRESSION_ZSTD
436 if (strstr(ws->request, "zstd")) {
437 cookie->flag |= STRUCT_HTTP_FLAG_ZSTD;
438 format |= TASK_FLAG_ZSTD;
439 }
440#endif
441
442#if defined(WANT_COMPRESSION_ZSTD) && defined(WANT_COMPRESSION_ZSTD_ALWAYS)
443 cookie->flag |= STRUCT_HTTP_FLAG_ZSTD;
444 format |= TASK_FLAG_ZSTD;
445#endif
446
447#if defined(WANT_COMPRESSION_GZIP) && defined(WANT_COMPRESSION_GZIP_ALWAYS)
448 cookie->flag |= STRUCT_HTTP_FLAG_GZIP;
449 format |= TASK_FLAG_GZIP;
450#endif
451#endif
452
453 stats_issue_event(ws, EVENT_FULLSCRAPE_REQUEST, 0, (uintptr_t)cookie->ip);
454
455#ifdef _DEBUG_HTTPERROR
456 fprintf(stderr, "%s", ws->debugbuf);
457#endif
458
459 /* Pass this task to the worker thread */
460 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
461 /* Clients waiting for us should not easily timeout */
462 fdstate_set_expiry(sock, 0);
463 http_interest_set(sock, cookie, 0);
464
465 if (!http_adopt(sock))
466 HTTPERROR_500;
467 fullscrape_deliver(sock, format);
468 return ws->reply_size = -2;
469}
470#endif
471
472static int compare_hash(const void *left, const void *right) { return memcmp(left, right, sizeof(ot_hash)); }
473
474static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
475 static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}};
476
477 ot_hash *multiscrape_buf = (ot_hash *)ws->request;
478 char *reply = ws->reply;
479 int scanon = 1;
480 size_t i, unique, numwant = 0;
481
482 /* This is to hack around stupid clients that send "scrape ?info_hash" */
483 if (read_ptr[-1] != '?') {
484 while ((*read_ptr != '?') && (*read_ptr != '\n'))
485 ++read_ptr;
486 if (*read_ptr == '\n')
487 HTTPERROR_400_PARAM;
488 ++read_ptr;
489 }
490
491 while (scanon) {
492 switch (urlencoded_match_keyword(keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
493 case -2:
494 scanon = 0;
495 break; /* TERMINATOR */
496 default:
497 HTTPERROR_400_PARAM; /* PARSE ERROR */
498 case -3:
499 urlencoded_skip_value(&read_ptr);
500 break;
501 case 1: /* matched "info_hash" */
502 /* ignore this, when we have less than 20 bytes */
503 if (urlencoded_parse(&read_ptr, (char *)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE) != (ssize_t)sizeof(ot_hash))
504 HTTPERROR_400_PARAM;
505 break;
506 }
507 }
508
509 /* No info_hash found? Inform user */
510 if (!numwant)
511 HTTPERROR_400_PARAM;
512
513 /* Limit number of hashes to process */
514 if (numwant > OT_MAXMULTISCRAPE_COUNT)
515 numwant = OT_MAXMULTISCRAPE_COUNT;
516
517 /* A bencoded dictionary has its keys in order and names each one once, so
518 the answer cannot just follow the order the client asked in */
519 qsort(multiscrape_buf, numwant, sizeof(ot_hash), compare_hash);
520 for (i = unique = 1; i < numwant; ++i)
521 if (memcmp(multiscrape_buf[i], multiscrape_buf[unique - 1], sizeof(ot_hash)))
522 memcpy(multiscrape_buf[unique++], multiscrape_buf[i], sizeof(ot_hash));
523 numwant = unique;
524
525 /* Enough for http header + whole scrape string */
526 return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE);
527 ws->reply_size = reply - ws->reply;
528 stats_issue_event(ws, EVENT_SCRAPE, FLAG_TCP, ws->reply_size);
529 return ws->reply_size;
530}
531
532#ifdef WANT_LOG_NUMWANT
533unsigned long long numwants[201];
534#endif
535
536#if defined(WANT_KEEPALIVE) || defined(WANT_IP_FROM_PROXY)
537static char *http_header(char *data, size_t byte_count, const char *header) {
538 size_t i;
539 long sl = strlen(header);
540 for (i = 0; i + sl + 2 < byte_count; ++i) {
541 if (data[i] != '\n' || data[i + sl + 1] != ':')
542 continue;
543 if (strncasecmp(data + i + 1, header, sl))
544 continue;
545 data += i + sl + 2;
546 while (*data == ' ' || *data == '\t')
547 ++data;
548 return data;
549 }
550 return 0;
551}
552#endif
553
554static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event", 3}, {"numwant", 4}, {"compact", 5}, {"compact6", 5}, {"info_hash", 6},
555#ifdef WANT_IP_FROM_QUERY_STRING
556 {"ip", 7},
557#endif
558 {"peer_id", 8}, {NULL, -3}};
559static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}};
560
561/* For the replies that are one fixed string and nothing else, where the only
562 thing the caller wants back is how long it turned out */
563static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) {
564 char *reply = ws->reply;
565
566 fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text));
567 return reply - ws->reply;
568}
569
570static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
571 size_t numwant;
572 int64_t tmp;
573 int scanon;
574 unsigned short port = 0;
575 char *write_ptr;
576 ssize_t len;
577 struct http_data *cookie = fdstate_cookie(sock);
578
579 /* This is to hack around stupid clients that send "announce ?info_hash" */
580 if (read_ptr[-1] != '?') {
581 while ((*read_ptr != '?') && (*read_ptr != '\n'))
582 ++read_ptr;
583 if (*read_ptr == '\n')
584 HTTPERROR_400_PARAM;
585 ++read_ptr;
586 }
587
588#ifdef WANT_IP_FROM_PROXY
589 if (accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_PROXY)) {
590 ot_ip6 proxied_ip;
591 char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for");
592 if (fwd && ip_parse(fwd, proxied_ip)) {
593 OT_SETIP(ws->peer, proxied_ip);
594 } else
595 OT_SETIP(ws->peer, cookie->ip);
596 } else
597#endif
598 OT_SETIP(ws->peer, cookie->ip);
599
600 ws->peer_id = NULL;
601 ws->hash = NULL;
602
603 OT_SETPORT(ws->peer, &port);
604 OT_PEERFLAG(ws->peer) = 0;
605 numwant = 50;
606 scanon = 1;
607
608 while (scanon) {
609 switch (urlencoded_match_keyword(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
610 case -2:
611 scanon = 0;
612 break; /* TERMINATOR */
613 case -1:
614 HTTPERROR_400_PARAM; /* PARSE ERROR */
615 case -3:
616 urlencoded_skip_value(&read_ptr);
617 break;
618 case 1: /* matched "port" */
619 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
620 if ((len <= 0) || scan_int(write_ptr, len, &tmp) || (tmp > 0xffff))
621 HTTPERROR_400_PARAM;
622 port = htons((uint16_t)tmp);
623 OT_SETPORT(&ws->peer, &port);
624 break;
625 case 2: /* matched "left" */
626 if ((len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
627 HTTPERROR_400_PARAM;
628 if (scan_int(write_ptr, len, &tmp))
629 tmp = 0;
630 if (!tmp)
631 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING;
632 break;
633 case 3: /* matched "event" */
634 switch (urlencoded_match_keyword(keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE)) {
635 case -1:
636 HTTPERROR_400_PARAM;
637 case 1: /* matched "completed" */
638 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_COMPLETED;
639 break;
640 case 2: /* matched "stopped" */
641 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_STOPPED;
642 break;
643 default:
644 break;
645 }
646 break;
647 case 4: /* matched "numwant" */
648 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
649 if ((len <= 0) || scan_int(write_ptr, len, &tmp))
650 HTTPERROR_400_PARAM;
651 if (tmp < 0)
652 tmp = 50;
653 if (tmp > 200)
654 tmp = 200;
655 numwant = (size_t)tmp;
656 break;
657 case 5: /* matched "compact" */
658 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
659 if ((len <= 0) || scan_int(write_ptr, len, &tmp))
660 HTTPERROR_400_PARAM;
661 if (!tmp)
662 HTTPERROR_400_COMPACT;
663 break;
664 case 6: /* matched "info_hash" */
665 if (ws->hash)
666 HTTPERROR_400_DOUBLEHASH;
667 /* ignore this, when we have less than 20 bytes */
668 if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20)
669 HTTPERROR_400_PARAM;
670 ws->hash = (ot_hash *)write_ptr;
671 break;
672#ifdef WANT_IP_FROM_QUERY_STRING
673 case 7: /* matched "ip" */
674 {
675 char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16;
676 len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE);
677 tmp_buf2[len] = 0;
678 if ((len <= 0) || ip_parse(tmp_buf2, tmp_buf1) != (size_t)len)
679 HTTPERROR_400_PARAM;
680 OT_SETIP(&ws->peer, tmp_buf1);
681 } break;
682#endif
683 case 8: /* matched "peer_id" */
684 /* ignore this, when we have less than 20 bytes */
685 if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20)
686 HTTPERROR_400_PARAM;
687 ws->peer_id = write_ptr;
688 break;
689 }
690 }
691
692#ifdef WANT_LOG_NUMWANT
693 numwants[numwant]++;
694#endif
695
696 /* XXX DEBUG
697 stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ws->reply );
698 */
699
700 /* Scanned whole query string */
701 if (!ws->hash)
702 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");
703
704 if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED)
705 ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws);
706 else
707 ws->reply_size = add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, numwant);
708
709 stats_issue_event(ws, EVENT_ANNOUNCE, FLAG_TCP, ws->reply_size);
710 return ws->reply_size;
711}
712
713ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
714 ssize_t reply_off, len;
715 char *read_ptr = ws->request, *write_ptr;
716
717#ifdef _DEBUG_HTTPERROR
718 reply_off = ws->request_size;
719 if (ws->request_size >= G_DEBUGBUF_SIZE)
720 reply_off = G_DEBUGBUF_SIZE - 1;
721 memcpy(ws->debugbuf, ws->request, reply_off);
722 ws->debugbuf[reply_off] = 0;
723#endif
724
725 /* Tell subroutines where to put reply data */
726 ws->reply = ws->outbuf + SUCCESS_HTTP_HEADER_LENGTH;
727
728 /* Find out if the client wants to keep this connection alive, before any
729 subroutine gets to answer: an error reply finishes with the connection
730 on its way out, and ws outlives the connection it was serving */
731 ws->keep_alive = 0;
732#ifdef WANT_KEEPALIVE
733 if ((write_ptr = http_header(ws->request, ws->header_size, "connection")) && (*write_ptr == 'K' || *write_ptr == 'k'))
734 ws->keep_alive = 1;
735#endif
736 {
737 struct http_data *connection = fdstate_cookie(sock);
738 if (connection) {
739 if (ws->keep_alive)
740 connection->flag |= STRUCT_HTTP_FLAG_KEEPALIVE;
741 else
742 connection->flag &= ~STRUCT_HTTP_FLAG_KEEPALIVE;
743 }
744 }
745
746 /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */
747 if (memcmp(read_ptr, "GET /", 5))
748 HTTPERROR_400;
749
750 /* Skip leading '/' */
751 for (read_ptr += 4; *read_ptr == '/'; ++read_ptr)
752 ;
753
754 /* Try to parse the request.
755 In reality we abandoned requiring the url to be correct. This now
756 only decodes url encoded characters, we check for announces and
757 scrapes by looking for "a*" or "sc" */
758 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_PATH);
759
760 /* If parsing returned an error, leave with not found */
761 if (g_redirecturl && (len == -2))
762 HTTPERROR_302;
763 if (len <= 0)
764 HTTPERROR_404;
765
766 /* This is the hardcore match for announce*/
767 if ((*write_ptr == 'a') || (*write_ptr == '?'))
768 http_handle_announce(sock, ws, read_ptr);
769#ifdef WANT_FULLSCRAPE
770 else if (!memcmp(write_ptr, "scrape HTTP/", 12))
771 http_handle_fullscrape(sock, ws);
772#endif
773 /* This is the hardcore match for scrape */
774 else if (!memcmp(write_ptr, "sc", 2))
775 http_handle_scrape(sock, ws, read_ptr);
776 /* All the rest is matched the standard way */
777 else if (len == g_stats_path_len && !memcmp(write_ptr, g_stats_path, len))
778 http_handle_stats(sock, ws, read_ptr);
779 else
780 HTTPERROR_404;
781
782 /* If routines handled sending themselves, just return */
783 if (ws->reply_size == -2)
784 return 0;
785 /* If routine failed, let http error take over */
786 if (ws->reply_size <= 0)
787 HTTPERROR_500;
788
789 /* This one is rather ugly, so I take you step by step through it.
790
791 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to
792 write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our work buffer, which is enough for the static string
793 plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate
794 the space NOT needed to expand in reply_off
795 */
796 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size);
797 ws->reply = ws->outbuf + reply_off;
798
799 /* 2. Now we write the header so that its terminating '\0' lands exactly one byte before the content starts. The bound is the
800 space step 1 reserved, so the '\0' falls where step 3 expects it. Complete packet size is increased by size of header plus
801 one byte '\n', we will copy over '\0' in next step */
802 ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off),
803 "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size);
804
805 /* 3. Finally we join both blocks neatly */
806 ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n';
807
808 http_senddata(sock, ws);
809 return ws->reply_size;
810}