corz.org text viewer..
[currently viewing: /public/mac/soft/stuff/www.c - raw]
/* darkstat: a network traffic analyzer
 * (c) 2001-2003, Emil Mikulic.
 */


/* darkstat's own mini-httpd! 
    colours by cor ;o)    */


#include "darkstat.h"
#include "www.h"
#include "dns.h"
#include "host_db.h"
#include "port_db.h"
#include "proto.h"
#include "graph.h"
#include "gif.h"
#include "content.h"
#include "acct.h"

#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>



#define POLLDELAY 2 /* seconds */
#define SCOREBOARD 10

static int refresh; /* saves me passing it around all the time */

static char cellbg_1[] = "\"#99CCFF\"";
static char cellbg_2[] = "\"#6699FF\"";



static void html_graph(content *cnt, const char *title,
        const int GRAPHWIDTH, const int GRAPHHEIGHT,
        const int64 graph_in[], const int64 graph_out[],
        const int pos, const int GRAPH_SIZE)
{

#define _IN "\"#3333CC\""
#define _I_R 0x33
#define _I_G 0x33
#define _I_B 0xCC

#define _OUT "\"#6699FF\""
#define _O_R 0x66
#define _O_G 0x99
#define _O_B 0xFF

/* SAFE_GRAPHS causes the graph data to be copied to a temporary local
 * array first.  The downside is a small performance hit, the upside is
 * no more botched graphs.
 */

#define SAFE_GRAPHS

    int i, total_width = 0, total_strips = 0, skip,
        incr = (graph_in == graph_day_in);
        /* cheat: mday is g_days+1 */
    double divisor;
    int64 max_transfer;
#ifndef HAVE_64PRINT_COMMAS
    char *maximum;
#endif

#ifdef SAFE_GRAPHS
    int64 *local_in, *local_out;

    local_in = (int64*)malloc(sizeof(int64) * GRAPH_SIZE);
    local_out = (int64*)malloc(sizeof(int64) * GRAPH_SIZE);

    if (!local_in || !local_out)
        freakout("Ran out of memory in html_graph()");

    memcpy(local_in, graph_in, sizeof(int64) * GRAPH_SIZE);
    memcpy(local_out, graph_out, sizeof(int64) * GRAPH_SIZE);
    #define graph_in local_in
    #define graph_out local_out
#endif

    SET64(max_transfer, 0,0);

    /* find maximum value for this graph */
    for (i=0; i<GRAPH_SIZE; i++)
    {
        int64 tmp;
    
        tmp = graph_in[i];
        i64add64(tmp, graph_out[i]);

        max_transfer = i64max(max_transfer, tmp);
    }

#ifdef HAVE_64PRINT_COMMAS
    c_appendf(cnt,
        "<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\">"
        "<tr><td bgcolor=\"#003366\">"
        "<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\">"
        "<tr><td align=\"center\">"
        "<font color=\"#FFFFFF\"><b>%s</b> "
        "(<b><font color=" _IN ">%s</font></b> %s "
         "<b><font color=" _OUT ">%s</font></b>)<br>"
        "%s %'Lu %s</td></tr>"
        "<tr><td bgcolor=\"#FFFFFF\">"
        "<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\">",
        title, _("in"), _("and"), _("out"), 
        _("Maximum:"), max_transfer, _("bytes"));
#else
    c_appendf(cnt,
        "<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\">"
        "<tr><td bgcolor=\"#003366\">"
        "<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\">"
        "<tr><td align=\"center\">"
        "<font color=\"#FFFFFF\"><b>%s</b> "
        "(<b><font color=" _IN ">%s</font></b> %s "
         "<b><font color=" _OUT ">%s</font></b>)<br>"
        "%s %s %s</td></tr>"
        "<tr><td bgcolor=\"#FFFFFF\">"
        "<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\">",
        title, _("in"), _("and"), _("out"), 
        _("Maximum:"), maximum = strint64(max_transfer), _("bytes"));

    free(maximum);
#endif

    divisor = db_i64div32(max_transfer, GRAPHHEIGHT);
    if (divisor < 1.0) divisor = 1.0;

    c_appends(cnt, "<tr>");

    i = pos;
    do
    {
        int in, out, blank, width;
        char alt[64];
#ifndef HAVE_64PRINT_COMMAS
        char *in64, *out64;
#endif

        i++;
        if (i >= GRAPH_SIZE) i = 0;

        /* width calculation */
        width = (GRAPHWIDTH - total_width - 2*GRAPH_SIZE) /
            (GRAPH_SIZE - total_strips);
        total_strips++;
        total_width += width;

        /* get data */
        in = i_i64divdb(graph_in[i], divisor);
        out = i_i64divdb(graph_out[i], divisor);
        blank = GRAPHHEIGHT - in - out;

        /* construct alt */
#ifndef HAVE_64PRINT_COMMAS
        snprintf(alt, 64, "%d: %s %s / %s %s",
            i+incr,
            in64 = strint64(graph_in[i]), _("in"),
            out64 = strint64(graph_out[i]), _("out"));
        free(in64);
        free(out64);
#else
        snprintf(alt, 64, "%d: %'Lu %s / %'Lu %s",
            i+incr,
            graph_in[i], _("in"),
            graph_out[i], _("out"));
#endif

        /* write it out */
        c_appends(cnt, "<td valign=\"bottom\">");
        if (blank)
        {
            c_appendf(cnt,
            "<img src=\"i/x\" width=\"%d\" height=\"%d\">",
            width, blank);
        }
        if (in) c_appendf(cnt,
            "%s<img src=\"i/i\" width=\"%d\" height=\"%d\" "
                   "alt=\"%s\" title=\"%s\">",
                (blank)?"<br>":"", width, in, alt, alt);
        if (out) c_appendf(cnt,
            "%s<img src=\"i/o\" width=\"%d\" height=\"%d\" "
                   "alt=\"%s\" title=\"%s\">",
                (in||blank)?"<br>":"", width, out, alt,
                alt);

        c_appends(cnt, "</td>");

    }
    while (i != pos);

    c_appends(cnt, "</tr>");

    /* add an extra table row containing the bar times */
    if (GRAPH_SIZE > 31)
    {
        /* thin bars */
        c_appends(cnt, "<tr align=\"center\">");
        skip = 3;
    }
    else
    {
        /* thick bars */
        c_appends(cnt, "<tr align=\"right\">");
        skip = 2;
    }

    for (i=0; i<GRAPH_SIZE; i+=skip)
    {
        int num = (i+pos+2) % GRAPH_SIZE;

        /* last bar on the day graph? */
        if (GRAPH_SIZE == 31 && i+skip >= GRAPH_SIZE)
            c_appends(cnt, "<td><font size=\"-3\">"
                "&nbsp;</font></td>");
        else
        c_appendf(cnt,
            "<td colspan=\"%d\"><font size=\"-3\">"
             "%d"
            "</font></td>",
            skip, num+incr);
    }

    c_appends(cnt,
        "</tr>");

    c_appends(cnt,
        "</table>"
        "</td></tr></table></td></tr></table>");

#ifdef SAFE_GRAPHS
#undef graph_in
#undef graph_out
    free(local_in);
    free(local_out);
#endif
}



