/* * mod_curb.c - Limit bandwidth usage over a given period of time. * * Author : Steve Kemp * 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("\n", r); ap_rputs(" \n", r); ap_rputs(" mod_curb current stats\n", r ); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs("

mod_curb", r ); ap_rputs("

\n", r); ap_rputs("

\n", r); ap_rprintf(r, " Apache HTTP Server version: %s\n", ap_get_server_version()); ap_rputs("

\n", r);; ap_rputs("

Current Configuration

\n", r); ap_rputs("\n", r); ap_rprintf(r, "\n", dcfg->maxBandwidth); ap_rprintf(r, "\n", transferred); ap_rprintf(r, "\n", dcfg->redirectURL, dcfg->redirectURL); ap_rprintf(r, "\n", dcfg->monitorurl, dcfg->monitorurl); ap_rputs("
Maximum Bandwidth%d bytes
Current Bandwidth%d bytes
Redirect URL%s
Realtime Monitor URL%s
\n", r); ap_rputs("

Sample Configuration

\n", r); ap_rputs( "
\n"
"<IfModule mod_curb.c>\n"
"\n"
"     BandWidthExceeded http://some.server.org/exceeded.php\n"
"     BandWidthLimit 650Mb\n"
"     BandWidthMonitorURL /files/status/\n"
"\n"
"</IfModule>\n"
"
\n", r ); ap_rputs( "
\n", r ); ap_rputs( "\n", r ); ap_rputs( "\n", r); ap_rputs( "\n", r ); ap_rputs( "
mod_curb v" VERSION_STRING " Steve Kemp <skx@tardis.ed.ac.uk>
", r); ap_rputs(" \n", r); ap_rputs("\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 */ };