
/* 
 * mod_curb.c - Limit bandwidth usage over a given period of time.
 *
 * Author  : Steve Kemp <skx@tardis.ed.ac.uk>
 * Homepage: www.steve.org.uk/Software/mod_curb
 * Version : 1.1
 *
 * History:
 *
 * 1.0 - Initial Working Release
 * 1.1 - Fixed MIME type error.
 *
 */



#include "httpd.h"     
#include "http_config.h"     
#include "http_request.h"     
#include "http_protocol.h"     
#include "http_core.h"     
#include "http_main.h"    
#include "http_log.h"     
#include "scoreboard.h"  


#define VERSION_STRING "1.1"

void mod_curb_printf( const char *fmt, ... )
{
 int   l;
 char *p1;
 FILE *log;

 va_list ap;

 char log_line[4096];

 log = fopen( "/tmp/modcurb.log","a" );

 if ( !log ) 
   {
    return; 
   }

 va_start( ap, fmt );

 l = vsprintf(log_line, fmt, ap);

 p1=log_line;
 while((*p1!=0)&&(*p1!=13)&&(*p1!=10)) p1++;
 *p1=0;

 fprintf( log, "%s\n", log_line );

 fclose( log );

 va_end(ap); 

 return; 
}

module MODULE_VAR_EXPORT mod_curb;


typedef struct
{
  int maxBandwidth;
  char *maxBandwidthString;
  char *redirectURL;
  char *monitorurl;
  char *mailto;
} modcurb_config_rec;




/**
 * Convert a string of the form 'dddMb' or 'ddddGb' to
 * the correct number of bytes.
 */
int stringToBytes( char *sz )
{
  int kb = 0;
  int mb = 0;
  int gb = 0;

  int tmp= 0;

  if ( strstr( sz, "k" ) != NULL )
    {
      kb = 1;
    }
  else if ( strstr( sz, "Mb" ) != NULL ) 
    {
      mb = 1;
    }
  else if ( strstr( sz, "Gb" ) != NULL ) 
    {
      gb = 1;
    }
  else
    {
      return( atoi( sz ) );
    }

  tmp = atoi( sz );
  if ( kb ) { tmp *= 1024; }
  else if ( mb ) { tmp *= 1024 * 1024 ; }
  else if ( gb ) { tmp *= 1024 * 1024 * 1024 ; }

  return( tmp );
}

/**
 * Create a new configuration record - this will store our modules
 * settings, as read from the configuration file.
 */
static void *modcurb_create_dir_config(pool *p, char *dir )
{
  /* Create the new configuration record. */
  modcurb_config_rec *rec = 
    (modcurb_config_rec *)ap_pcalloc(p, sizeof( modcurb_config_rec));

  /* Initialize to defaults */
  rec->redirectURL = NULL;
  rec->mailto      = NULL;
  rec->monitorurl  = NULL;
  rec->maxBandwidthString = NULL;
  rec->maxBandwidth= 0;


  /* Return */
  return( rec );
}


/**
 * Merge our records
 */
static void *modcurb_merge_config(pool *p, void *base, void *new )
{
    modcurb_config_rec *merged = 
	(modcurb_config_rec *)ap_pcalloc(p, sizeof(*merged)); 
    modcurb_config_rec *parent = (modcurb_config_rec *)base; 
    modcurb_config_rec *child  = (modcurb_config_rec *)new; 

 
    merged->redirectURL = child->redirectURL ?  
      child->redirectURL : parent->redirectURL; 

    merged->monitorurl = child->monitorurl ?  
      child->monitorurl : parent->monitorurl; 

    merged->mailto = child->mailto ?  
      child->mailto : parent->mailto; 

    merged->maxBandwidth = child->maxBandwidth ?  
      child->maxBandwidth : parent->maxBandwidth; 

    merged->maxBandwidthString = child->maxBandwidthString ?  
      child->maxBandwidthString : parent->maxBandwidthString; 

    return( merged );
}



static request_rec *last_r(request_rec *first)
{
    request_rec *r;
    /* make sure we use the last request of the internal chain
     * for most infos
     */
    for(r=first; r->next != NULL; r=r->next) { 
        continue;  
    } 

    return r;
}



