Logo Search packages:      
Sourcecode: darkstat version File versions  Download package

www.c

/* darkstat: a network traffic analyzer
 * (c) 2001-2003, Emil Mikulic.
 */

/* darkstat's own mini-httpd! */

#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[] = "\"#F0F0F0\"";
static char cellbg_2[] = "\"#E0E0E0\"";



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 "\"#F0C060\""
#define _I_R 0xF0
#define _I_G 0xC0
#define _I_B 0x60

#define _OUT "\"#A05040\""
#define _O_R 0xA0
#define _O_G 0x50
#define _O_B 0x40

/* 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=\"#000000\">"
            "<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=\"#000000\">"
            "<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=\"#000000\"><font color=\"#FFFFFF\"><b>"
      "&nbsp;" PACKAGE " v" VERSION "&nbsp;"
      "</b></font></td>"
      "<td bgcolor=\"#000000\">"
      "<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=\"#000000\">"
      "<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=\"#000000\">"
      "<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=\"#000000\">"
      "<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


Generated by  Doxygen 1.6.0   Back to index