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