#include "darkstat.h"
#include "http.h"
+#include "config.h"
#include "conv.h"
#include "hosts_db.h"
#include "graph_db.h"
#include <unistd.h>
#include <zlib.h>
-char *base_url = NULL;
-
static const char mime_type_xml[] = "text/xml";
static const char mime_type_html[] = "text/html; charset=us-ascii";
static const char mime_type_css[] = "text/css";
static const char server[] = PACKAGE_NAME "/" PACKAGE_VERSION;
static int idletime = 60;
-static int sockin = -1; /* socket to accept connections from */
#define MAX_REQUEST_LENGTH 4000
+static int *insocks = NULL;
+static unsigned int insock_num = 0;
+
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
static LIST_HEAD(conn_list_head, connection) connlist =
LIST_HEAD_INITIALIZER(conn_list_head);
+struct bindaddr_entry {
+ STAILQ_ENTRY(bindaddr_entry) entries;
+ const char *s;
+};
+static STAILQ_HEAD(bindaddrs_head, bindaddr_entry) bindaddrs =
+ STAILQ_HEAD_INITIALIZER(bindaddrs);
+
/* ---------------------------------------------------------------------------
* Decode URL by converting %XX (where XX are hexadecimal digits) to the
* character it represents. Don't forget to free the return value.
/* ---------------------------------------------------------------------------
* Accept a connection from sockin and add it to the connection queue.
*/
-static void accept_connection(void)
+static void accept_connection(const int sockin)
{
struct sockaddr_storage addrin;
socklen_t sin_size;
return;
}
- /* make relative (or fail) */
- decoded_url = safe_url;
- if (!str_starts_with(decoded_url, base_url))
- {
- default_reply(conn, 404, "Not Found",
- "The page you requested could not be found.");
- free(decoded_url);
- return;
- }
- safe_url = decoded_url + strlen(base_url) - 1;
-
if (strcmp(safe_url, "/") == 0) {
struct str *buf = html_front_page();
str_extract(buf, &(conn->reply_length), &(conn->reply));
if (buf == NULL) {
default_reply(conn, 404, "Not Found",
"The page you requested could not be found.");
- free(decoded_url);
+ free(safe_url);
return;
}
str_extract(buf, &(conn->reply_length), &(conn->reply));
else {
default_reply(conn, 404, "Not Found",
"The page you requested could not be found.");
- free(decoded_url);
+ free(safe_url);
return;
}
- free(decoded_url);
+ free(safe_url);
process_gzip(conn);
assert(conn->mime_type != NULL);
iov[0].iov_base = conn->header;
iov[0].iov_len = conn->header_length;
- iov[1].iov_base = conn->reply + conn->reply_sent;
- iov[1].iov_len = conn->reply_length - conn->reply_sent;
+ iov[1].iov_base = conn->reply;
+ iov[1].iov_len = conn->reply_length;
sent = writev(conn->socket, iov, 2);
conn->last_active = now;
conn->header_sent = conn->header_length;
sent -= conn->header_length;
- if (conn->reply_sent + sent < conn->reply_length) {
+ if (sent < (ssize_t)conn->reply_length) {
verbosef("partially sent reply");
conn->reply_sent += sent;
conn->state = SEND_REPLY;
if (conn->reply_sent == conn->reply_length) conn->state = DONE;
}
-
-
-/* --------------------------------------------------------------------------
- * Initialize the base path.
+/* Use getaddrinfo to figure out what type of socket to create and
+ * what to bind it to. "bindaddr" can be NULL. Remember to freeaddrinfo()
+ * the result.
*/
-static void http_init_base(const char *url)
+static struct addrinfo *get_bind_addr(
+ const char *bindaddr, const unsigned short bindport)
{
- char *slashed_url, *safe_url;
- size_t urllen;
-
- if (url == NULL) {
- base_url = strdup("/");
- return;
- }
-
- /* make sure that the url has leading and trailing slashes */
- urllen = strlen(url);
- slashed_url = xmalloc(urllen+3);
- memset(slashed_url, '/', urllen+2);
- memcpy(slashed_url+1, url, urllen); /* don't copy NULL */
- slashed_url[urllen+2] = '\0';
-
- /* clean the url */
- safe_url = make_safe_uri(slashed_url);
- free(slashed_url);
- if (safe_url == NULL) {
- verbosef("invalid base \"%s\", ignored", url);
- base_url = strdup("/"); /* set to default */
- return;
- }
- else
- base_url = safe_url;
-}
-
-/* --------------------------------------------------------------------------
- * Initialize the sockin global. This is the socket that we accept
- * connections from. Pass -1 as max_conn for system limit.
- */
-void http_init(const char *base, const char *bindaddr,
- const unsigned short bindport, const int max_conn)
-{
- struct sockaddr_storage addrin;
- struct addrinfo hints, *ai, *aiptr;
- char ipaddr[INET6_ADDRSTRLEN], portstr[12];
- int sockopt, ret;
-
- http_init_base(base);
+ struct addrinfo hints, *ai;
+ char portstr[6];
+ int ret;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
+#ifdef linux
+ if (bindaddr == NULL)
+ hints.ai_family = AF_INET6; /* dual stack socket */
+#endif
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
#ifdef AI_ADDRCONFIG
hints.ai_flags |= AI_ADDRCONFIG;
#endif
snprintf(portstr, sizeof(portstr), "%u", bindport);
+ if ((ret = getaddrinfo(bindaddr, portstr, &hints, &ai)))
+ err(1, "getaddrinfo(%s,%s) failed: %s",
+ bindaddr ? bindaddr : "NULL", portstr, gai_strerror(ret));
+ if (ai == NULL)
+ err(1, "getaddrinfo() returned NULL pointer");
+ return ai;
+}
- if ((ret = getaddrinfo(bindaddr, portstr, &hints, &aiptr)))
- err(1, "getaddrinfo(): %s", gai_strerror(ret));
-
- for (ai = aiptr; ai; ai = ai->ai_next) {
- /* create incoming socket */
- sockin = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if (sockin == -1)
- continue;
-
- /* reuse address */
- sockopt = 1;
- if (setsockopt(sockin, SOL_SOCKET, SO_REUSEADDR,
- &sockopt, sizeof(sockopt)) == -1) {
- close(sockin);
- continue;
- }
+void http_add_bindaddr(const char *bindaddr)
+{
+ struct bindaddr_entry *ent;
- /* Recover address and port strings. */
- getnameinfo(ai->ai_addr, ai->ai_addrlen, ipaddr, sizeof(ipaddr),
- NULL, 0, NI_NUMERICHOST);
+ ent = xmalloc(sizeof(*ent));
+ ent->s = bindaddr;
+ STAILQ_INSERT_TAIL(&bindaddrs, ent, entries);
+}
- /* bind socket */
- memcpy(&addrin, ai->ai_addr, ai->ai_addrlen);
- if (bind(sockin, (struct sockaddr *)&addrin, ai->ai_addrlen) == -1) {
- close(sockin);
- continue;
- }
+static void http_listen_one(struct addrinfo *ai,
+ const unsigned short bindport)
+{
+ char ipaddr[INET6_ADDRSTRLEN];
+ int sockin, sockopt, ret;
+
+ /* create incoming socket */
+ if ((sockin = socket(ai->ai_family, SOCK_STREAM, 0)) == -1)
+ err(1, "socket() failed");
+
+ /* reuse address */
+ sockopt = 1;
+ if (setsockopt(sockin, SOL_SOCKET, SO_REUSEADDR,
+ &sockopt, sizeof(sockopt)) == -1)
+ err(1, "can't set SO_REUSEADDR");
+
+ /* format address into ipaddr string */
+ if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, ipaddr,
+ sizeof(ipaddr), NULL, 0, NI_NUMERICHOST)) != 0)
+ err(1, "getnameinfo failed: %s", gai_strerror(ret));
+
+ /* bind socket */
+ if (bind(sockin, ai->ai_addr, ai->ai_addrlen) == -1)
+ err(1, "bind(\"%s\") failed", ipaddr);
+
+ /* listen on socket */
+ if (listen(sockin, -1) == -1)
+ err(1, "listen() failed");
+
+ verbosef("listening on http://%s%s%s:%u/",
+ (ai->ai_family == AF_INET6) ? "[" : "",
+ ipaddr,
+ (ai->ai_family == AF_INET6) ? "]" : "",
+ bindport);
+
+ /* add to insocks */
+ insocks = xrealloc(insocks, sizeof(*insocks) * (insock_num + 1));
+ insocks[insock_num++] = sockin;
+}
- verbosef("listening on %s:%u", ipaddr, bindport);
+/* Initialize the http sockets and listen on them. */
+void http_listen(const unsigned short bindport)
+{
+ /* If the user didn't specify any bind addresses, add a NULL.
+ * This will become a wildcard.
+ */
+ if (STAILQ_EMPTY(&bindaddrs))
+ http_add_bindaddr(NULL);
- /* listen on socket */
- if (listen(sockin, max_conn) >= 0)
- /* Successfully bound and now listening. */
- break;
+ /* Listen on every specified interface. */
+ while (!STAILQ_EMPTY(&bindaddrs)) {
+ struct bindaddr_entry *bindaddr = STAILQ_FIRST(&bindaddrs);
+ struct addrinfo *ai, *ais = get_bind_addr(bindaddr->s, bindport);
- /* Next candidate. */
- continue;
- }
+ /* There could be multiple addresses returned, handle them all. */
+ for (ai = ais; ai; ai = ai->ai_next)
+ http_listen_one(ai, bindport);
- freeaddrinfo(aiptr);
+ freeaddrinfo(ais);
- if (ai == NULL)
- err(1, "getaddrinfo() unable to locate address");
+ STAILQ_REMOVE_HEAD(&bindaddrs, entries);
+ free(bindaddr);
+ }
/* ignore SIGPIPE */
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
{
struct connection *conn, *next;
int minidle = idletime + 1;
+ unsigned int i;
#define MAX_FD_SET(sock, fdset) do { \
FD_SET(sock, fdset); *max_fd = max(*max_fd, sock); } while(0)
- MAX_FD_SET(sockin, recv_set);
+ for (i=0; i<insock_num; i++)
+ MAX_FD_SET(insocks[i], recv_set);
LIST_FOREACH_SAFE(conn, &connlist, entries, next)
{
void http_poll(fd_set *recv_set, fd_set *send_set)
{
struct connection *conn;
+ unsigned int i;
- if (FD_ISSET(sockin, recv_set)) accept_connection();
+ for (i=0; i<insock_num; i++)
+ if (FD_ISSET(insocks[i], recv_set))
+ accept_connection(insocks[i]);
LIST_FOREACH(conn, &connlist, entries)
switch (conn->state)
}
void http_stop(void) {
- free(base_url);
- close(sockin);
+ struct connection *conn;
+ unsigned int i;
+
+ /* Close listening sockets. */
+ for (i=0; i<insock_num; i++)
+ close(insocks[i]);
+ free(insocks);
+ insocks = NULL;
+
+ /* Close in-flight connections. */
+ LIST_FOREACH(conn, &connlist, entries) {
+ LIST_REMOVE(conn, entries);
+ free_connection(conn);
+ free(conn);
+ }
}
/* vim:set ts=4 sw=4 et tw=78: */