/* ====================================================================
 * Copyright (c) 1995 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/**
 **  This is mod_throttle. A module to allow intelligent dynamic
 **  throttling of individual users.
 **
 **  (adapted from mod_limit by Sameer Parekh)
 ##
 **  (c) Oct 1997, Mark Lovell 
 **      (in so much as it does not interfere with previous copyrights)
 **
 **  Second revision 1/18/97 -- incloude System V IPC support for those
 **  without the benefits of BSD (or with mmaps that don't agree with
 **  BSD)
 **/

#include <pwd.h>
#include <sys/mman.h>
#include <sys/types.h>
#ifdef LINUX
#include <sys/ipc.h>
#include <sys/shm.h>
#endif

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "ap_compat.h"
#include "mod_throttle.h"

module throttle_module;

/**
 ** How many displayable status leves are there?
 **/
#define stat_levels 6

/**
 ** Global configuration and status information
 **/
typedef struct {
    struct throttle_user *tableptr;
    unsigned startSlack;
    char     *dataFile;
    unsigned alertlevels[stat_levels];
} throttle_state;

/**
 ** Names for all but the base level of alert
 **/
static char* alerttxt[] = { "blue", "green", "amber", "red", "critical" };

/**
 ** Set the value of various throttle related config params.
 **/
static const char *
set_file (cmd_parms *parms, void *dummy, char *arg)
{
    throttle_state *cls = get_module_config (parms->server->module_config,
					     &throttle_module);
    cls->dataFile = arg;

    return NULL;
}

static const char *
set_alert (cmd_parms *parms, void *dummy, char *arg1, char *arg2)
{
    int i;
    throttle_state *cls = get_module_config (parms->server->module_config,
					     &throttle_module);

    for (i = 0; i < (stat_levels - 1); i++) {
	if (strcmp(arg1, alerttxt[i]) == 0) {
	    cls->alertlevels[i + 1] = atoi(arg2);
	    return NULL;
	}
    }

    return pstrcat(parms->pool, "Invalid Throttle Indicator: ", arg1, NULL);
}

static const char *
set_slack (cmd_parms *parms, void *dummy, char *arg)
{
    throttle_state *cls = get_module_config (parms->server->module_config,
					     &throttle_module);

    cls->startSlack = atoi(arg);

    return NULL;
}

/**
 ** Commands we are expected to handle
 **/
command_rec throttle_cmds[] = {
{ "ThrottleConfig",    set_file,  NULL, RSRC_CONF, TAKE1,
        "the filename of the throttle database" },
{ "ThrottleIndicator", set_alert, NULL, RSRC_CONF, TAKE2,
	"Levels for various visual throttle allerts" },
{ "ThrottleSlack",     set_slack, NULL, RSRC_CONF, TAKE1,
	"Startup slack to prevent initial overload" },
{ NULL }
};

/**
 ** alloc the base state structure, initialize it's values
 **/
void *
make_throttle_state (pool *p, server_rec *s)
{
    unsigned deflevels[stat_levels] = 
	{0, 10,40, 70, 82, 92};
    throttle_state *cls = 
	(throttle_state *)palloc (p, sizeof (throttle_state));

    cls->tableptr = NULL;
    cls->dataFile = NULL;
    cls->startSlack = 60*60; /* an hour of slack to handle initial high demand */
    memcpy(cls->alertlevels, deflevels, sizeof(unsigned) * stat_levels);

    return (void *)cls;
}

/**
 ** Second level startup, allocate things set vars.
 **/