static int getServedBytes(request_rec *r)
{
    char *loc;
    int i, res;
    int ready = 0;
    int busy = 0;
    unsigned long count = 0;
    unsigned long lres, bytes;
    unsigned long my_lres, my_bytes, conn_bytes;
    unsigned short conn_lres;
    unsigned long bcount = 0;
    unsigned long kbcount = 0;
    long req_time;
    short_score score_record;
    parent_score ps_record;
    clock_t tu, ts, tcu, tcs;
    server_rec *vhost;

    tu = ts = tcu = tcs = 0;

    if (!ap_exists_scoreboard_image()) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
		    "Server status unavailable in inetd mode");
	return HTTP_INTERNAL_SERVER_ERROR;
    }
    r->allowed = (1 << M_GET);
    if (r->method_number != M_GET)
	return DECLINED;

    /*
     * Simple table-driven form data set parser that lets you alter the header
     */

    ap_sync_scoreboard_image();
    for (i = 0; i < HARD_SERVER_LIMIT; ++i) {
	score_record = ap_scoreboard_image->servers[i];
	ps_record = ap_scoreboard_image->parent[i];
	res = score_record.status;
	if (res == SERVER_READY)
	    ready++;
	else if (res != SERVER_DEAD)
	    busy++;

	lres = score_record.access_count;
	bytes = score_record.bytes_served;
	bcount += bytes;
    }


    return( bcount );
}


/*
 * Called to fixup a request.
 *
 * Here we see if the bandwidth has been exceeded - if it has
 * we do the redirect magic.
 */
static int log_fixup(request_rec *first)
{
  request_rec *r = last_r(first); 
  modcurb_config_rec *dcfg = (modcurb_config_rec *) 
    ap_get_module_config(r->per_dir_config, &mod_curb); 


  int transferred = getServedBytes( first );

#if 0
  mod_curb_printf("Read Request: %s\n", first->uri );
#endif
  /* TODO: FIXME */
  if ( ( dcfg->maxBandwidth == 0 ) && ( dcfg->maxBandwidthString != NULL ) )
  {
    dcfg->maxBandwidth = stringToBytes( dcfg->maxBandwidthString );
  }


  /*
   * Test to see if this is the server status page
   */
  if( strcasecmp ( first->uri, dcfg->monitorurl ) == 0 )
  {
    r->content_type = "text/html";

    ap_send_http_header(r);
#ifdef CHARSET_EBCDIC
    /* Server-generated response, converted */
    ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out = 1);
#endif

    /*
     * If we're only supposed to send header information (HEAD request), we're
     * already there.
     */
    if (r->header_only) {
        ap_kill_timeout(r);
        return OK;
    }

    {

      request_rec *last = last_r(r); 
      modcurb_config_rec *dcfg = (modcurb_config_rec *) 
	ap_get_module_config(last->per_dir_config, &mod_curb); 

      /*
       * Now send our actual output.  Since we tagged this as being
       * "text/html", we need to embed any HTML.
       */
      ap_rputs(DOCTYPE_HTML_3_2, r);
      ap_rputs("<HTML>\n", r);
      ap_rputs(" <HEAD>\n", r);
      ap_rputs("  <TITLE>mod_curb current stats\n", r );
      ap_rputs("  </TITLE>\n", r);
      ap_rputs(" </HEAD>\n", r);
      ap_rputs(" <BODY>\n", r);
      ap_rputs("  <H1><SAMP>mod_curb</SAMP>", r );
      ap_rputs("  </H1>\n", r);
      ap_rputs("  <P>\n", r);
      ap_rprintf(r, "  Apache HTTP Server version: %s\n",
		 ap_get_server_version());
      ap_rputs("  </P>\n", r);;
      ap_rputs("<h3>Current Configuration</h3>\n", r);
      ap_rputs("<table>\n", r);
      ap_rprintf(r, "<tr><td>Maximum Bandwidth</td><td align='right'>%d bytes</td></tr>\n",
		dcfg->maxBandwidth);
      ap_rprintf(r, "<tr><td>Current Bandwidth</td><td align='right'>%d bytes</td></tr>\n",
		transferred);
      ap_rprintf(r, "<tr><td>Redirect URL</td><td><a href='%s'>%s</a></td></tr>\n",
		dcfg->redirectURL, dcfg->redirectURL);
      ap_rprintf(r, "<tr><td>Realtime Monitor URL</td><td><a href='%s'>%s</a></td></tr>\n",
		dcfg->monitorurl, dcfg->monitorurl);
      ap_rputs("  </table>\n", r);

      ap_rputs("<h3>Sample Configuration</h3>\n", r);
      ap_rputs( 
"<pre>\n"
"&lt;IfModule mod_curb.c&gt;\n"
"\n"
"     BandWidthExceeded http://some.server.org/exceeded.php\n"
"     BandWidthLimit 650Mb\n"
"     BandWidthMonitorURL /files/status/\n"
"\n"
"&lt;/IfModule&gt;\n"
"</pre>\n",

	       r );

      ap_rputs( "<hr>\n", r );
      ap_rputs( "<table width='100%'><tr>\n", r );
      ap_rputs( "<td align='left'><a href='http://www.steve.org.uk/Software/mod_curb/'>mod_curb</a> v" VERSION_STRING " </td>\n", r);
      ap_rputs( "<td align='right'><a href='mailto:skx@tardis.ed.ac.uk'>Steve Kemp &lt;skx@tardis.ed.ac.uk&gt;</a></td>\n", r );
      ap_rputs( "</tr></table>", r);
      ap_rputs(" </BODY>\n", r);
      ap_rputs("</HTML>\n", r);
    }

    /*
     * We're all done, so cancel the timeout we set.  Since this is probably
     * the end of the request we *could* assume this would be done during
     * post-processing - but it's possible that another handler might be
     * called and inherit our outstanding timer.  Not good; to each its own.
     */
    ap_kill_timeout(r);
    /*
     * We did what we wanted to do, so tell the rest of the server we
     * succeeded.
     */
    return DONE;
  }

  
  /*
   * Now we see if the bandwidth limit has been
   * reached
   */
  if ( transferred <= dcfg->maxBandwidth )
  {
    /* Bandwidth limit not exceeded. */
    return OK;
  }
  else
  {
#if 0
    mod_curb_printf( "Redirected request from: %s to %s [%d %d]",
		     first->uri,
		     dcfg->redirectURL, 
		     transferred, 
		     dcfg->maxBandwidth );
#endif    
    ap_table_setn(r->headers_out, "Location",
		  dcfg->redirectURL);

    
    return HTTP_MOVED_PERMANENTLY;
  }
}