static const char css[] =
    "<style type=\"text/css\">"
    "A{color:#404040;text-decoration:none;}"
    "A:hover{color:#000000;background:#E0E0E0; text-decoration:underline;}"
    "A.black{color:#FFFFFF;text-decoration:underline;}"
    "A.black:hover{color:#FFFFFF;background:#808080;"
                  "text-decoration:underline;}"
    "</style>";

static char *linkbox = NULL;

static void init_linkbox(void)
{
    char buf[1024];
    
    sprintf(buf, 
    "<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">"
    "<tr><td bgcolor=\"#003366\"><font color=\"#FFFFFF\"><b>"
    "&nbsp;" PACKAGE " v" VERSION "&nbsp;"
    "</b></font></td>"
    "<td bgcolor=\"#003366\">"
    "<table border=\"0\" cellspacing=\"1\" cellpadding=\"2\">"
    "<tr><td bgcolor=\"#FFFFFF\">"
    "<a href=\"/\">%s</a> | "
    "<a href=\"hosts-total.html\">%s</a> | "
    "<a href=\"ports-total.html\">%s</a> | "
    "<a href=\"protocols.html\">%s</a> | "
    "<a href=\"graphs.html\">%s</a> | "
    "<a href=\"" HOMEPAGE "\">%s</a>"
    "</td></tr></table>"
    "</td></tr></table>",

    _("main"), _("hosts"), _("ports"), _("protocols"),
    _("graphs"), _("homepage")
    );

    linkbox = strdup(buf);
    if (!linkbox) freakout("WWW: ran out of memory for linkbox");    
}



/* refresh idea due to Danny Brewer - thank you */
static void refresh_header(content *cnt, const char *title)
{
    if (refresh) c_appendf(cnt,
        "<html><head><title>darkstat : %s</title>"
        "<meta http-equiv=\"refresh\" content=\"%d\">"
        "%s</head><body>%s<br>",
        title, refresh, css, linkbox);

    else c_appendf(cnt,
        "<html><head><title>darkstat : %s</title>"
        "%s</head><body>%s<br>",
        title, css, linkbox);
}

static void refresh_footer(content *cnt)
{
    if (refresh == 0)
    {
        c_appends(cnt, "<br><form method=\"GET\">");
        c_appendf(cnt, _("Want this page refreshed every %s seconds?"),
         "<input type=\"text\" name=\"refresh\" value=\"5\" "
         "size=\"4\">");
        c_appendf(cnt, " <input type=\"submit\" value=\"%s\"></form>"
             "</body></html>",
         _("Yes"));
    }
    else
    {
        char buf[70];
        snprintf(buf, 70, "<input type=\"text\" name=\"refresh\" "
            "value=\"%d\" size=\"4\">", refresh);

        c_appends(cnt, "<br><form method=\"GET\">");
        c_appendf(cnt, _("This page is being refreshed "
         "every %s seconds."), buf);
        c_appendf(cnt, "<input type=\"submit\" value=\"%s\"> "
             "<input type=\"submit\" value=\"%s\" name=\"cutitout\">"
         "</form></body></html>",
         _("Change"), _("Stop"));
    }
}