void
init_throttle (server_rec *s, pool *p)
{
  throttle_state *cls = get_module_config (s->module_config,
					   &throttle_module);
  FILE *fp;
  int uid, max;
  char name[MAX_STRING_LEN];
  time_t now = time(NULL);

  /*
   * I've only done this under LINUX and BSDI, your OS will 
   * be different
   */
  #ifndef LINUX
    cls->tableptr = 
      (struct throttle_user *) mmap(NULL, NUM_RECORDS *
		       sizeof(struct throttle_user), PROT_READ | PROT_WRITE,
		       MAP_ANON | MAP_SHARED | MAP_ANON, -1, 0);
  #else
    {
      int id = shmget(IPC_PRIVATE, 
                      NUM_RECORDS * sizeof(struct throttle_user),
		      IPC_CREAT | 0600);
      if (id == -1) {
          perror("shmget");
	  fprintf(stderr, "httpd: Could not map memory for throttle list\n");
	  exit(1);
      }

      cls->tableptr = (struct throttle_user *)shmat(id, NULL, 0);
    }
  #endif

  /*
   * Is there a more standard way to handle the failure case?
   */
  if(cls->tableptr == (struct throttle_user *) -1 )
    {
      perror("mmap");
      fprintf(stderr, "httpd: Could not map memory for throttle list\n");
      exit(1);
    }

  if (cls->dataFile == NULL)
    return;

  /* Open up the initialization file */
  cls->dataFile = server_root_relative(p, cls->dataFile);
  fp=fopen(cls->dataFile, "r");
  while(fscanf(fp, "%s %d", name, &max) != EOF)
    {
      struct passwd *p = getpwnam(name);
      if (p == NULL || p->pw_uid > NUM_RECORDS)
	continue;
      strncpy(cls->tableptr[p->pw_uid].account, name, 16);
      cls->tableptr[p->pw_uid].started    = now - cls->startSlack;
      cls->tableptr[p->pw_uid].sleepage   = 0;
      cls->tableptr[p->pw_uid].bytes_sent = 0;
      cls->tableptr[p->pw_uid].bytes_max  = max;
    }
  fclose(fp);
}

/**
 ** Do the map, make the delay
 **/
int
check_throttle(request_rec *r)
{
  unsigned long persec;
  time_t now = time(NULL);

  throttle_state *cls = get_module_config(r->server->module_config,
				          &throttle_module);

  if(r->finfo.st_uid > NUM_RECORDS)
    return OK;

  if(cls->tableptr[r->finfo.st_uid].bytes_max == 0)
    return OK;

  if((now - cls->tableptr[r->finfo.st_uid].started) > 86400) 
    {
      /* prevent roll-over */
      cls->tableptr[r->finfo.st_uid].bytes_sent /= 2;
      cls->tableptr[r->finfo.st_uid].started    += 
	(now - cls->tableptr[r->finfo.st_uid].started)/2;
    }

  /* 
   * I like my world in bytes/sec
   */
  persec = (float)cls->tableptr[r->finfo.st_uid].bytes_sent /
	   (now - cls->tableptr[r->finfo.st_uid].started);

  /* if they are over limit */
  if(cls->tableptr[r->finfo.st_uid].bytes_max < persec)
    {
       /* sleep a second longer than we were sleeping before */
       cls->tableptr[r->finfo.st_uid].sleepage++;
    }
  else
    if (cls->tableptr[r->finfo.st_uid].sleepage > 0)
      cls->tableptr[r->finfo.st_uid].sleepage--;

  /* slow them down if they are over limit */
  if (cls->tableptr[r->finfo.st_uid].sleepage != 0)
    sleep(cls->tableptr[r->finfo.st_uid].sleepage);

  return OK;
}

/**
 ** Add any data transmitted in this transaction to the total
 **/
int
throttle_transaction(request_rec *orig)
{
  /* Add some bits to a given user's data */

  throttle_state *cls = get_module_config (orig->server->module_config,
					   &throttle_module);

  /* Find the actual request */
  for( ; orig->next != NULL; orig = orig->next);

  if(orig->finfo.st_mode == 0)
    {
      return OK;
    }

  if (cls->tableptr[orig->finfo.st_uid].started == 0) 
      cls->tableptr[orig->finfo.st_uid].started = time(NULL) - cls->startSlack;

  if(orig->finfo.st_uid > NUM_RECORDS)
    return OK;
  if(orig->bytes_sent > 0)
    cls->tableptr[orig->finfo.st_uid].bytes_sent += orig->bytes_sent;

  return OK;
}

/**
 ** Display a page of throttiling information
 **/