static command_rec modcurb_cmds[] = { 
    { 
	"BandWidthExceeded", 
	ap_set_string_slot, 
	(void *)XtOffsetOf(modcurb_config_rec, redirectURL), 
	RSRC_CONF, 
	TAKE1, 
	"The URL to redirect to when bandwidth usage is exceeded",
    }, 
    { 
	"BandwidthMonitorURL", 
	ap_set_string_slot, 
	(void *)XtOffsetOf(modcurb_config_rec, monitorurl), 
	RSRC_CONF, 
	TAKE1, 
	"The URL used to dump the current transfer rate",
    }, 
    { 
	"BandWidthLimit", 
	ap_set_string_slot, 
	(void *)XtOffsetOf(modcurb_config_rec, maxBandwidthString), 
	RSRC_CONF, 
	TAKE1, 
	"The URL to redirect to when bandwidth usage is exceeded",
    }, 
#if 0
    { 
	"MailOnExceeded", 
	ap_set_string_slot, 
	(void *)XtOffsetOf(modcurb_config_rec, mailto), 
	OR_ALL, 
	TAKE1, 
	"The user to email when bandwidth usage is exceeded",
    }, 
#endif
    { NULL },
};




module MODULE_VAR_EXPORT mod_curb = {
    STANDARD_MODULE_STUFF, 
    NULL,                  /* module initializer                  */
    modcurb_create_dir_config, /* create per-dir    config structures */
    modcurb_merge_config,  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    modcurb_cmds,          /* table of config file commands       */
    NULL,                  /* [#8] MIME-typed-dispatched handlers */
    NULL,                  /* [#1] URI to filename translation    */
    NULL,                  /* [#4] validate user id from request  */
    NULL,                  /* [#5] check if the user is ok _here_ */
    NULL,                  /* [#3] check access by host address   */
    NULL,                  /* [#6] determine MIME type            */
    log_fixup,             /* [#7] pre-run fixups                 */
    NULL,           /* [#9] log a transaction              */
    NULL,                  /* [#2] header parser                  */
    NULL,                  /* child_init                          */
    NULL,                  /* child_exit                          */
    NULL                   /* [#0] post read-request              */
};