static content *www_frontpage(void)
{
#ifndef HAVE_64PRINT_COMMAS
    char *x_packets, *x_data, *x_hosts;
#endif
    content *cnt = c_new();

    time_t uptime = t_already + (time(NULL) - t_start);
    dword days, hours, minutes;
    
    refresh_header(cnt, _("main"));
    c_appends(cnt,
        "<table border=\"0\" cellspacing=\"0\" cellpadding=\"1\">");

#ifdef HAVE_64PRINT_COMMAS
    c_appendf(cnt,
    "<tr><td align=\"right\"><b>%s</b></td>"
    "<td>%'Lu</td></tr>"
    "<tr><td align=\"right\"><b>%s</b></td>"
    "<td>%'Lu %s</td></tr>"
    "<tr><td align=\"right\"><b>%s</b></td>"
    "<td>%'u</td></tr>",
    _("Packets captured:"), num_packets,
    _("Data off the wire:"), total_data, _("bytes"),
    _("Unique hosts:"), host_db_used() );
#else    
    c_appendf(cnt,

    "<tr><td align=\"right\"><b>%s</b></td>"
    "<td>%s</td></tr>"
    "<tr><td align=\"right\"><b>%s</b></td>"
    "<td>%s %s</td></tr>"
    "<tr><td align=\"right\"><b>%s</b></td>"
    "<td>%s</td></tr>"    ,
    _("Packets captured:"), x_packets = strint64(num_packets),
    _("Data off the wire:"), x_data = strint64(total_data), _("bytes"),
    _("Unique hosts:"), x_hosts = strint32(host_db_used()) );

    free(x_packets);
    free(x_data);
    free(x_hosts);
#endif

    /* uptime */
#define S_MIN 60
#define S_HR (S_MIN*60)
#define S_DAY (S_HR*24)

    days = uptime / S_DAY;
    uptime -= days * S_DAY;

    hours = uptime / S_HR;
    uptime -= hours * S_HR;

    minutes = uptime / S_MIN;
    uptime -= minutes * S_MIN;

#undef S_DAY
#undef S_HR
#undef S_MIN

    c_appendf(cnt, "<tr><td align=\"right\"><b>%s:</b></td><td>",
        _("Counting"));
    if (days)
        c_appendf(cnt, "%u %s ", days, _("days"));
    if (days || hours)
        c_appendf(cnt, "%u %s ", hours, _("hours"));
    if (days || hours || minutes)
        c_appendf(cnt, "%u %s ", minutes, _("minutes"));
    c_appendf(cnt, "%u %s ", uptime, _("seconds"));

    /* since */
    c_appendf(cnt, "<tr><td align=\"right\"><b>%s</b></td>"
     "<td>%s</td></tr></table><br>",
     _("Running since:"), asctime(localtime(&t_start)) );

    pthread_mutex_lock(&graph_mutex);
    graph_calibrate_to_clock();
    html_graph(cnt, _("Last 60 seconds"), 400, 200,
            graph_sec_in, graph_sec_out, g_secs, GRAPH_SECS);
    pthread_mutex_unlock(&graph_mutex);

    c_appendf(cnt,
        "<br>%s ::[ "
        "%s<a href=\"/dns-on.html\">%s</a>%s | "
        "%s<a href=\"/dns-off.html\">%s</a>%s | "
        "%s<a href=\"/dns-cycle.html\">%s</a>%s "
        "]::<br>",

        _("DNS resolution is"),
(want_resolve==ON)?"<b>":"", _("on"), (want_resolve==ON)?"</b>":"",
(want_resolve==OFF)?"<b>":"", _("off"), (want_resolve==OFF)?"</b>":"",
(want_resolve==IN_PROGRESS)?"<b>":"", _("cycling once"),
(want_resolve==IN_PROGRESS)?"</b>":""
        );

    refresh_footer(cnt);

    return cnt;
}



static content *www_graphs(void)
{
    content *cnt = c_new();

    refresh_header(cnt, _("graphs"));
    c_appends(cnt,
        "<table border=\"0\">"
        "<tr><td align=\"center\">");

    pthread_mutex_lock(&graph_mutex);
    graph_calibrate_to_clock();
    html_graph(cnt, _("Last 60 seconds"), 300, 200,
            graph_sec_in, graph_sec_out, g_secs, GRAPH_SECS);
    c_appends(cnt, "</td><td align=\"center\">");
    html_graph(cnt, _("Last 60 minutes"), 300, 200,
            graph_min_in, graph_min_out, g_mins, GRAPH_MINS);
    c_appends(cnt, "</td></tr><tr><td align=\"center\">");
    html_graph(cnt, _("Last 24 hours"), 300, 200,
            graph_hr_in, graph_hr_out, g_hrs, GRAPH_HRS);
    c_appends(cnt, "</td><td align=\"center\">");
    html_graph(cnt, _("Last 30 days"), 300, 200,
            graph_day_in, graph_day_out, g_days, GRAPH_DAYS);
    pthread_mutex_unlock(&graph_mutex);

    c_appends(cnt, "</td></tr></table>");
    refresh_footer(cnt);
    return cnt;
}