static int
throttle_handler(request_rec *r)
{
  throttle_state *cls = get_module_config(r->server->module_config,
				       &throttle_module);
  time_t now = time(NULL);
  int index;
  struct {
	char*    tag;
	char*	 etag;
  } static stat_font[stat_levels] = {
	{"", ""},
	{"<FONT COLOR=#00C6CE>", "</FONT>"},
	{"<FONT COLOR=#71EF00>", "</FONT>"},
	{"<FONT COLOR=#EFCA00>", "</FONT>"},
	{"<FONT COLOR=#FF3131>", "</FONT>"},
	{"<BLINK><FONT COLOR=#FF3130>", "</FONT></BLINK>"}
  };

  /* Spew to client info about throttling */

  /* This version ignores the file, later versions */
  /* will use the file to make info more specific */
  r->content_type = "text/html";
  send_http_header(r);

  rprintf(r, "<HTML><HEAD><TITLE>Throttle Status</TITLE>"
	     "<META HTTP-EQUIV=refresh CONTENT=60>"
	     "</HEAD>"
	     "<BODY TEXT=WHITE BGCOLOR=BLACK>"
	     "<CENTER><H1>Throttle Information for %s</H1><TABLE BORDER>\n"
	     "<TR><TH ROWSPAN=2>Name</TH>"
		 "<TH COLSPAN=3>Current</TH>"
	         "<TH COLSPAN=2>Max</TH>"
		 "<TH ROWSPAN=2>Current<BR>Delay</TH></TR>"
	     "<TR><TH>Percent<BR>Max</TH><TH>Bytes/sec</TH><TH>GB/month</TH>"
		 "<TH>Bytes/sec</TH><TH>GB/month</TH></TR>\n",
	      r->server->server_hostname);
  for(index = 0; index < NUM_RECORDS; index++)
    {
      if (cls->tableptr[index].bytes_sent != 0)
	{
	  int stat_level = 0 ;
	  int percent = 0;

	  if (cls->tableptr[index].bytes_max != 0)
	    {
	      percent = (cls->tableptr[index].bytes_sent /
	  	         (now - cls->tableptr[index].started) * 
			 100) / cls->tableptr[index].bytes_max;
	      for (stat_level = stat_levels - 1; stat_level > 0; stat_level--)
	        {
	          if (percent >= cls->alertlevels[stat_level]) 
		    break;
	    	}
	    }

	  if (cls->tableptr[index].account[0] == '\0')
	    {
	      struct passwd *p = getpwuid(index);
	      if (! p)
		snprintf(cls->tableptr[index].account,
			 sizeof(cls->tableptr[index].account),
			 "[%d]", index);
	      else
		strncpy(cls->tableptr[index].account, p->pw_name,
			sizeof(cls->tableptr[index].account));
	    }
	  rprintf(r, "<TR><TD>%s</TD>"
	  	     "<TD ALIGN=RIGHT>%s%d%s</TD>"
		     "<TD ALIGN=RIGHT>%s%d%s</TD>"
		     "<TD ALIGN=RIGHT>%s%6.2f%s</TD>"
		     "<TD ALIGN=RIGHT>%s%d%s</TD>"
		     "<TD ALIGN=RIGHT>%s%6.2f%s</TD>"
		     "<TD ALIGN=RIGHT>%s%d%s</TD>\n",
		cls->tableptr[index].account,
		stat_font[stat_level].tag,
		percent,
		stat_font[stat_level].etag,
		stat_font[stat_level].tag,
		cls->tableptr[index].bytes_sent / 
		  (now - cls->tableptr[index].started),
		stat_font[stat_level].etag,
		stat_font[stat_level].tag,
		(cls->tableptr[index].bytes_sent / 
		  (now - cls->tableptr[index].started)) * 0.0026297,
		stat_font[stat_level].etag,
		stat_font[stat_level].tag,
		cls->tableptr[index].bytes_max,
		stat_font[stat_level].etag,
		stat_font[stat_level].tag,
		cls->tableptr[index].bytes_max * 0.0026297,
		stat_font[stat_level].etag,
		stat_font[stat_level].tag,
		cls->tableptr[index].sleepage,
		stat_font[stat_level].etag);
	}
    }
  rprintf(r, "</TABLE></CENTER></BODY></HTML>\n");
  return OK;
}
	
handler_rec throttle_handlers[] = {
  { "throttle-info", throttle_handler },
  { NULL }
};
 
module throttle_module = {
   STANDARD_MODULE_STUFF,
   init_throttle,    		/* initializer */
   NULL,			/* create per-dir config */
   NULL,			/* merge per-dir config */
   make_throttle_state,	        /* server config */
   NULL,			/* merge server config */
   throttle_cmds, 	        /* command table */
   throttle_handlers,		/* handlers */
   NULL,			/* filename translation */
   NULL,			/* check_user_id */
   NULL,			/* check auth */
   check_throttle,		/* check access */
   NULL,			/* type_checker */
   NULL,			/* fixups */
   throttle_transaction, 	/* logger */
   NULL				/* header parser */
};
