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