static content *www_hosts(const sort_type st, const int limited)
{
    int i;
    content *cnt = c_new();
    host_record **list;
    int listend, used = host_db_used();
    char *cellbg = cellbg_2;

    refresh_header(cnt, _("hosts"));

    pthread_mutex_lock(&db_mutex);
    if (limited)
        c_appendf(cnt,
            _("Hosts (sorted by %s, top %d)"),
            (st==IN)?_("incoming"):(st==OUT)?_("outgoing"):
            (st==TOTAL)?_("total"):(st==MAIN)?_("IP"):"ERROR",
            min(HOST_REPORT_LIMIT, used));
    else
        c_appendf(cnt,
            _("Hosts (sorted by %s)"),
            (st==IN)?_("incoming"):(st==OUT)?_("outgoing"):
            (st==TOTAL)?_("total"):(st==MAIN)?_("IP"):"ERROR");

    list = host_db_sort(st);
    pthread_mutex_unlock(&db_mutex);

    c_appendf(cnt, "<br><br>"
    "<table border=\"0\" cellpadding=\"1\" cellspacing=\"0\">"
    "<tr><td bgcolor=\"#003366\">"
    "<table border=\"0\" cellspacing=\"0\" cellpadding=\"2\"><tr>"
    "<td><font color=\"#FFFFFF\">"
     "<b><a class=\"black\" href=\"hosts-ip.html\">%s</a></b>"
     " (<a class=\"black\" href=\"hosts-ip-full.html\">%s</a>)"
    "</font></td>"
    "<td><font color=\"#FFFFFF\"><b>%s</b></font></td>"
    "<td><font color=\"#FFFFFF\">"
     "<b><a class=\"black\" href=\"hosts-in.html\">%s</a></b>"
     " (<a class=\"black\" href=\"hosts-in-full.html\">%s</a>)"
    "</font></td>"
    "<td><font color=\"#FFFFFF\">"
     "<b><a class=\"black\" href=\"hosts-out.html\">%s</a></b>"
     " (<a class=\"black\" href=\"hosts-out-full.html\">%s</a>)"
    "</font></td>"
    "<td><font color=\"#FFFFFF\">"
     "<b><a class=\"black\" href=\"hosts-total.html\">%s</a></b>"
     " (<a class=\"black\" href=\"hosts-total-full.html\">%s</a>)"
    "</font></td>"
    "</tr>",
        _("IP"), _("full"),
        _("Hostname"),
        _("In"), _("full"),
        _("Out"), _("full"),
        _("Total"), _("full")
    );

    /* you want the full thing or not? */
    if (limited)
        listend = used-HOST_REPORT_LIMIT;
    else
        listend = 0;
    if (listend < 0) listend = 0;

    for (i=used-1; i>=listend; i--)
    {
        host_record *h;
        int64 t64;
#ifndef HAVE_64PRINT_COMMAS
        char *data_in, *data_out, *total;
#endif

        cellbg = (cellbg == cellbg_1) ? cellbg_2 : cellbg_1;

        /* grab the record */    
        h = list[i];

        /* total = in + out */
        t64 = h->data_in;
        i64add64(t64, h->data_out);

#ifdef HAVE_64PRINT_COMMAS
        c_appendf(cnt,
            "<tr>"
            "<td bgcolor=%s>%d.%d.%d.%d</td>"
            "<td bgcolor=%s>%s</td>"
            "<td align=\"right\" bgcolor=%s>%'Lu</td>"
            "<td align=\"right\" bgcolor=%s>%'Lu</td>"
            "<td align=\"right\" bgcolor=%s>%'Lu</td></tr>",
            cellbg, (h->ip_addr >> 24) & 255,
                (h->ip_addr >> 16) & 255,
                (h->ip_addr >> 8) & 255,
                 h->ip_addr & 255,
            cellbg, h->hostname?h->hostname:"&nbsp;",
            cellbg, h->data_in,
            cellbg, h->data_out,
            cellbg, h->data_in + h->data_out);
#else
        c_appendf(cnt,
            "<tr>"
            "<td bgcolor=%s>%d.%d.%d.%d</td>"
            "<td bgcolor=%s>%s</td>"
            "<td align=\"right\" bgcolor=%s>%s</td>"
            "<td align=\"right\" bgcolor=%s>%s</td>"
            "<td align=\"right\" bgcolor=%s>%s</td></tr>",
            cellbg, (h->ip_addr >> 24) & 255,
                (h->ip_addr >> 16) & 255,
                (h->ip_addr >> 8) & 255,
                 h->ip_addr & 255,
            cellbg, h->hostname?h->hostname:"&nbsp;",
            cellbg, data_in = strint64(h->data_in),
            cellbg, data_out = strint64(h->data_out),
            cellbg, total = strint64(t64));

        free(data_in);
        free(data_out);
        free(total);
#endif
    }
    free(list);

    c_appends(cnt,
        "</table>"
        "</td></tr></table>");

    refresh_footer(cnt);
    return cnt;
}



static content *www_ports(const sort_type st, const int limited)
{
    content *cnt = c_new();
    int i;
    port_record **list;
    int listend;
    char *cellbg = cellbg_2;

    refresh_header(cnt, _("ports"));
    pthread_mutex_lock(&db_mutex);
    if (limited)
        c_appendf(cnt,
            _("Ports (TCP, sorted by %s, top %d)"),
            (st==IN)?_("incoming"):(st==OUT)?_("outgoing"):
            (st==TOTAL)?_("total"):(st==MAIN)?_("port number"):
            "ERROR",
            min(PORT_REPORT_LIMIT, port_db.used));
    else
        c_appendf(cnt,
            _("Ports (TCP, sorted by %s)"),
            (st==IN)?_("incoming"):(st==OUT)?_("outgoing"):
            (st==TOTAL)?_("total"):(st==MAIN)?_("port number"):
            "ERROR");
        
    c_appendf(cnt, "<br><br>"
    "<table border=\"0\" cellpadding=\"1\" cellspacing=\"0\">"
    "<tr><td bgcolor=\"#003366\">"
    "<table border=\"0\" cellspacing=\"1\" cellpadding=\"2\">"
    "<tr>"
    "<td colspan=\"2\"><font color=\"#FFFFFF\">"
     "<b><a class=\"black\" href=\"ports-num.html\">%s</a></b>"
     " (<a class=\"black\" href=\"ports-num-full.html\">%s</a>)"
    "</font></td>"
    "<td><font color=\"#FFFFFF\">"
     "<b><a class=\"black\" href=\"ports-in.html\">%s</a></b>"
     " (<a class=\"black\" href=\"ports-in-full.html\">%s</a>)"
    "</font></td>"
    "<td><font color=\"#FFFFFF\">"
     "<b><a class=\"black\" href=\"ports-out.html\">%s</a></b>"
     " (<a class=\"black\" href=\"ports-out-full.html\">%s</a>)"
    "</font></td>"
    "<td><font color=\"#FFFFFF\">"
     "<b><a class=\"black\" href=\"ports-total.html\">%s</a></b>"
     " (<a class=\"black\" href=\"ports-total-full.html\">%s</a>)"
    "</font></td>"
    "</tr>",
        _("Port"), _("full"),
        _("In"), _("full"),
        _("Out"), _("full"),
        _("Total"), _("full")
    );

    list = port_db_sort(st);

    /* you want the full thing or not? */
    if (limited)
        listend = port_db.used-PORT_REPORT_LIMIT;
    else
        listend = 0;
    if (listend < 0) listend = 0;

    for (i=port_db.used-1; i>=listend; i--)
    {
        port_record *p;
        int64 t64;
#ifndef HAVE_64PRINT_COMMAS
        char *data_in, *data_out, *total;
#endif
        const char *name;

        cellbg = (cellbg == cellbg_1) ? cellbg_2 : cellbg_1;

        /* grab the record */    
        p = list[i];

        /* total */
        t64 = p->data_in;
        i64add64(t64, p->data_out);

        name = service_name(p->port);
        if (!name) name = "(unknown)";

#ifdef HAVE_64PRINT_COMMAS
        c_appendf(cnt,
            "<tr>"
            "<td bgcolor=%s>%d</td>"
            "<td bgcolor=%s>%s</td>"
            "<td align=\"right\" bgcolor=%s>%'Lu</td>"
            "<td align=\"right\" bgcolor=%s>%'Lu</td>"
            "<td align=\"right\" bgcolor=%s>%'Lu</td></tr>",
            cellbg,p->port,
            cellbg,name,
            cellbg,p->data_in,
            cellbg,p->data_out,
            cellbg,t64);
#else
        c_appendf(cnt,
            "<tr>"
            "<td bgcolor=%s>%d</td>"
            "<td bgcolor=%s>%s</td>"
            "<td align=\"right\" bgcolor=%s>%s</td>"
            "<td align=\"right\" bgcolor=%s>%s</td>"
            "<td align=\"right\" bgcolor=%s>%s</td></tr>",
            cellbg,p->port,
            cellbg,name,
            cellbg,data_in = strint64(p->data_in),
            cellbg,data_out = strint64(p->data_out),
            cellbg,total = strint64(t64));

        free(data_in);
        free(data_out);
        free(total);
#endif
    }
    pthread_mutex_unlock(&db_mutex);
    free(list);

    c_appends(cnt,
        "</table>"
        "</td></tr></table>");

    refresh_footer(cnt);
    return cnt;
}



