Bring back the --base functionality.
[darkstat] / http.c
1 /* darkstat 3
2 * copyright (c) 2001-2012 Emil Mikulic.
3 *
4 * http.c: embedded webserver.
5 * This borrows a lot of code from darkhttpd.
6 *
7 * You may use, modify and redistribute this file under the terms of the
8 * GNU General Public License version 2. (see COPYING.GPL)
9 */
10
11 #include "cdefs.h"
12 #include "config.h"
13 #include "conv.h"
14 #include "err.h"
15 #include "graph_db.h"
16 #include "hosts_db.h"
17 #include "http.h"
18 #include "now.h"
19 #include "queue.h"
20 #include "str.h"
21
22 #include <sys/uio.h>
23 #include <sys/socket.h>
24 #include <arpa/inet.h>
25 #include <netinet/in.h>
26 #include <netdb.h>
27 #include <assert.h>
28 #include <ctype.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <signal.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <time.h>
37 #include <unistd.h>
38 #include <zlib.h>
39
40 static char *http_base_url = NULL;
41
42 static const char mime_type_xml[] = "text/xml";
43 static const char mime_type_html[] = "text/html; charset=us-ascii";
44 static const char mime_type_css[] = "text/css";
45 static const char mime_type_js[] = "text/javascript";
46 static const char encoding_identity[] = "identity";
47 static const char encoding_gzip[] = "gzip";
48
49 static const char server[] = PACKAGE_NAME "/" PACKAGE_VERSION;
50 static int idletime = 60;
51 #define MAX_REQUEST_LENGTH 4000
52
53 static int *insocks = NULL;
54 static unsigned int insock_num = 0;
55
56 struct connection {
57 LIST_ENTRY(connection) entries;
58
59 int socket;
60 struct sockaddr_storage client;
61 time_t last_active_mono;
62 enum {
63 RECV_REQUEST, /* receiving request */
64 SEND_HEADER_AND_REPLY, /* try to send header+reply together */
65 SEND_HEADER, /* sending generated header */
66 SEND_REPLY, /* sending reply */
67 DONE /* conn closed, need to remove from queue */
68 } state;
69
70 /* char request[request_length+1] is null-terminated */
71 char *request;
72 size_t request_length;
73 int accept_gzip;
74
75 /* request fields */
76 char *method, *uri, *query; /* query can be NULL */
77
78 char *header;
79 const char *mime_type, *encoding, *header_extra;
80 size_t header_length, header_sent;
81 int header_dont_free, header_only, http_code;
82
83 char *reply;
84 int reply_dont_free;
85 size_t reply_length, reply_sent;
86
87 unsigned int total_sent; /* header + body = total, for logging */
88 };
89
90 static LIST_HEAD(conn_list_head, connection) connlist =
91 LIST_HEAD_INITIALIZER(conn_list_head);
92
93 struct bindaddr_entry {
94 STAILQ_ENTRY(bindaddr_entry) entries;
95 const char *s;
96 };
97 static STAILQ_HEAD(bindaddrs_head, bindaddr_entry) bindaddrs =
98 STAILQ_HEAD_INITIALIZER(bindaddrs);
99
100 /* ---------------------------------------------------------------------------
101 * Decode URL by converting %XX (where XX are hexadecimal digits) to the
102 * character it represents. Don't forget to free the return value.
103 */
104 static char *urldecode(const char *url)
105 {
106 size_t i, len = strlen(url);
107 char *out = xmalloc(len+1);
108 int pos;
109
110 for (i=0, pos=0; i<len; i++)
111 {
112 if (url[i] == '%' && i+2 < len &&
113 isxdigit(url[i+1]) && isxdigit(url[i+2]))
114 {
115 /* decode %XX */
116 #define HEX_TO_DIGIT(hex) ( \
117 ((hex) >= 'A' && (hex) <= 'F') ? ((hex)-'A'+10): \
118 ((hex) >= 'a' && (hex) <= 'f') ? ((hex)-'a'+10): \
119 ((hex)-'0') )
120
121 out[pos++] = HEX_TO_DIGIT(url[i+1]) * 16 +
122 HEX_TO_DIGIT(url[i+2]);
123 i += 2;
124
125 #undef HEX_TO_DIGIT
126 }
127 else
128 {
129 /* straight copy */
130 out[pos++] = url[i];
131 }
132 }
133 out[pos] = 0;
134 #if 0
135 /* don't really need to realloc here - it's probably a performance hit */
136 out = xrealloc(out, strlen(out)+1); /* dealloc what we don't need */
137 #endif
138 return (out);
139 }
140
141
142
143 /* ---------------------------------------------------------------------------
144 * Consolidate slashes in-place by shifting parts of the string over repeated
145 * slashes.
146 */
147 static void consolidate_slashes(char *s)
148 {
149 size_t left = 0, right = 0;
150 int saw_slash = 0;
151
152 assert(s != NULL);
153
154 while (s[right] != '\0')
155 {
156 if (saw_slash)
157 {
158 if (s[right] == '/') right++;
159 else
160 {
161 saw_slash = 0;
162 s[left++] = s[right++];
163 }
164 }
165 else
166 {
167 if (s[right] == '/') saw_slash++;
168 s[left++] = s[right++];
169 }
170 }
171 s[left] = '\0';
172 }
173
174
175
176 /* ---------------------------------------------------------------------------
177 * Resolve /./ and /../ in a URI, returing a new, safe URI, or NULL if the URI
178 * is invalid/unsafe. Returned buffer needs to be deallocated.
179 */
180 static char *make_safe_uri(char *uri)
181 {
182 char **elem, *out;
183 unsigned int slashes = 0, elements = 0;
184 size_t urilen, i, j, pos;
185
186 assert(uri != NULL);
187 if (uri[0] != '/')
188 return (NULL);
189 consolidate_slashes(uri);
190 urilen = strlen(uri);
191
192 /* count the slashes */
193 for (i=0, slashes=0; i<urilen; i++)
194 if (uri[i] == '/') slashes++;
195
196 /* make an array for the URI elements */
197 elem = xmalloc(sizeof(*elem) * slashes);
198 for (i=0; i<slashes; i++)
199 elem[i] = (NULL);
200
201 /* split by slashes and build elem[] array */
202 for (i=1; i<urilen;)
203 {
204 /* look for the next slash */
205 for (j=i; j<urilen && uri[j] != '/'; j++)
206 ;
207
208 /* process uri[i,j) */
209 if ((j == i+1) && (uri[i] == '.'))
210 /* "." */;
211 else if ((j == i+2) && (uri[i] == '.') && (uri[i+1] == '.'))
212 {
213 /* ".." */
214 if (elements == 0)
215 {
216 /*
217 * Unsafe string so free elem[]. All its elements are free
218 * at this point.
219 */
220 free(elem);
221 return (NULL);
222 }
223 else
224 {
225 elements--;
226 free(elem[elements]);
227 }
228 }
229 else elem[elements++] = split_string(uri, i, j);
230
231 i = j + 1; /* uri[j] is a slash - move along one */
232 }
233
234 /* reassemble */
235 out = xmalloc(urilen+1); /* it won't expand */
236 pos = 0;
237 for (i=0; i<elements; i++)
238 {
239 size_t delta = strlen(elem[i]);
240
241 assert(pos <= urilen);
242 out[pos++] = '/';
243
244 assert(pos+delta <= urilen);
245 memcpy(out+pos, elem[i], delta);
246 free(elem[i]);
247 pos += delta;
248 }
249 free(elem);
250
251 if ((elements == 0) || (uri[urilen-1] == '/')) out[pos++] = '/';
252 assert(pos <= urilen);
253 out[pos] = '\0';
254
255 #if 0
256 /* don't really need to do this and it's probably a performance hit: */
257 /* shorten buffer if necessary */
258 if (pos != urilen) out = xrealloc(out, strlen(out)+1);
259 #endif
260 return (out);
261 }
262
263 /* ---------------------------------------------------------------------------
264 * Allocate and initialize an empty connection.
265 */
266 static struct connection *new_connection(void)
267 {
268 struct connection *conn = xmalloc(sizeof(*conn));
269
270 conn->socket = -1;
271 memset(&conn->client, 0, sizeof(conn->client));
272 conn->last_active_mono = now_mono();
273 conn->request = NULL;
274 conn->request_length = 0;
275 conn->accept_gzip = 0;
276 conn->method = NULL;
277 conn->uri = NULL;
278 conn->query = NULL;
279 conn->header = NULL;
280 conn->mime_type = NULL;
281 conn->encoding = NULL;
282 conn->header_extra = "";
283 conn->header_length = 0;
284 conn->header_sent = 0;
285 conn->header_dont_free = 0;
286 conn->header_only = 0;
287 conn->http_code = 0;
288 conn->reply = NULL;
289 conn->reply_dont_free = 0;
290 conn->reply_length = 0;
291 conn->reply_sent = 0;
292 conn->total_sent = 0;
293
294 /* Make it harmless so it gets garbage-collected if it should, for some
295 * reason, fail to be correctly filled out.
296 */
297 conn->state = DONE;
298
299 return (conn);
300 }
301
302
303
304 /* ---------------------------------------------------------------------------
305 * Accept a connection from sockin and add it to the connection queue.
306 */
307 static void accept_connection(const int sockin)
308 {
309 struct sockaddr_storage addrin;
310 socklen_t sin_size;
311 struct connection *conn;
312 char ipaddr[INET6_ADDRSTRLEN], portstr[12];
313 int sock;
314
315 sin_size = (socklen_t)sizeof(addrin);
316 sock = accept(sockin, (struct sockaddr *)&addrin, &sin_size);
317 if (sock == -1)
318 {
319 if (errno == ECONNABORTED || errno == EINTR)
320 {
321 verbosef("accept() failed: %s", strerror(errno));
322 return;
323 }
324 /* else */ err(1, "accept()");
325 }
326
327 fd_set_nonblock(sock);
328
329 /* allocate and initialise struct connection */
330 conn = new_connection();
331 conn->socket = sock;
332 conn->state = RECV_REQUEST;
333 memcpy(&conn->client, &addrin, sizeof(conn->client));
334 LIST_INSERT_HEAD(&connlist, conn, entries);
335
336 getnameinfo((struct sockaddr *) &addrin, sin_size,
337 ipaddr, sizeof(ipaddr), portstr, sizeof(portstr),
338 NI_NUMERICHOST | NI_NUMERICSERV);
339 verbosef("accepted connection from %s:%s", ipaddr, portstr);
340 }
341
342
343
344 /* ---------------------------------------------------------------------------
345 * Log a connection, then cleanly deallocate its internals.
346 */
347 static void free_connection(struct connection *conn)
348 {
349 dverbosef("free_connection(%d)", conn->socket);
350 if (conn->socket != -1)
351 close(conn->socket);
352 free(conn->request);
353 free(conn->method);
354 free(conn->uri);
355 free(conn->query);
356 if (!conn->header_dont_free)
357 free(conn->header);
358 if (!conn->reply_dont_free)
359 free(conn->reply);
360 }
361
362
363
364 /* ---------------------------------------------------------------------------
365 * Format [when] as an RFC1123 date, stored in the specified buffer. The same
366 * buffer is returned for convenience.
367 */
368 #define DATE_LEN 30 /* strlen("Fri, 28 Feb 2003 00:02:08 GMT")+1 */
369 static char *rfc1123_date(char *dest, time_t when) {
370 if (strftime(dest, DATE_LEN,
371 "%a, %d %b %Y %H:%M:%S %Z", gmtime(&when) ) == 0)
372 errx(1, "strftime() failed [%s]", dest);
373 return dest;
374 }
375
376 static void generate_header(struct connection *conn,
377 const int code, const char *text)
378 {
379 char date[DATE_LEN];
380
381 assert(conn->header == NULL);
382 assert(conn->mime_type != NULL);
383 if (conn->encoding == NULL)
384 conn->encoding = encoding_identity;
385
386 verbosef("http: %d %s (%s: %zu bytes)",
387 code,
388 text,
389 conn->encoding,
390 conn->reply_length);
391 conn->header_length = xasprintf(&(conn->header),
392 "HTTP/1.1 %d %s\r\n"
393 "Date: %s\r\n"
394 "Server: %s\r\n"
395 "Vary: Accept-Encoding\r\n"
396 "Content-Type: %s\r\n"
397 "Content-Length: %qu\r\n"
398 "Content-Encoding: %s\r\n"
399 "X-Robots-Tag: noindex, noarchive\r\n"
400 "%s"
401 "\r\n",
402 code, text,
403 rfc1123_date(date, now_real()),
404 server,
405 conn->mime_type,
406 (qu)conn->reply_length,
407 conn->encoding,
408 conn->header_extra);
409 conn->http_code = code;
410 }
411
412
413
414 /* ---------------------------------------------------------------------------
415 * A default reply for any (erroneous) occasion.
416 */
417 static void default_reply(struct connection *conn,
418 const int errcode, const char *errname, const char *format, ...)
419 _printflike_(4, 5);
420 static void default_reply(struct connection *conn,
421 const int errcode, const char *errname, const char *format, ...)
422 {
423 char *reason;
424 va_list va;
425
426 va_start(va, format);
427 xvasprintf(&reason, format, va);
428 va_end(va);
429
430 conn->reply_length = xasprintf(&(conn->reply),
431 "<html><head><title>%d %s</title></head><body>\n"
432 "<h1>%s</h1>\n" /* errname */
433 "%s\n" /* reason */
434 "<hr>\n"
435 "Generated by %s"
436 "</body></html>\n",
437 errcode, errname, errname, reason, server);
438 free(reason);
439
440 /* forget any dangling metadata */
441 conn->mime_type = mime_type_html;
442 conn->encoding = encoding_identity;
443
444 generate_header(conn, errcode, errname);
445 }
446
447
448
449 /* ---------------------------------------------------------------------------
450 * Parses a single HTTP request field. Returns string from end of [field] to
451 * first \r, \n or end of request string. Returns NULL if [field] can't be
452 * matched.
453 *
454 * You need to remember to deallocate the result.
455 * example: parse_field(conn, "Referer: ");
456 */
457 static char *parse_field(const struct connection *conn, const char *field)
458 {
459 size_t bound1, bound2;
460 char *pos;
461
462 /* find start */
463 pos = strstr(conn->request, field);
464 if (pos == NULL)
465 return (NULL);
466 bound1 = pos - conn->request + strlen(field);
467
468 /* find end */
469 for (bound2 = bound1;
470 conn->request[bound2] != '\r' &&
471 bound2 < conn->request_length; bound2++)
472 ;
473
474 /* copy to buffer */
475 return (split_string(conn->request, bound1, bound2));
476 }
477
478
479
480 /* ---------------------------------------------------------------------------
481 * Parse an HTTP request like "GET /hosts/?sort=in HTTP/1.1" to get the method
482 * (GET), the uri (/hosts/), the query (sort=in) and whether the UA will
483 * accept gzip encoding. Remember to deallocate all these buffers. Query
484 * can be NULL. The method will be returned in uppercase.
485 */
486 static int parse_request(struct connection *conn)
487 {
488 size_t bound1, bound2, mid;
489 char *accept_enc;
490
491 /* parse method */
492 for (bound1 = 0; bound1 < conn->request_length &&
493 conn->request[bound1] != ' '; bound1++)
494 ;
495
496 conn->method = split_string(conn->request, 0, bound1);
497 strntoupper(conn->method, bound1);
498
499 /* parse uri */
500 for (; bound1 < conn->request_length &&
501 conn->request[bound1] == ' '; bound1++)
502 ;
503
504 if (bound1 == conn->request_length)
505 return (0); /* fail */
506
507 for (bound2=bound1+1; bound2 < conn->request_length &&
508 conn->request[bound2] != ' ' &&
509 conn->request[bound2] != '\r'; bound2++)
510 ;
511
512 /* find query string */
513 for (mid=bound1; mid<bound2 && conn->request[mid] != '?'; mid++)
514 ;
515
516 if (conn->request[mid] == '?') {
517 conn->query = split_string(conn->request, mid+1, bound2);
518 bound2 = mid;
519 }
520
521 conn->uri = split_string(conn->request, bound1, bound2);
522
523 /* parse important fields */
524 accept_enc = parse_field(conn, "Accept-Encoding: ");
525 if (accept_enc != NULL) {
526 if (strstr(accept_enc, "gzip") != NULL)
527 conn->accept_gzip = 1;
528 free(accept_enc);
529 }
530 return (1);
531 }
532
533 /* FIXME: maybe we need a smarter way of doing static pages: */
534
535 /* ---------------------------------------------------------------------------
536 * Web interface: static stylesheet.
537 */
538 static void
539 static_style_css(struct connection *conn)
540 {
541 #include "stylecss.h"
542
543 conn->reply = style_css;
544 conn->reply_length = style_css_len;
545 conn->reply_dont_free = 1;
546 conn->mime_type = mime_type_css;
547 }
548
549 /* ---------------------------------------------------------------------------
550 * Web interface: static JavaScript.
551 */
552 static void
553 static_graph_js(struct connection *conn)
554 {
555 #include "graphjs.h"
556
557 conn->reply = graph_js;
558 conn->reply_length = graph_js_len;
559 conn->reply_dont_free = 1;
560 conn->mime_type = mime_type_js;
561 }
562
563 /* ---------------------------------------------------------------------------
564 * gzip a reply, if requested and possible. Don't bother with a minimum
565 * length requirement, I've never seen a page fail to compress.
566 */
567 static void
568 process_gzip(struct connection *conn)
569 {
570 char *buf;
571 size_t len;
572 z_stream zs;
573
574 if (!conn->accept_gzip)
575 return;
576
577 buf = xmalloc(conn->reply_length);
578 len = conn->reply_length;
579
580 zs.zalloc = Z_NULL;
581 zs.zfree = Z_NULL;
582 zs.opaque = Z_NULL;
583
584 if (deflateInit2(&zs,
585 Z_BEST_COMPRESSION,
586 Z_DEFLATED,
587 15+16, /* 15 = biggest window,
588 16 = add gzip header+trailer */
589 8 /* default */,
590 Z_DEFAULT_STRATEGY) != Z_OK) {
591 free(buf);
592 return;
593 }
594
595 zs.avail_in = conn->reply_length;
596 zs.next_in = (unsigned char *)conn->reply;
597
598 zs.avail_out = conn->reply_length;
599 zs.next_out = (unsigned char *)buf;
600
601 if (deflate(&zs, Z_FINISH) != Z_STREAM_END) {
602 deflateEnd(&zs);
603 free(buf);
604 verbosef("failed to compress %zu bytes", len);
605 return;
606 }
607
608 if (conn->reply_dont_free)
609 conn->reply_dont_free = 0;
610 else
611 free(conn->reply);
612 conn->reply = buf;
613 conn->reply_length -= zs.avail_out;
614 conn->encoding = encoding_gzip;
615 deflateEnd(&zs);
616 }
617
618 /* ---------------------------------------------------------------------------
619 * Process a GET/HEAD request
620 */
621 static void process_get(struct connection *conn)
622 {
623 char *decoded_url, *safe_url;
624
625 verbosef("http: %s \"%s\" %s", conn->method, conn->uri,
626 (conn->query == NULL)?"":conn->query);
627
628 /* work out path of file being requested */
629 decoded_url = urldecode(conn->uri);
630
631 /* make sure it's safe */
632 safe_url = make_safe_uri(decoded_url);
633 free(decoded_url);
634 if (safe_url == NULL)
635 {
636 default_reply(conn, 400, "Bad Request",
637 "You requested an invalid URI: %s", conn->uri);
638 return;
639 }
640
641 /* make relative (or fail) */
642 decoded_url = safe_url;
643 if (!str_starts_with(decoded_url, http_base_url))
644 {
645 default_reply(conn, 404, "Not Found",
646 "The page you requested could not be found.");
647 free(decoded_url);
648 return;
649 }
650 safe_url = decoded_url + strlen(http_base_url) - 1;
651
652 if (strcmp(safe_url, "/") == 0) {
653 struct str *buf = html_front_page();
654 str_extract(buf, &(conn->reply_length), &(conn->reply));
655 conn->mime_type = mime_type_html;
656 }
657 else if (str_starts_with(safe_url, "/hosts/")) {
658 /* FIXME here - make this saner */
659 struct str *buf = html_hosts(safe_url, conn->query);
660 if (buf == NULL) {
661 default_reply(conn, 404, "Not Found",
662 "The page you requested could not be found.");
663 free(decoded_url);
664 return;
665 }
666 str_extract(buf, &(conn->reply_length), &(conn->reply));
667 conn->mime_type = mime_type_html;
668 }
669 else if (str_starts_with(safe_url, "/graphs.xml")) {
670 struct str *buf = xml_graphs();
671 str_extract(buf, &(conn->reply_length), &(conn->reply));
672 conn->mime_type = mime_type_xml;
673 /* hack around Opera caching the XML */
674 conn->header_extra = "Pragma: no-cache\r\n";
675 }
676 else if (strcmp(safe_url, "/style.css") == 0)
677 static_style_css(conn);
678 else if (strcmp(safe_url, "/graph.js") == 0)
679 static_graph_js(conn);
680 else {
681 default_reply(conn, 404, "Not Found",
682 "The page you requested could not be found.");
683 free(decoded_url);
684 return;
685 }
686 free(decoded_url);
687
688 process_gzip(conn);
689 assert(conn->mime_type != NULL);
690 generate_header(conn, 200, "OK");
691 }
692
693
694
695 /* ---------------------------------------------------------------------------
696 * Process a request: build the header and reply, advance state.
697 */
698 static void process_request(struct connection *conn)
699 {
700 if (!parse_request(conn))
701 {
702 default_reply(conn, 400, "Bad Request",
703 "You sent a request that the server couldn't understand.");
704 }
705 else if (strcmp(conn->method, "GET") == 0)
706 {
707 process_get(conn);
708 }
709 else if (strcmp(conn->method, "HEAD") == 0)
710 {
711 process_get(conn);
712 conn->header_only = 1;
713 }
714 else
715 {
716 default_reply(conn, 501, "Not Implemented",
717 "The method you specified (%s) is not implemented.",
718 conn->method);
719 }
720
721 /* advance state */
722 if (conn->header_only)
723 conn->state = SEND_HEADER;
724 else
725 conn->state = SEND_HEADER_AND_REPLY;
726 }
727
728
729
730 /* ---------------------------------------------------------------------------
731 * Receiving request.
732 */
733 static void poll_recv_request(struct connection *conn)
734 {
735 char buf[65536];
736 ssize_t recvd;
737
738 recvd = recv(conn->socket, buf, sizeof(buf), 0);
739 dverbosef("poll_recv_request(%d) got %d bytes", conn->socket, (int)recvd);
740 if (recvd <= 0)
741 {
742 if (recvd == -1)
743 verbosef("recv(%d) error: %s", conn->socket, strerror(errno));
744 conn->state = DONE;
745 return;
746 }
747 conn->last_active_mono = now_mono();
748
749 /* append to conn->request */
750 conn->request = xrealloc(conn->request, conn->request_length+recvd+1);
751 memcpy(conn->request+conn->request_length, buf, (size_t)recvd);
752 conn->request_length += recvd;
753 conn->request[conn->request_length] = 0;
754
755 /* die if it's too long */
756 if (conn->request_length > MAX_REQUEST_LENGTH)
757 {
758 default_reply(conn, 413, "Request Entity Too Large",
759 "Your request was dropped because it was too long.");
760 conn->state = SEND_HEADER;
761 return;
762 }
763
764 /* process request if we have all of it */
765 if (conn->request_length > 4 &&
766 memcmp(conn->request+conn->request_length-4, "\r\n\r\n", 4) == 0)
767 {
768 process_request(conn);
769
770 /* request not needed anymore */
771 free(conn->request);
772 conn->request = NULL; /* important: don't free it again later */
773 }
774 }
775
776
777
778 /* ---------------------------------------------------------------------------
779 * Try to send header and [a part of the] reply in one packet.
780 */
781 static void poll_send_header_and_reply(struct connection *conn)
782 {
783 ssize_t sent;
784 struct iovec iov[2];
785
786 assert(!conn->header_only);
787 assert(conn->reply_length > 0);
788 assert(conn->header_sent == 0);
789
790 assert(conn->reply_sent == 0);
791
792 /* Fill out iovec */
793 iov[0].iov_base = conn->header;
794 iov[0].iov_len = conn->header_length;
795
796 iov[1].iov_base = conn->reply;
797 iov[1].iov_len = conn->reply_length;
798
799 sent = writev(conn->socket, iov, 2);
800 conn->last_active_mono = now_mono();
801
802 /* handle any errors (-1) or closure (0) in send() */
803 if (sent < 1) {
804 if (sent == -1)
805 verbosef("writev(%d) error: %s", conn->socket, strerror(errno));
806 conn->state = DONE;
807 return;
808 }
809
810 /* Figure out what we've sent. */
811 conn->total_sent += (unsigned int)sent;
812 if (sent < (ssize_t)conn->header_length) {
813 verbosef("partially sent header");
814 conn->header_sent = sent;
815 conn->state = SEND_HEADER;
816 return;
817 }
818 /* else */
819 conn->header_sent = conn->header_length;
820 sent -= conn->header_length;
821
822 if (sent < (ssize_t)conn->reply_length) {
823 verbosef("partially sent reply");
824 conn->reply_sent += sent;
825 conn->state = SEND_REPLY;
826 return;
827 }
828 /* else */
829 conn->reply_sent = conn->reply_length;
830 conn->state = DONE;
831 }
832
833 /* ---------------------------------------------------------------------------
834 * Sending header. Assumes conn->header is not NULL.
835 */
836 static void poll_send_header(struct connection *conn)
837 {
838 ssize_t sent;
839
840 sent = send(conn->socket, conn->header + conn->header_sent,
841 conn->header_length - conn->header_sent, 0);
842 conn->last_active_mono = now_mono();
843 dverbosef("poll_send_header(%d) sent %d bytes", conn->socket, (int)sent);
844
845 /* handle any errors (-1) or closure (0) in send() */
846 if (sent < 1)
847 {
848 if (sent == -1)
849 verbosef("send(%d) error: %s", conn->socket, strerror(errno));
850 conn->state = DONE;
851 return;
852 }
853 conn->header_sent += (unsigned int)sent;
854 conn->total_sent += (unsigned int)sent;
855
856 /* check if we're done sending */
857 if (conn->header_sent == conn->header_length)
858 {
859 if (conn->header_only)
860 conn->state = DONE;
861 else
862 conn->state = SEND_REPLY;
863 }
864 }
865
866
867
868 /* ---------------------------------------------------------------------------
869 * Sending reply.
870 */
871 static void poll_send_reply(struct connection *conn)
872 {
873 ssize_t sent;
874
875 sent = send(conn->socket,
876 conn->reply + conn->reply_sent,
877 conn->reply_length - conn->reply_sent, 0);
878 conn->last_active_mono = now_mono();
879 dverbosef("poll_send_reply(%d) sent %d: [%d-%d] of %d",
880 conn->socket, (int)sent,
881 (int)conn->reply_sent,
882 (int)(conn->reply_sent + sent - 1),
883 (int)conn->reply_length);
884
885 /* handle any errors (-1) or closure (0) in send() */
886 if (sent < 1)
887 {
888 if (sent == -1)
889 verbosef("send(%d) error: %s", conn->socket, strerror(errno));
890 else if (sent == 0)
891 verbosef("send(%d) closure", conn->socket);
892 conn->state = DONE;
893 return;
894 }
895 conn->reply_sent += (unsigned int)sent;
896 conn->total_sent += (unsigned int)sent;
897
898 /* check if we're done sending */
899 if (conn->reply_sent == conn->reply_length) conn->state = DONE;
900 }
901
902
903
904 /* --------------------------------------------------------------------------
905 * Initialize the base url.
906 */
907 void http_init_base(const char *url) {
908 char *slashed_url, *safe_url;
909 size_t urllen;
910
911 if (url == NULL) {
912 http_base_url = strdup("/");
913 return;
914 }
915
916 /* Make sure that the url has leading and trailing slashes. */
917 urllen = strlen(url);
918 slashed_url = xmalloc(urllen+3);
919 slashed_url[0] = '/';
920 memcpy(slashed_url+1, url, urllen); /* don't copy NUL */
921 slashed_url[urllen+1] = '/';
922 slashed_url[urllen+2] = '\0';
923
924 /* Clean the url. */
925 safe_url = make_safe_uri(slashed_url);
926 free(slashed_url);
927 if (safe_url == NULL) {
928 verbosef("invalid base \"%s\", ignored", url);
929 http_base_url = strdup("/"); /* set to default */
930 return;
931 } else {
932 http_base_url = safe_url;
933 }
934 }
935
936 /* Use getaddrinfo to figure out what type of socket to create and
937 * what to bind it to. "bindaddr" can be NULL. Remember to freeaddrinfo()
938 * the result.
939 */
940 static struct addrinfo *get_bind_addr(
941 const char *bindaddr, const unsigned short bindport)
942 {
943 struct addrinfo hints, *ai;
944 char portstr[6];
945 int ret;
946
947 memset(&hints, 0, sizeof(hints));
948 hints.ai_family = AF_UNSPEC;
949 hints.ai_socktype = SOCK_STREAM;
950 hints.ai_flags = AI_PASSIVE;
951
952 snprintf(portstr, sizeof(portstr), "%u", bindport);
953 if ((ret = getaddrinfo(bindaddr, portstr, &hints, &ai)))
954 err(1, "getaddrinfo(%s, %s) failed: %s",
955 bindaddr ? bindaddr : "NULL", portstr, gai_strerror(ret));
956 if (ai == NULL)
957 err(1, "getaddrinfo() returned NULL pointer");
958 return ai;
959 }
960
961 void http_add_bindaddr(const char *bindaddr)
962 {
963 struct bindaddr_entry *ent;
964
965 ent = xmalloc(sizeof(*ent));
966 ent->s = bindaddr;
967 STAILQ_INSERT_TAIL(&bindaddrs, ent, entries);
968 }
969
970 static void http_listen_one(struct addrinfo *ai,
971 const unsigned short bindport)
972 {
973 char ipaddr[INET6_ADDRSTRLEN];
974 int sockin, sockopt, ret;
975
976 /* format address into ipaddr string */
977 if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, ipaddr,
978 sizeof(ipaddr), NULL, 0, NI_NUMERICHOST)) != 0)
979 err(1, "getnameinfo failed: %s", gai_strerror(ret));
980
981 /* create incoming socket */
982 if ((sockin = socket(ai->ai_family, ai->ai_socktype,
983 ai->ai_protocol)) == -1) {
984 warn("http_listen_one(%s, %u): socket(%d (%s), %d, %d) failed",
985 ipaddr, (unsigned int)bindport,
986 ai->ai_family,
987 (ai->ai_family == AF_INET6) ? "AF_INET6" :
988 (ai->ai_family == AF_INET) ? "AF_INET" :
989 "?",
990 ai->ai_socktype, ai->ai_protocol);
991 return;
992 }
993
994 fd_set_nonblock(sockin);
995
996 /* reuse address */
997 sockopt = 1;
998 if (setsockopt(sockin, SOL_SOCKET, SO_REUSEADDR,
999 &sockopt, sizeof(sockopt)) == -1)
1000 err(1, "can't set SO_REUSEADDR");
1001
1002 #ifdef IPV6_V6ONLY
1003 /* explicitly disallow IPv4 mapped addresses since OpenBSD doesn't allow
1004 * dual stack sockets under any circumstances
1005 */
1006 if (ai->ai_family == AF_INET6) {
1007 sockopt = 1;
1008 if (setsockopt(sockin, IPPROTO_IPV6, IPV6_V6ONLY,
1009 &sockopt, sizeof(sockopt)) == -1)
1010 err(1, "can't set IPV6_V6ONLY");
1011 }
1012 #endif
1013
1014 /* bind socket */
1015 if (bind(sockin, ai->ai_addr, ai->ai_addrlen) == -1) {
1016 warn("bind(\"%s\") failed", ipaddr);
1017 close(sockin);
1018 return;
1019 }
1020
1021 /* listen on socket */
1022 if (listen(sockin, 128) == -1)
1023 err(1, "listen() failed");
1024
1025 verbosef("listening on http://%s%s%s:%u%s",
1026 (ai->ai_family == AF_INET6) ? "[" : "",
1027 ipaddr,
1028 (ai->ai_family == AF_INET6) ? "]" : "",
1029 bindport,
1030 http_base_url);
1031
1032 /* add to insocks */
1033 insocks = xrealloc(insocks, sizeof(*insocks) * (insock_num + 1));
1034 insocks[insock_num++] = sockin;
1035 }
1036
1037 /* Initialize the http sockets and listen on them. */
1038 void http_listen(const unsigned short bindport)
1039 {
1040 /* If the user didn't specify any bind addresses, add a NULL.
1041 * This will become a wildcard.
1042 */
1043 if (STAILQ_EMPTY(&bindaddrs))
1044 http_add_bindaddr(NULL);
1045
1046 /* Listen on every specified interface. */
1047 while (!STAILQ_EMPTY(&bindaddrs)) {
1048 struct bindaddr_entry *bindaddr = STAILQ_FIRST(&bindaddrs);
1049 struct addrinfo *ai, *ais = get_bind_addr(bindaddr->s, bindport);
1050
1051 /* There could be multiple addresses returned, handle them all. */
1052 for (ai = ais; ai; ai = ai->ai_next)
1053 http_listen_one(ai, bindport);
1054
1055 freeaddrinfo(ais);
1056
1057 STAILQ_REMOVE_HEAD(&bindaddrs, entries);
1058 free(bindaddr);
1059 }
1060
1061 if (insocks == NULL)
1062 errx(1, "was not able to bind any ports for http interface");
1063
1064 /* ignore SIGPIPE */
1065 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
1066 err(1, "can't ignore SIGPIPE");
1067 }
1068
1069
1070
1071 /* ---------------------------------------------------------------------------
1072 * Set recv/send fd_sets and calculate timeout length.
1073 */
1074 void
1075 http_fd_set(fd_set *recv_set, fd_set *send_set, int *max_fd,
1076 struct timeval *timeout, int *need_timeout)
1077 {
1078 struct connection *conn, *next;
1079 int minidle = idletime + 1;
1080 unsigned int i;
1081
1082 #define MAX_FD_SET(sock, fdset) do { \
1083 FD_SET(sock, fdset); *max_fd = MAX(*max_fd, sock); } while(0)
1084
1085 for (i=0; i<insock_num; i++)
1086 MAX_FD_SET(insocks[i], recv_set);
1087
1088 LIST_FOREACH_SAFE(conn, &connlist, entries, next)
1089 {
1090 int idlefor = now_mono() - conn->last_active_mono;
1091
1092 /* Time out dead connections. */
1093 if (idlefor >= idletime) {
1094 char ipaddr[INET6_ADDRSTRLEN];
1095 /* FIXME: this is too late on FreeBSD, socket is invalid */
1096 int ret = getnameinfo((struct sockaddr *)&conn->client,
1097 sizeof(conn->client), ipaddr, sizeof(ipaddr),
1098 NULL, 0, NI_NUMERICHOST);
1099 if (ret == 0)
1100 verbosef("http socket timeout from %s (fd %d)",
1101 ipaddr, conn->socket);
1102 else
1103 warn("http socket timeout: getnameinfo error: %s",
1104 gai_strerror(ret));
1105 conn->state = DONE;
1106 }
1107
1108 /* Connections that need a timeout. */
1109 if (conn->state != DONE)
1110 minidle = MIN(minidle, (idletime - idlefor));
1111
1112 switch (conn->state)
1113 {
1114 case DONE:
1115 /* clean out stale connection */
1116 LIST_REMOVE(conn, entries);
1117 free_connection(conn);
1118 free(conn);
1119 break;
1120
1121 case RECV_REQUEST:
1122 MAX_FD_SET(conn->socket, recv_set);
1123 break;
1124
1125 case SEND_HEADER_AND_REPLY:
1126 case SEND_HEADER:
1127 case SEND_REPLY:
1128 MAX_FD_SET(conn->socket, send_set);
1129 break;
1130
1131 default: errx(1, "invalid state");
1132 }
1133 }
1134 #undef MAX_FD_SET
1135
1136 /* Only set timeout if cap hasn't already. */
1137 if ((*need_timeout == 0) && (minidle <= idletime)) {
1138 *need_timeout = 1;
1139 timeout->tv_sec = minidle;
1140 timeout->tv_usec = 0;
1141 }
1142 }
1143
1144
1145
1146 /* ---------------------------------------------------------------------------
1147 * poll connections that select() says need attention
1148 */
1149 void http_poll(fd_set *recv_set, fd_set *send_set)
1150 {
1151 struct connection *conn;
1152 unsigned int i;
1153
1154 for (i=0; i<insock_num; i++)
1155 if (FD_ISSET(insocks[i], recv_set))
1156 accept_connection(insocks[i]);
1157
1158 LIST_FOREACH(conn, &connlist, entries)
1159 switch (conn->state)
1160 {
1161 case RECV_REQUEST:
1162 if (FD_ISSET(conn->socket, recv_set)) poll_recv_request(conn);
1163 break;
1164
1165 case SEND_HEADER_AND_REPLY:
1166 if (FD_ISSET(conn->socket, send_set)) poll_send_header_and_reply(conn);
1167 break;
1168
1169 case SEND_HEADER:
1170 if (FD_ISSET(conn->socket, send_set)) poll_send_header(conn);
1171 break;
1172
1173 case SEND_REPLY:
1174 if (FD_ISSET(conn->socket, send_set)) poll_send_reply(conn);
1175 break;
1176
1177 case DONE: /* fallthrough */
1178 default: errx(1, "invalid state");
1179 }
1180 }
1181
1182 void http_stop(void) {
1183 struct connection *conn;
1184 unsigned int i;
1185
1186 free(http_base_url);
1187
1188 /* Close listening sockets. */
1189 for (i=0; i<insock_num; i++)
1190 close(insocks[i]);
1191 free(insocks);
1192 insocks = NULL;
1193
1194 /* Close in-flight connections. */
1195 LIST_FOREACH(conn, &connlist, entries) {
1196 LIST_REMOVE(conn, entries);
1197 free_connection(conn);
1198 free(conn);
1199 }
1200 }
1201
1202 /* vim:set ts=4 sw=4 et tw=78: */