"Fossies" - the Fresh Open Source Software Archive

Member "darkstat-3.0.721/http.c" (12 Jan 2022, 34616 Bytes) of package /linux/privat/darkstat-3.0.721.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    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: */