static content *www_protocols(void)
{
    content *cnt = c_new();
    char *cellbg = cellbg_2;
    int i;
    int64 zero;
    SET64(zero, 0, 0);

    refresh_header(cnt, _("protocols"));
    c_appendf(cnt,
    "<table border=\"0\" cellpadding=\"1\" cellspacing=\"0\">"
    "<tr><td bgcolor=\"#003366\">"
    "<table border=\"0\" cellspacing=\"1\" cellpadding=\"2\"><tr>"
    "<td colspan=\"2\" bgcolor=\"#000000\">"
        "<font color=\"#FFFFFF\"><b>%s</b></font></td>"
    "<td bgcolor=\"#000000\">"
        "<font color=\"#FFFFFF\"><b>%s</b></font></td>"
    "<td bgcolor=\"#000000\">"
        "<font color=\"#FFFFFF\"><b>%s</b></font></td>"
    "<td bgcolor=\"#000000\">"
        "<font color=\"#FFFFFF\"><b>%s</b></font></td>"
    "<td bgcolor=\"#000000\">"
        "<font color=\"#FFFFFF\"><b>%s</b></font></td></tr>",
    _("Protocol"), _("In"), _("Out"), _("Other"), _("Total"));

    for (i=0; i<256; i++)
    if (i64bigger(proto_in[i], zero) || i64bigger(proto_out[i], zero) ||
        i64bigger(proto_other[i], zero))
    {
        int64 t64;
#ifndef HAVE_64PRINT_COMMAS
        char *data_in, *data_out, *data_other, *total;
#endif

        cellbg = (cellbg == cellbg_1) ? cellbg_2 : cellbg_1;

        /* total */
        t64 = proto_in[i];
        i64add64(t64, proto_out[i]);
        i64add64(t64, proto_other[i]);

#ifdef HAVE_64PRINT_COMMAS
    c_appendf(cnt,
        "<tr><td bgcolor=%s align=\"right\">%d</td>"
        "<td bgcolor=%s>%s</td>"
        "<td align=\"right\" bgcolor=%s>%'Lu</td>"
        "<td align=\"right\" bgcolor=%s>%'Lu</td>"
        "<td align=\"right\" bgcolor=%s>%'Lu</td>"
        "<td align=\"right\" bgcolor=%s>%'Lu</td></tr>\n",
        cellbg,i, cellbg,proto_name(i),
        cellbg,proto_in[i],
            cellbg,proto_out[i],
        cellbg,proto_other[i],
        cellbg,t64);
#else
    c_appendf(cnt,
        "<tr><td bgcolor=%s align=\"right\">%d</td>"
        "<td bgcolor=%s>%s</td>"
        "<td align=\"right\" bgcolor=%s>%s</td>"
        "<td align=\"right\" bgcolor=%s>%s</td>"
        "<td align=\"right\" bgcolor=%s>%s</td>"
        "<td align=\"right\" bgcolor=%s>%s</td></tr>\n",
        cellbg,i, cellbg,proto_name(i),
        cellbg,data_in = strint64(proto_in[i]),
        cellbg,data_out = strint64(proto_out[i]),
        cellbg,data_other = strint64(proto_other[i]),
        cellbg,total = strint64(t64));

    free(data_in);
    free(data_out);
    free(data_other);
    free(total);
#endif /* HAVE_64PRINT_COMMAS */
    }

    c_appends(cnt,
        "</table>"
        "</td></tr></table>");

    refresh_footer(cnt);
    return cnt;
}



static inline content *new_gif(void)
{
    content *cnt = c_new();

    assert(GIF_SIZE <= cnt->pool);
    memcpy(cnt->buf, gif, GIF_SIZE);
    cnt->used = GIF_SIZE;
    return cnt;
}



