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!

If something isn't working, I'm probably improving it, try again in a minute. If it's still not working, please mail me!