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