static content *info_menu(void)
{
    content *c = c_new();
    c_appendf(c,
        "<html><head>"
        "<title>%s</title>"
        "</head><body>"
        "<h1>%s</h1>", _("Info"), _("Info")
        );
    c_appendf(c,
        "<a href=\"/info/bytes-total.txt\">%s</a>",
        _("Data off the wire")
        );
    c_appendf(c,
        "</body></html>"
        );
    return c;
}


static content *info_bytes_total(void)
{
#ifndef HAVE_64PRINT_COMMAS
    char *x_data;
#endif
    content *cnt = c_new();

#ifdef HAVE_64PRINT_COMMAS
    c_appendf(cnt, "%'Lu", total_data);
#else    
    c_appendf(cnt, "%s", x_data = strint64(total_data));
    free(x_data);
#endif

    return cnt;
}



static content *www_unimplemented(void)
{
    content *c = c_new();
    c_appendf(c,
        "<html><body>"
        "<h1>%s</h1>%s"
        "</body></html>",

        _("Not Implemented"),
        _("Whatever the heck you just requested, I can't generate.")
        );
    return c;
}



static content *www_notfound(void)
{
    content *c = c_new();
    c_appendf(c,
        "<html><body>"
        "<h1>%s</h1>%s"
        "</body></html>",
        
        _("Not Found"),
        _("Whatever you just requested, I don't have.")
        );

    return c;
}



static content *redirect(const char *url)
{
    content *c = c_new();
    c_appendf(c,
        "<html><head>"
        "<meta http-equiv=\"REFRESH\" content=\"0; URL=%s\">"
        "</head>"
        "<body>Moved <a href=\"%s\">here</a>.</body></html>",
        url, url);
    return c;
}



static SOCKET    sb_socket[SCOREBOARD];
static int    sb_state[SCOREBOARD];
#define STATE_OPEN 0
#define STATE_REQUEST 1
#define STATE_REQCOMPLETE 2
#define STATE_REPLY 3

#define MAX_REQ_SIZE 8192
static char    sb_request[SCOREBOARD][MAX_REQ_SIZE];
static content *sb_reply[SCOREBOARD];
static ssize_t    sb_sent[SCOREBOARD];

static void kill_connection(const int slot)
{
    if (sb_socket[slot] != -1)
    {
        shutdown(sb_socket[slot], SHUT_WR);
        close(sb_socket[slot]);
    }
    sb_socket[slot] = -1;
    sb_state[slot] = STATE_OPEN;
    sb_request[slot][0] = 0;
    if (sb_reply[slot]) c_delete(&(sb_reply[slot]));
    assert(sb_reply[slot] == NULL);
    sb_sent[slot] = 0;
}

static int get_url(const char *request, const char *url)
{
    char d = request[strlen(url)];
    
    if (memcmp(request, url, strlen(url)) != 0) return 0;
    if (d == '\r' || d == '\n' || d == ' ' || d == '?') return 1;
    else return 0;
}



static void http_200_ok(content *hdr, const int is_cachable)
{
    char datebuf[200];
    time_t now = time(NULL);
    struct tm *tm = gmtime(&now);

    strftime(datebuf, 200,
        "%a, %e %b %Y %H:%M:%S GMT", tm);

    c_appendf(hdr,
        "HTTP/1.1 200 OK\r\n"
        "Date: %s\r\n"
        "Last-Modified: %s\r\n",
        datebuf, datebuf);

    if (!is_cachable)
        c_appendf(hdr, "Expires: %s\r\n", datebuf);
    else
    {
        now += 86400;
        tm = gmtime(&now);
        strftime(datebuf, 200,
            "%a, %e %b %Y %H:%M:%S GMT", tm);

        c_appendf(hdr, "Expires: %s\r\n", datebuf);
    }
}



