1 /*
2 * aprsc
3 *
4 * (c) Heikki Hannikainen, OH7LZB <hessu@hes.iki.fi>
5 *
6 * This program is licensed under the BSD license, which can be found
7 * in the file LICENSE.
8 */
9
10 /*
11 * http.c: the HTTP server thread, serving status pages and taking position uploads
12 */
13
14 #include <signal.h>
15 #include <poll.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <glob.h>
23
24 #include <event2/event.h>
25 #include <event2/http.h>
26 #include <event2/buffer.h>
27 #include <event2/keyvalq_struct.h>
28
29 #if 0
30 #ifdef HAVE_EVENT2_EVENT_H
31 #else // LIBEVENT 1.x
32 #include <event.h>
33 #include <evhttp.h>
34 #include <evutil.h>
35 #endif
36 #endif
37
38 #include "http.h"
39 #include "config.h"
40 #include "version.h"
41 #include "hlog.h"
42 #include "hmalloc.h"
43 #include "worker.h"
44 #include "status.h"
45 #include "passcode.h"
46 #include "incoming.h"
47 #include "login.h"
48 #include "counterdata.h"
49
50 #ifdef HAVE_LIBZ
51 #include <zlib.h>
52 #endif
53
54 /* supported HTTP transfer-encoding methods */
55 #define HTTP_COMPR_GZIP 1
56
57 const char *compr_type_strings[] = {
58 "none",
59 "gzip",
60 "deflate"
61 };
62
63 int http_shutting_down;
64 int http_reconfiguring;
65
66 unsigned long http_requests = 0;
67
68 struct http_static_t {
69 char *name;
70 char *filename;
71 };
72
73 struct worker_t *http_worker = NULL;
74 struct client_t *http_pseudoclient = NULL;
75
76 /*
77 * This is a list of files that the http server agrees to serve.
78 * Due to security concerns the list is static.
79 * It's a lot of work to actually implement a full-blown secure web
80 * server, and that's not what we're trying to do here.
81 */
82
83 static struct http_static_t http_static_files[] = {
84 { "/", "index.html" },
85 { "/favicon.ico", "favicon.ico" },
86 { "/aprsc.css", "aprsc.css" },
87 { "/aprsc.js", "aprsc.js" },
88 { "/aprsc-graph.js", "aprsc-graph.js" },
89 /* allow old index.html versions to load the new logo */
90 { "/aprsc-logo4.png", "aprsc-logo4.png" },
91 { "/aprsc-logo4@2x.png", "aprsc-logo4@2x.png" },
92 { "/aprsc-joulukissa.jpg", "aprsc-joulukissa.jpg" },
93 { "/excanvas.min.js", "excanvas.min.js" },
94 { "/jquery.flot.min.js", "jquery.flot.min.js" },
95 { "/jquery.flot.time.min.js", "jquery.flot.time.min.js" },
96 { "/jquery.flot.selection.min.js", "jquery.flot.selection.min.js" },
97 { "/jquery.flot.resize.min.js", "jquery.flot.resize.min.js" },
98 { "/motd.html", "motd.html" },
99 { "/jquery.min.js", "jquery.min.js" },
100 { "/angular.min.js", "angular.min.js" },
101 { "/angular-translate.min.js", "angular-translate.min.js" },
102 { "/angular-translate-loader-url.min.js", "angular-translate-loader-url.min.js" },
103 { "/ngDialog.min.js", "ngDialog.min.js" },
104 { "/ngDialog.min.css", "ngDialog.min.css" },
105 { "/ngDialog-theme-plain.min.css", "ngDialog-theme-plain.min.css" },
106 { "/bootstrap.min.css", "bootstrap.min.css" },
107 { "/fonts/glyphicons-halflings-regular.eot", "glyphicons-halflings-regular.eot" },
108 { "/fonts/glyphicons-halflings-regular.ttf", "glyphicons-halflings-regular.ttf" },
109 { "/fonts/glyphicons-halflings-regular.woff", "glyphicons-halflings-regular.woff" },
110 { "/fonts/glyphicons-halflings-regular.woff2", "glyphicons-halflings-regular.woff2" },
111 { NULL, NULL }
112 };
113
114 int http_language_count = 0;
115 struct http_static_t **http_language_files = NULL;
116
117 /*
118 * Content types for the required file extensions
119 */
120
121 static struct http_static_t http_content_types[] = {
122 { ".html", "text/html; charset=UTF-8" },
123 { ".ico", "image/x-icon" },
124 { ".css", "text/css; charset=UTF-8" },
125 { ".js", "application/x-javascript; charset=UTF-8" },
126 { ".jpg", "image/jpeg" },
127 { ".jpeg", "image/jpeg" },
128 { ".json", "application/json" },
129 { ".png", "image/png" },
130 { ".gif", "image/gif" },
131 { NULL, NULL }
132 };
133
134 /*
135 * Find a content-type for a file name
136 */
137
http_content_type(const char * fn)138 static char *http_content_type(const char *fn)
139 {
140 struct http_static_t *cmdp;
141 static char default_ctype[] = "text/html";
142 char *s;
143
144 s = strrchr(fn, '.');
145 if (!s)
146 return default_ctype;
147
148 for (cmdp = http_content_types; cmdp->name != NULL; cmdp++)
149 if (strcasecmp(cmdp->name, s) == 0)
150 break;
151
152 if (cmdp->name == NULL)
153 return default_ctype;
154
155 return cmdp->filename;
156 }
157
158 /*
159 * HTTP date formatting
160 */
161
http_date(char * buf,int len,time_t t)162 static int http_date(char *buf, int len, time_t t)
163 {
164 struct tm tb;
165 char *wkday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NULL };
166 char *mon[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
167 "Sep", "Oct", "Nov", "Dec", NULL };
168
169 gmtime_r(&t, &tb);
170
171 return snprintf(buf, len, "%s, %02d %s %04d %02d:%02d:%02d GMT",
172 wkday[tb.tm_wday], tb.tm_mday, mon[tb.tm_mon], tb.tm_year + 1900,
173 tb.tm_hour, tb.tm_min, tb.tm_sec);
174 }
175
http_header_base(struct evkeyvalq * headers,int last_modified)176 static void http_header_base(struct evkeyvalq *headers, int last_modified)
177 {
178 char dbuf[80];
179
180 http_date(dbuf, sizeof(dbuf), tick);
181
182 evhttp_add_header(headers, "Server", verstr_http);
183 evhttp_add_header(headers, "Date", dbuf);
184
185 if (last_modified) {
186 http_date(dbuf, sizeof(dbuf), last_modified);
187 evhttp_add_header(headers, "Last-Modified", dbuf);
188 }
189 }
190
191 /*
192 * Split a login + packet string. Terminates login string with 0,
193 * returns length of packet.
194 */
195
loginpost_split(char * post,int len,char ** login_string,char ** packet)196 int loginpost_split(char *post, int len, char **login_string, char **packet)
197 {
198 char *cr, *lf;
199 char *pack;
200 int packet_len;
201
202 // find line feed, terminate string
203 lf = memchr(post, '\n', len);
204 if (!lf)
205 return -1;
206
207 *lf = 0;
208
209 // find optional carriage return, terminate string
210 cr = memchr(post, '\r', lf-post);
211 if (cr)
212 *cr = 0;
213
214 // ok, we have a login string.
215 *login_string = post;
216
217 // now the first line contains a login string. Go for the packet body, find optional lf:
218 pack = lf + 1;
219 packet_len = len - (pack - post);
220 lf = memchr(pack, '\n', packet_len);
221 if (lf) {
222 *lf = 0;
223 packet_len = lf - pack;
224 }
225
226 // find optional carriage return, terminate string
227 cr = memchr(pack, '\r', packet_len);
228 if (cr) {
229 *cr = 0;
230 packet_len = cr - pack;
231 }
232
233 *packet = pack;
234
235 return packet_len;
236 }
237
238 /*
239 * Process an incoming HTTP or UDP packet by parsing it and pushing
240 * it to the dupecheck thread through the pseudoworker
241 */
242
pseudoclient_push_packet(struct worker_t * worker,struct client_t * pseudoclient,const char * username,char * packet,int packet_len)243 int pseudoclient_push_packet(struct worker_t *worker, struct client_t *pseudoclient, const char *username, char *packet, int packet_len)
244 {
245 int e;
246
247 /* fill the user's information in the pseudoclient's structure
248 * for the q construct handler's viewing pleasure
249 */
250 strncpy(pseudoclient->username, username, sizeof(pseudoclient->username));
251 pseudoclient->username[sizeof(pseudoclient->username)-1] = 0;
252 pseudoclient->username_len = strlen(pseudoclient->username);
253
254 /* ok, try to digest the packet */
255 e = incoming_parse(worker, pseudoclient, packet, packet_len);
256
257 pseudoclient->username[0] = 0;
258 pseudoclient->username_len = 0;
259
260 if (e < 0)
261 return e;
262
263 /* if the packet parser managed to digest the packet and put it to
264 * the thread-local incoming queue, flush it for dupecheck to
265 * grab
266 */
267 if (worker->pbuf_incoming_local)
268 incoming_flush(worker);
269
270 return e;
271 }
272
273 /*
274 * Accept a POST containing a position
275 */
276
277 #define MAX_HTTP_POST_DATA 2048
278
http_upload_position(struct evhttp_request * r,const char * remote_host)279 static void http_upload_position(struct evhttp_request *r, const char *remote_host)
280 {
281 struct evbuffer *bufin, *bufout;
282 struct evkeyvalq *req_headers;
283 const char *ctype, *clength;
284 int clength_i;
285 char post[MAX_HTTP_POST_DATA+1];
286 ev_ssize_t l;
287 char *login_string = NULL;
288 char *packet = NULL;
289 char *username = NULL;
290 char validated;
291 int e;
292 int packet_len;
293
294 req_headers = evhttp_request_get_input_headers(r);
295 ctype = evhttp_find_header(req_headers, "Content-Type");
296
297 if (!ctype || strcasecmp(ctype, "application/octet-stream") != 0) {
298 evhttp_send_error(r, HTTP_BADREQUEST, "Bad request, wrong or missing content-type");
299 return;
300 }
301
302 clength = evhttp_find_header(req_headers, "Content-Length");
303 if (!clength) {
304 evhttp_send_error(r, HTTP_BADREQUEST, "Bad request, missing content-length");
305 return;
306 }
307
308 clength_i = atoi(clength);
309 if (clength_i > MAX_HTTP_POST_DATA) {
310 evhttp_send_error(r, HTTP_BADREQUEST, "Bad request, too large body");
311 return;
312 }
313
314 /* get the HTTP POST body */
315 bufin = evhttp_request_get_input_buffer(r);
316 l = evbuffer_copyout(bufin, post, MAX_HTTP_POST_DATA);
317
318 /* Just for convenience and safety, null-terminate. Easier to log. */
319 post[MAX_HTTP_POST_DATA] = 0;
320 if (l <= MAX_HTTP_POST_DATA)
321 post[l] = 0;
322
323 if (l != clength_i) {
324 evhttp_send_error(r, HTTP_BADREQUEST, "Body size does not match content-length");
325 return;
326 }
327
328 hlog(LOG_DEBUG, "got post data: %s", post);
329
330 packet_len = loginpost_split(post, l, &login_string, &packet);
331 if (packet_len == -1) {
332 evhttp_send_error(r, HTTP_BADREQUEST, "No newline (LF) found in data");
333 return;
334 }
335
336 if (!login_string) {
337 evhttp_send_error(r, HTTP_BADREQUEST, "No login string in data");
338 return;
339 }
340
341 if (!packet) {
342 evhttp_send_error(r, HTTP_BADREQUEST, "No packet data found in data");
343 return;
344 }
345
346 hlog(LOG_DEBUG, "login string: %s", login_string);
347 hlog(LOG_DEBUG, "packet: %s", packet);
348
349 /* process the login string */
350 validated = http_udp_upload_login(remote_host, login_string, &username, "HTTP POST");
351 if (validated < 0) {
352 evhttp_send_error(r, HTTP_BADREQUEST, "Invalid login string");
353 return;
354 }
355
356 if (validated != 1) {
357 evhttp_send_error(r, 403, "Invalid passcode");
358 return;
359 }
360
361 /* packet size limits */
362 if (packet_len < PACKETLEN_MIN) {
363 evhttp_send_error(r, HTTP_BADREQUEST, "Packet too short");
364 return;
365 }
366
367 if (packet_len > PACKETLEN_MAX-2) {
368 evhttp_send_error(r, HTTP_BADREQUEST, "Packet too long");
369 return;
370 }
371
372 e = pseudoclient_push_packet(http_worker, http_pseudoclient, username, packet, packet_len);
373
374 if (e < 0) {
375 hlog(LOG_DEBUG, "http incoming packet parse failure code %d: %s", e, packet);
376 evhttp_send_error(r, HTTP_BADREQUEST, "Packet parsing failure");
377 return;
378 }
379
380 bufout = evbuffer_new();
381 evbuffer_add(bufout, "ok\n", 3);
382
383 struct evkeyvalq *headers = evhttp_request_get_output_headers(r);
384 http_header_base(headers, 0);
385 evhttp_add_header(headers, "Content-Type", "text/plain; charset=UTF-8");
386
387 evhttp_send_reply(r, HTTP_OK, "OK", bufout);
388 evbuffer_free(bufout);
389 }
390
391 /*
392 * Check if the client will dig a compressed response
393 */
394
395 #ifdef HAVE_LIBZ
http_check_req_compressed(struct evhttp_request * r)396 static int http_check_req_compressed(struct evhttp_request *r)
397 {
398 struct evkeyvalq *req_headers;
399 const char *accept_enc;
400
401 req_headers = evhttp_request_get_input_headers(r);
402 accept_enc = evhttp_find_header(req_headers, "Accept-Encoding");
403
404 if (!accept_enc)
405 return 0;
406
407 if (strstr(accept_enc, "gzip") != NULL)
408 return HTTP_COMPR_GZIP;
409
410 return 0;
411 }
412 #endif
413
414 /*
415 * gzip compress a buffer
416 */
417
418 #ifdef HAVE_LIBZ
http_compress_gzip(char * in,int ilen,char * out,int ospace)419 static int http_compress_gzip(char *in, int ilen, char *out, int ospace)
420 {
421 z_stream ctx;
422
423 ctx.zalloc = Z_NULL;
424 ctx.zfree = Z_NULL;
425 ctx.opaque = Z_NULL;
426
427 /* magic 15 bits + 16 enables gzip header generation */
428 if (deflateInit2(&ctx, 7, Z_DEFLATED, (15+16), MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
429 hlog(LOG_ERR, "http_compress_gzip: deflateInit2 failed");
430 return -1;
431 }
432
433 ctx.next_in = (unsigned char *)in;
434 ctx.avail_in = ilen;
435 ctx.next_out = (unsigned char *)out;
436 ctx.avail_out = ospace;
437
438 int ret = deflate(&ctx, Z_FINISH);
439 if (ret != Z_STREAM_END) {
440 hlog(LOG_ERR, "http_compress_gzip: deflate returned %d instead of Z_STREAM_END", ret);
441 (void)deflateEnd(&ctx);
442 return -1;
443 }
444
445 int olen = ospace - ctx.avail_out;
446 //hlog(LOG_DEBUG, "http_compress_gzip: compressed %d bytes to %d bytes: %.1f %%", ilen, olen, (float)olen / (float)ilen * 100.0);
447
448 (void)deflateEnd(&ctx);
449
450 return olen;
451 }
452 #endif
453
454 /*
455 * Transmit an OK HTTP response, given headers and data.
456 * Compress response, if possible.
457 */
458
http_send_reply_ok(struct evhttp_request * r,struct evkeyvalq * headers,char * data,int len,int allow_compress)459 static void http_send_reply_ok(struct evhttp_request *r, struct evkeyvalq *headers, char *data, int len, int allow_compress)
460 {
461 #ifdef HAVE_LIBZ
462 char *compr = NULL;
463
464 /* Gzipping files below 150 bytes can actually make them larger. */
465 if (len > 150 && allow_compress) {
466 /* Consider returning a compressed version */
467 int compr_type = http_check_req_compressed(r);
468 /*
469 if (compr_type)
470 hlog(LOG_DEBUG, "http_send_reply_ok, client supports transfer-encoding: %s", compr_type_strings[compr_type]);
471 */
472
473 if (compr_type == HTTP_COMPR_GZIP) {
474 /* for small files it's possible that the output is actually
475 * larger than the input
476 */
477 int oblen = len + 60;
478 compr = hmalloc(oblen);
479 int olen = http_compress_gzip(data, len, compr, oblen);
480 /* If compression succeeded, replace buffer with the compressed one and free the
481 * uncompressed one. Add HTTP header to indicate compressed response.
482 * If the file got larger, send uncompressed.
483 */
484 if (olen > 0 && olen < len) {
485 data = compr;
486 len = olen;
487 evhttp_add_header(headers, "Content-Encoding", "gzip");
488 }
489 }
490 }
491 #endif
492
493 struct evbuffer *buffer = evbuffer_new();
494 evbuffer_add(buffer, data, len);
495
496 evhttp_send_reply(r, HTTP_OK, "OK", buffer);
497 evbuffer_free(buffer);
498
499 #ifdef HAVE_LIBZ
500 if (compr)
501 hfree(compr);
502 #endif
503 }
504
505
506 /*
507 * Generate a status JSON response
508 */
509
http_status(struct evhttp_request * r)510 static void http_status(struct evhttp_request *r)
511 {
512 char *json;
513
514 struct evkeyvalq *headers = evhttp_request_get_output_headers(r);
515 http_header_base(headers, tick);
516 evhttp_add_header(headers, "Content-Type", "application/json; charset=UTF-8");
517 evhttp_add_header(headers, "Cache-Control", "max-age=9");
518
519 json = status_json_string(0, 0);
520 http_send_reply_ok(r, headers, json, strlen(json), 1);
521 free(json);
522 }
523
524 /*
525 * Return counterdata in JSON
526 */
527
http_counterdata(struct evhttp_request * r,const char * uri)528 static void http_counterdata(struct evhttp_request *r, const char *uri)
529 {
530 char *json;
531 const char *query;
532
533 query = evhttp_uri_get_query(evhttp_request_get_evhttp_uri(r));
534 hlog(LOG_DEBUG, "http counterdata query: %s", query);
535
536 json = cdata_json_string(query);
537 if (!json) {
538 evhttp_send_error(r, HTTP_BADREQUEST, "Bad request, no such counter");
539 return;
540 }
541
542 struct evkeyvalq *headers = evhttp_request_get_output_headers(r);
543 http_header_base(headers, tick);
544 evhttp_add_header(headers, "Content-Type", "application/json; charset=UTF-8");
545 evhttp_add_header(headers, "Cache-Control", "max-age=58");
546
547 http_send_reply_ok(r, headers, json, strlen(json), 1);
548 hfree(json);
549 }
550
551 /*
552 * HTTP static file server
553 */
554
http_static_file(struct evhttp_request * r,const char * fname)555 static void http_static_file(struct evhttp_request *r, const char *fname)
556 {
557 struct stat st;
558 int fd;
559 int file_size;
560 struct evkeyvalq *req_headers;
561 const char *ims;
562 char last_modified[128];
563 char *contenttype;
564 char *buf;
565
566 fd = open(fname, 0, O_RDONLY);
567 if (fd < 0) {
568 if (errno == ENOENT) {
569 /* don't complain about missing motd.html - it's optional. */
570 int level = LOG_ERR;
571 if (strcmp(fname, "motd.html") == 0)
572 level = LOG_DEBUG;
573 hlog(level, "http static file '%s' not found: 404", fname);
574 evhttp_send_error(r, HTTP_NOTFOUND, "Not found");
575 return;
576 }
577
578 hlog(LOG_ERR, "http static file '%s' could not be opened for reading: %s", fname, strerror(errno));
579 evhttp_send_error(r, HTTP_INTERNAL, "Could not access file");
580 return;
581 }
582
583 if (fstat(fd, &st) == -1) {
584 hlog(LOG_ERR, "http static file '%s' could not fstat() after opening: %s", fname, strerror(errno));
585 evhttp_send_error(r, HTTP_INTERNAL, "Could not access file");
586 if (close(fd) < 0)
587 hlog(LOG_ERR, "http static file '%s' could not be closed after failed stat: %s", fname, strerror(errno));
588 return;
589 }
590
591 http_date(last_modified, sizeof(last_modified), st.st_mtime);
592
593 contenttype = http_content_type(fname);
594 //hlog(LOG_DEBUG, "found content-type %s", contenttype);
595
596 struct evkeyvalq *headers = evhttp_request_get_output_headers(r);
597 http_header_base(headers, st.st_mtime);
598 evhttp_add_header(headers, "Content-Type", contenttype);
599
600 /* Consider an IMS hit */
601 req_headers = evhttp_request_get_input_headers(r);
602 ims = evhttp_find_header(req_headers, "If-Modified-Since");
603
604 if ((ims) && strcasecmp(ims, last_modified) == 0) {
605 hlog(LOG_DEBUG, "http static file '%s' IMS hit", fname);
606 evhttp_send_reply(r, HTTP_NOTMODIFIED, "Not modified", NULL);
607 if (close(fd) < 0)
608 hlog(LOG_ERR, "http static file '%s' could not be closed after failed stat: %s", fname, strerror(errno));
609 return;
610 }
611
612 file_size = st.st_size;
613
614 /* yes, we are not going to serve large files. */
615 buf = hmalloc(file_size);
616 int n = read(fd, buf, file_size);
617
618 if (close(fd) < 0) {
619 hlog(LOG_ERR, "http static file '%s' could not be closed after reading: %s", fname, strerror(errno));
620 evhttp_send_error(r, HTTP_INTERNAL, "Could not access file");
621 hfree(buf);
622 return;
623 }
624
625 if (n != file_size) {
626 hlog(LOG_ERR, "http static file '%s' could only read %d of %d bytes", fname, n, file_size);
627 evhttp_send_error(r, HTTP_INTERNAL, "Could not access file");
628 hfree(buf);
629 return;
630 }
631
632 int allow_compress;
633 if (strncmp(contenttype, "image/", 6) == 0)
634 allow_compress = 0;
635 else
636 allow_compress = 1;
637
638 http_send_reply_ok(r, headers, buf, n, allow_compress);
639 hfree(buf);
640 }
641
642
643 #define HTTP_FNAME_LEN 1024
644
http_route_static(struct evhttp_request * r,const char * uri)645 static void http_route_static(struct evhttp_request *r, const char *uri)
646 {
647 struct http_static_t *cmdp;
648 char fname[HTTP_FNAME_LEN];
649
650 for (cmdp = http_static_files; cmdp->name != NULL; cmdp++)
651 if (strcmp(cmdp->name, uri) == 0)
652 break;
653
654 if (cmdp->name == NULL) {
655 hlog(LOG_DEBUG, "http static file request: 404: %s", uri);
656 evhttp_send_error(r, HTTP_NOTFOUND, "Not found");
657 return;
658 }
659
660 snprintf(fname, HTTP_FNAME_LEN, "%s/%s", webdir, cmdp->filename);
661
662 //hlog(LOG_DEBUG, "http static file request: %s", uri);
663
664 return http_static_file(r, fname);
665 }
666
667 /*
668 * Return translated strings in JSON
669 */
670
http_strings(struct evhttp_request * r,const char * uri)671 static void http_strings(struct evhttp_request *r, const char *uri)
672 {
673 const char *query;
674
675 query = evhttp_uri_get_query(evhttp_request_get_evhttp_uri(r));
676 //hlog(LOG_DEBUG, "strings query: %s", query);
677
678 const char *lang = NULL;
679 struct evkeyvalq args;
680
681 if (evhttp_parse_query_str(query, &args) == 0) {
682 lang = evhttp_find_header(&args, "lang");
683 //hlog(LOG_DEBUG, "lang: %s, checking against %d languages", lang, http_language_count);
684
685 int i;
686 for (i = 0; i < http_language_count; i++) {
687 if (strcasecmp(lang, http_language_files[i]->name) == 0) {
688 hlog(LOG_DEBUG, "http strings query: %s: %s", lang, http_language_files[i]->filename);
689 evhttp_clear_headers(&args);
690
691 return http_static_file(r, http_language_files[i]->filename);
692 }
693 }
694 }
695
696 hlog(LOG_DEBUG, "http strings query: 404: %s", uri);
697 evhttp_send_error(r, HTTP_NOTFOUND, "Not found");
698
699 evhttp_clear_headers(&args);
700
701 return;
702 }
703
704
705 /*
706 * HTTP request router
707 */
708
http_router(struct evhttp_request * r,void * which_server)709 static void http_router(struct evhttp_request *r, void *which_server)
710 {
711 char *remote_host;
712 ev_uint16_t remote_port;
713 const char *uri = evhttp_request_get_uri(r);
714 struct evhttp_connection *conn = evhttp_request_get_connection(r);
715 evhttp_connection_get_peer(conn, &remote_host, &remote_port);
716
717 hlog(LOG_DEBUG, "http %s [%s] request %s", (which_server == (void *)1) ? "status" : "upload", remote_host, uri);
718
719 http_requests++;
720
721 /* status server routing */
722 if (which_server == (void *)1) {
723 if (strncmp(uri, "/status.json", 12) == 0) {
724 http_status(r);
725 return;
726 }
727
728 if (strncmp(uri, "/counterdata?", 13) == 0) {
729 http_counterdata(r, uri);
730 return;
731 }
732
733 if (strncmp(uri, "/strings?", 9) == 0) {
734 http_strings(r, uri);
735 return;
736 }
737
738 http_route_static(r, uri);
739 return;
740 }
741
742 /* position upload server routing */
743 if (which_server == (void *)2) {
744 if (strncmp(uri, "/", 7) == 0) {
745 http_upload_position(r, remote_host);
746 return;
747 }
748
749 hlog(LOG_DEBUG, "http request on upload server for '%s': 404 not found", uri);
750 evhttp_send_error(r, HTTP_NOTFOUND, "Not found");
751 return;
752 }
753
754 hlog(LOG_ERR, "http request on unknown server for '%s': 404 not found", uri);
755 evhttp_send_error(r, HTTP_NOTFOUND, "Server not found");
756
757 return;
758 }
759
760 struct event *ev_timer = NULL;
761 struct evhttp *srvr_status = NULL;
762 struct evhttp *srvr_upload = NULL;
763 struct event_base *libbase = NULL;
764
765 /*
766 * HTTP timer event, mainly to catch the shutdown signal
767 */
768
http_timer(evutil_socket_t fd,short events,void * arg)769 static void http_timer(evutil_socket_t fd, short events, void *arg)
770 {
771 struct timeval http_timer_tv;
772 http_timer_tv.tv_sec = 0;
773 http_timer_tv.tv_usec = 200000;
774
775 //hlog(LOG_DEBUG, "http_timer fired");
776
777 if (http_shutting_down || http_reconfiguring) {
778 http_timer_tv.tv_usec = 1000;
779 event_base_loopexit(libbase, &http_timer_tv);
780 return;
781 }
782
783 event_add(ev_timer, &http_timer_tv);
784 }
785
http_srvr_defaults(struct evhttp * srvr)786 static void http_srvr_defaults(struct evhttp *srvr)
787 {
788 // limit what the clients can do a bit
789 evhttp_set_allowed_methods(srvr, EVHTTP_REQ_GET);
790 evhttp_set_timeout(srvr, 30);
791 evhttp_set_max_body_size(srvr, 10*1024);
792 evhttp_set_max_headers_size(srvr, 10*1024);
793
794 // TODO: How to limit the amount of concurrent HTTP connections?
795 }
796
http_server_free(void)797 static void http_server_free(void)
798 {
799 if (ev_timer) {
800 event_del(ev_timer);
801 hfree(ev_timer);
802 ev_timer = NULL;
803 }
804
805 if (srvr_status) {
806 evhttp_free(srvr_status);
807 srvr_status = NULL;
808 }
809
810 if (srvr_upload) {
811 evhttp_free(srvr_upload);
812 srvr_upload = NULL;
813 }
814
815 if (libbase) {
816 event_base_free(libbase);
817 libbase = NULL;
818 }
819
820 if (http_language_files && http_language_count) {
821 int i;
822 for (i = 0; i < http_language_count; i++) {
823 hfree(http_language_files[i]->name);
824 hfree(http_language_files[i]->filename);
825 hfree(http_language_files[i]);
826 http_language_files[i] = NULL;
827 }
828 hfree(http_language_files);
829 http_language_files = NULL;
830 http_language_count = 0;
831 }
832 }
833
834 /*
835 * Scan for language string files
836 */
837
lang_scan(void)838 void lang_scan(void)
839 {
840 hlog(LOG_DEBUG, "Scanning languages");
841
842 struct http_static_t **new_language_files = NULL;
843 int languages_loaded = 0;
844
845 #define LANG_FNAME_PREFIX "strings-"
846 #define LANG_FNAME_SUFFIX ".json"
847 const char glob_s[] = LANG_FNAME_PREFIX "*" LANG_FNAME_SUFFIX;
848 int glob_l = strlen(webdir) + 1 + strlen(glob_s) + 1;
849 char *fullglob = hmalloc(glob_l);
850 snprintf(fullglob, glob_l, "%s/%s", webdir, glob_s);
851
852 glob_t globbuf;
853
854 int ret = glob(fullglob, GLOB_NOSORT|GLOB_ERR, NULL, &globbuf);
855 if (ret == 0) {
856 int i;
857
858 hlog(LOG_DEBUG, "%d language files found", globbuf.gl_pathc);
859
860 new_language_files = hmalloc(sizeof(*new_language_files) * globbuf.gl_pathc);
861 memset(new_language_files, 0, sizeof(*new_language_files) * globbuf.gl_pathc);
862
863 for (i = 0; i < globbuf.gl_pathc; i++) {
864 hlog(LOG_DEBUG, "Language file: %s", globbuf.gl_pathv[i]);
865
866 char *lang = NULL;
867 char *bp, *ep = NULL;
868 bp = strstr(globbuf.gl_pathv[i], LANG_FNAME_PREFIX);
869 if (bp) {
870 bp += strlen(LANG_FNAME_PREFIX);
871 ep = strstr(bp, LANG_FNAME_SUFFIX);
872 }
873
874 if (ep) {
875 int langlen = ep - bp;
876 lang = hmalloc(langlen + 1);
877 strncpy(lang, bp, langlen + 1);
878 lang[langlen] = 0;
879
880 new_language_files[languages_loaded] = hmalloc(sizeof(struct http_static_t));
881 new_language_files[languages_loaded]->name = lang;
882 new_language_files[languages_loaded]->filename = hstrdup(globbuf.gl_pathv[i]);
883
884 hlog(LOG_INFO, "Language %d installed: %s: %s", languages_loaded, lang, globbuf.gl_pathv[i]);
885
886 languages_loaded++;
887 }
888
889 }
890 } else {
891 switch (ret) {
892 case GLOB_NOSPACE:
893 hlog(LOG_ERR, "Language file search failed: Out of memory");
894 break;
895 case GLOB_ABORTED:
896 hlog(LOG_ERR, "Language file search failed: Read error / %s", strerror(errno));
897 break;
898 case GLOB_NOMATCH:
899 hlog(LOG_INFO, "Language file search failed: No files found");
900 break;
901 default:
902 break;
903 }
904 }
905
906 globfree(&globbuf);
907 hfree(fullglob);
908
909 http_language_count = languages_loaded;
910 http_language_files = new_language_files;
911 }
912
913 /*
914 * HTTP server thread
915 */
916
http_thread(void * asdf)917 void http_thread(void *asdf)
918 {
919 sigset_t sigs_to_block;
920 struct http_config_t *lc;
921 struct timeval http_timer_tv;
922
923 http_timer_tv.tv_sec = 0;
924 http_timer_tv.tv_usec = 200000;
925
926 pthreads_profiling_reset("http");
927
928 sigemptyset(&sigs_to_block);
929 sigaddset(&sigs_to_block, SIGALRM);
930 sigaddset(&sigs_to_block, SIGINT);
931 sigaddset(&sigs_to_block, SIGTERM);
932 sigaddset(&sigs_to_block, SIGQUIT);
933 sigaddset(&sigs_to_block, SIGHUP);
934 sigaddset(&sigs_to_block, SIGURG);
935 sigaddset(&sigs_to_block, SIGPIPE);
936 sigaddset(&sigs_to_block, SIGUSR1);
937 sigaddset(&sigs_to_block, SIGUSR2);
938 pthread_sigmask(SIG_BLOCK, &sigs_to_block, NULL);
939
940 /* start the http thread, which will start server threads */
941 hlog(LOG_INFO, "HTTP thread starting...");
942
943 /* we allocate a worker structure to be used within the http thread
944 * for parsing incoming packets and passing them on to the dupecheck
945 * thread.
946 */
947 http_worker = worker_alloc();
948 http_worker->id = 80;
949
950 /* we also need a client structure to be used with incoming
951 * HTTP position uploads
952 */
953 http_pseudoclient = pseudoclient_setup(80);
954
955 http_reconfiguring = 1;
956 while (!http_shutting_down) {
957 if (http_reconfiguring) {
958 http_reconfiguring = 0;
959
960 // shut down existing instance
961 http_server_free();
962
963 lang_scan();
964
965 // do init
966 #if 1
967 libbase = event_base_new(); // libevent 2.x
968 #else
969 libbase = event_init(); // libevent 1.x
970 #endif
971
972 // timer for the whole libevent, to catch shutdown signal
973 ev_timer = event_new(libbase, -1, EV_TIMEOUT, http_timer, NULL);
974 event_add(ev_timer, &http_timer_tv);
975
976 for (lc = http_config; (lc); lc = lc->next) {
977 hlog(LOG_INFO, "Binding HTTP %s socket %s:%d", lc->upload_port ? "upload" : "status", lc->host, lc->port);
978
979 struct evhttp *srvr;
980 struct evhttp_bound_socket *handle;
981
982 if (lc->upload_port) {
983 if (!srvr_upload) {
984 srvr_upload = evhttp_new(libbase);
985 http_srvr_defaults(srvr_upload);
986 evhttp_set_allowed_methods(srvr_upload, EVHTTP_REQ_POST); /* uploads are POSTs, after all */
987 evhttp_set_gencb(srvr_upload, http_router, (void *)2);
988 }
989 srvr = srvr_upload;
990 } else {
991 if (!srvr_status) {
992 srvr_status = evhttp_new(libbase);
993 http_srvr_defaults(srvr_status);
994 evhttp_set_gencb(srvr_status, http_router, (void *)1);
995 }
996 srvr = srvr_status;
997 }
998
999 handle = evhttp_bind_socket_with_handle(srvr, lc->host, lc->port);
1000 if (!handle) {
1001 hlog(LOG_ERR, "Failed to bind HTTP socket %s:%d: %s", lc->host, lc->port, strerror(errno));
1002 // TODO: should exit?
1003 }
1004 }
1005
1006 hlog(LOG_INFO, "HTTP thread ready.");
1007 }
1008
1009 event_base_dispatch(libbase);
1010 }
1011
1012 hlog(LOG_DEBUG, "HTTP thread shutting down...");
1013
1014 http_server_free();
1015
1016 /* free up the pseudo-client */
1017 client_free(http_pseudoclient);
1018 http_pseudoclient = NULL;
1019
1020 /* free up the pseudo-worker structure */
1021 worker_free_buffers(http_worker);
1022 hfree(http_worker);
1023 http_worker = NULL;
1024 }
1025
1026