static void handle_connection(const int slot)
{
    content *hdr, *cnt;
    char *request = sb_request[slot], *tmp, *tmp2;
    int pos, recvd, is_gif = 0, is_txt = 0;
    SOCKET incoming = sb_socket[slot];

    assert(incoming != -1);

    /* FIXME: check for connections that time out */

    /* first, check if it's requesting */
    if (sb_state[slot] == STATE_REQUEST)
    {
        pos = strlen(request);

        recvd = recv(incoming, request+pos, MAX_REQ_SIZE-1-pos,
            MSG_NOSIGNAL);
        request[pos+recvd] = 0;

        if (recvd == MAX_REQ_SIZE-1-pos)
        {
            if (verbose)
                printf("WWW(%d): Request %d exceeded "
                    "size of %d.\n",
                    getpid(), slot, MAX_REQ_SIZE);

            kill_connection(slot);
            return;
        }
        if (recvd < 1)
        {
            if (verbose)
                printf("WWW(%d): Request %d broke off.\n",
                    getpid(), slot);

            kill_connection(slot);
            return;
        }
    
        if (
            (strcmp(request+strlen(request)-2, "\n\n") == 0) ||
            (strcmp(request+strlen(request)-2, "\r\r") == 0) ||
            (strcmp(request+strlen(request)-4, "\r\n\r\n") == 0) ||
            (strcmp(request+strlen(request)-4, "\n\r\n\r") == 0) )
        {
            sb_state[slot] = STATE_REQCOMPLETE;
            shutdown(incoming, 0); /* no more recv */

#define CMP(x) (memcmp(request,x,strlen(x)) == 0)
#define GET(x) (get_url(request, "GET " x))

    hdr = c_new();

    /* parse ?refresh= in URL */
    refresh = 0;
    tmp = strstr(request, "Referer: ");
    if (!tmp) tmp = request + MAX_REQ_SIZE; /* cheater */
    tmp2 = strstr(request, "?refresh=");
    if (tmp2 && tmp2 < tmp) refresh = atoi(tmp2+9);
    tmp2 = strstr(request, "cutitout=");
    if (tmp2 && tmp2 < tmp) refresh = 0;

    /* parse URL */
    if (!CMP("GET ")) {
        c_appends(hdr, "HTTP/1.1 501 Not Implemented\r\n");
        cnt = www_unimplemented();
    }
    else if GET("/")
            cnt = www_frontpage();
    else if GET("/hosts-total.html")
            cnt = www_hosts(TOTAL,1);
    else if GET("/hosts-total-full.html")
            cnt = www_hosts(TOTAL,0);
    else if GET("/hosts-ip.html")
            cnt = www_hosts(MAIN,1);
    else if GET("/hosts-ip-full.html")
            cnt = www_hosts(MAIN,0);
    else if GET("/hosts-in.html")
            cnt = www_hosts(IN,1);
    else if GET("/hosts-in-full.html")
            cnt = www_hosts(IN,0);
    else if GET("/hosts-out.html")
            cnt = www_hosts(OUT,1);
    else if GET("/hosts-out-full.html")
            cnt = www_hosts(OUT,0);
    else if GET("/ports-total.html")
            cnt = www_ports(TOTAL,1);
    else if GET("/ports-total-full.html")
            cnt = www_ports(TOTAL,0);
    else if GET("/ports-num.html")
            cnt = www_ports(MAIN,1);
    else if GET("/ports-num-full.html")
            cnt = www_ports(MAIN,0);
    else if GET("/ports-in.html")
            cnt = www_ports(IN,1);
    else if GET("/ports-in-full.html")
            cnt = www_ports(IN,0);
    else if GET("/ports-out.html")
            cnt = www_ports(OUT,1);
    else if GET("/ports-out-full.html")
            cnt = www_ports(OUT,0);
    else if GET("/protocols.html")
            cnt = www_protocols();
    else if GET("/graphs.html")
            cnt = www_graphs();
    else if CMP("GET /i/")
    {
        /* "validator" */
        if (strstr(request,"If-Modified-Since:"))
        {
            sb_reply[slot] = hdr;
            c_appends(sb_reply[slot],
                "HTTP/1.1 304 Not Modified\r\n");
            sb_state[slot] = STATE_REPLY;
            return;
        }

        cnt = new_gif();
        is_gif = 1;

        if GET("/i/i")
        {
            cnt->buf[GIF_RED] = _I_R;
            cnt->buf[GIF_GREEN] = _I_G;
            cnt->buf[GIF_BLUE] = _I_B;
        }
        else if GET("/i/o")
        {
            cnt->buf[GIF_RED] = _O_R;
            cnt->buf[GIF_GREEN] = _O_G;
            cnt->buf[GIF_BLUE] = _O_B;
        }
        else
            cnt->buf[GIF_PIXEL] = GIF_TRANSPARENT;
    }
    else if CMP("GET /dns-")
    {
        c_appends(hdr,
            "HTTP/1.1 302 Found\r\n"
            "Location: /\r\n");

        if GET("/dns-on.html") want_resolve = ON;
        else if GET("/dns-off.html") want_resolve = OFF;
        else if GET("/dns-cycle.html") want_resolve = IN_PROGRESS;

        cnt = redirect("/");
    }
    else if CMP("GET /info")
    {
        if GET("/info") {
            c_appends(hdr,
                "HTTP/1.1 302 Found\r\n"
                "Location: /info/\r\n");
            cnt = redirect("/info/");
        }
        else if GET("/info/") cnt = info_menu();
        else if GET("/info/bytes-total.txt")
        {
            is_txt = 1;
            cnt = info_bytes_total();
        }
    }
    else
    {
        c_appends(hdr, "HTTP/1.1 404 Not Found\r\n");
        cnt = www_notfound();
    }
#undef GET
#undef CMP
            if (!hdr->used) http_200_ok(hdr, is_gif);
            if (is_gif)
            {
                c_appends(hdr,
                 "Cache-Control: public\r\n"
                 "Content-Type: image/gif\r\n");
            }
            else
            {
                c_appends(hdr,
                 "Cache-Control: no-cache, must-revalidate, "
                                "max-age=0\r\n");
                if (is_txt)
                    c_appends(hdr,
                     "Content-Type: text/plain\r\n");
                else
                    c_appends(hdr,
                     "Content-Type: text/html; "
                     "charset=UTF-8\r\n");
            }

            c_appendf(hdr,    "Content-Length: %d\r\n"
                    "Connection: close\r\n"
                    "\r\n",
                    cnt->used);

            /* build final content buffer */
            sb_reply[slot] = hdr;
            sb_sent[slot] = 0;
            c_appendsl(sb_reply[slot], cnt->buf, cnt->used);
            c_delete(&cnt);

            /* we can now start sending it */
            sb_state[slot] = STATE_REPLY;
        }

        /* end of requesting code */
        return;
    }

    /* it's not requesting, it could be replying */
    if (sb_state[slot] == STATE_REPLY)
    {
        ssize_t sent = send(incoming,
            sb_reply[slot]->buf + sb_sent[slot],
            sb_reply[slot]->used - sb_sent[slot],
            MSG_NOSIGNAL);

        if (sent < 1)
        {
            if (verbose)
                printf("WWW(%d): Slot %d died during "
                    "reply sending.\n",
                    getpid(), slot);

            kill_connection(slot);
            return;
        }

        /* is it finished? */
        if ((unsigned)sent == sb_reply[slot]->used - sb_sent[slot])
            kill_connection(slot);
        else
        {
            if (verbose)
                printf("sent %d+%d, used %d\n",
                    sb_sent[slot], sent,
                    sb_reply[slot]->used);
            sb_sent[slot] += sent;
        }
    }
}



static RETSIGTYPE broken_pipe(int signum unused)
{
    if (verbose) printf("WWW: Suppressed SIGPIPE.\n");
}



SOCKET sockin;

void www_poll(void)
{
    int i, max_fd, sb_open = -1;
    struct timeval timeout;
    fd_set r_fd, w_fd;

    max_fd = sockin;
    FD_ZERO(&r_fd);
    FD_ZERO(&w_fd);

    for (i=0; i<SCOREBOARD; i++)
    {
        if (sb_socket[i] != -1)
            max_fd = max(max_fd, sb_socket[i]);
        else
            sb_open = i;
        
        /* poll connected sockets in REQUEST for reading */
        if (sb_state[i] == STATE_REQUEST)
            FD_SET(sb_socket[i], &r_fd);

        /* REPLY for writing */
        if (sb_state[i] == STATE_REPLY)
            FD_SET(sb_socket[i], &w_fd);

    }

    /* if scoreboard has an opening, poll sockin */
    if (sb_open != -1)
        FD_SET(sockin, &r_fd);

    timeout.tv_sec = POLLDELAY;
    timeout.tv_usec = 0;
    if (select(max_fd+1, &r_fd, &w_fd, NULL, &timeout) == -1)
        return; /* nothing worth doing */

    if (FD_ISSET(sockin, &r_fd))
    {
        struct sockaddr_in addrin;
        int sin_size;
        SOCKET incoming;

        assert(sb_open != -1);

        sin_size = sizeof(struct sockaddr);
        incoming = accept(sockin, (struct sockaddr *)&addrin,
            &sin_size);

        /* changed your mind? <-- happens often when the
         * web interface is being hammered with requests
         */

        if (incoming < 0)
        {
            if (verbose)
            printf("WWW: Lost a connection in accept()\n");
        }
        else
        {
            /* details about incoming connection */
            if (verbose)
                printf("WWW: Got a connection from %s:%d\n",
                    inet_ntoa(addrin.sin_addr),
                    ntohs(addrin.sin_port));

            sb_socket[sb_open] = incoming;
            sb_state[sb_open] = STATE_REQUEST;
        }
    }

    for (i=0; i<SCOREBOARD; i++)
    if (sb_socket[i] != -1)
        if (FD_ISSET(sb_socket[i], &r_fd) ||
            FD_ISSET(sb_socket[i], &w_fd))
                handle_connection(i);
}



void www_main(void *ignored unused)
{
    struct sockaddr_in addrin;
    int sockopt = 1, i;

    /* reset scoreboard */
    for (i=0; i<SCOREBOARD; i++)
    {
        sb_socket[i] = -1;
        sb_state[i] = STATE_OPEN;
        sb_request[i][0] = 0;
        sb_reply[i] = NULL;
        sb_sent[i] = 0;
    }

    /* create the incoming socket */
    sockin = socket(AF_INET, SOCK_STREAM, 0);
    if (sockin < 0) freakout("WWW: Problem creating socket");

    /* reuse address */
    if (setsockopt(sockin, SOL_SOCKET, SO_REUSEADDR,
        (char*)&sockopt, sizeof(sockopt)) < 0)
            freakout("WWW: Can't REUSEADDR");

    /* fill out a sockaddr struct */
    addrin.sin_family = AF_INET;
    addrin.sin_port = htons(webport);
    if (webip == -1)
        addrin.sin_addr.s_addr = INADDR_ANY;
    else
        addrin.sin_addr.s_addr = htonl(webip);
    bzero(&(addrin.sin_zero), 8);

    /* bind sockin to the incoming port */
    if (bind(sockin, (struct sockaddr *)&addrin,
        sizeof(struct sockaddr)) < 0)
    {
        printf("WWW: The web port is %d, the IP is %s.\n",
            webport, inet_ntoa(addrin.sin_addr));
        
        if (webport < 1024)
            freakout("WWW: Problem binding socket to port < 1024."
                 "  Are you not root?");
        else
            freakout("WWW: Problem binding socket."
                 "  Is the web port taken?");
    }
    
    /* listen on the socket */
    if (listen(sockin, SCOREBOARD) < 0)
        freakout("WWW: Problem listen()ing to socket");

    printf("WWW: Thread is awake and awaiting connections.\n");
    up_www = 1;

#if ENABLE_NLS
    /* TRANSLATORS: This is the only message written to the terminal screen
                        when darkstat is starting up.  Please keep this in
            English or at least US-ASCII.  Change it to something
            like "You are using the <x> language version.\n" */

    printf(_("WWW: You are using the English language version.\n"));
#else
    printf("WWW: Compiled without NLS\n");
#endif
    init_linkbox();

    if (!MSG_NOSIGNAL)
        /* sometimes send() and recv() throw it */
        signal(SIGPIPE, broken_pipe);

    while (!shutting_down) www_poll();

    close(sockin);
    for (i=0; i<SCOREBOARD; i++)
        kill_connection(i);

    printf("WWW: Thread down.\n");
    up_www = 0;
    pthread_exit(0);
}



#if 0
/* xgettext cues */

/* TRANSLATORS: If you're unsure about the context of any of these messages,
                take a look at the screenshots on:
                http://purl.org/net/darkstat
 */

_("")

/* TRANSLATORS: This means total data captured */
_("Data off the wire:")

/* TRANSLATORS: "Cycling once" means the DNS thread will go through the
                host database once and then stop, instead of cycling forever
                (which is what "on" means) */

_("cycling once")

#endif

Welcome to corz.org!

Since switching hosts (I hope you are alright, Ed! Wherever you are …) quite a few things seems to be wonky.

Juggling two energetic boys (of very different ages) on Coronavirus lockdown, I'm unlikely to have them all fixed any time soon. Mail me! to prioritise!