/* ====================================================================
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 * ====================================================================
 *
 * 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/>.
 *
 */

/****************************************************************************
 * mod_quota: Implement access controls to user pages and/or directories
 *
 * Author  : Boyd Currey
 * Started : Thu Feb 19 22:15:32 EST 1998
 * Modified: Tue Jun 30 14:38:03 EST 1998 - Update for Apache 1.3
 *
 ****************************************************************************/

#include "httpd.h"
#include "http_config.h"

/*****************************************************************************/
/* structures and declarations                                               */
/*                                                                           */

module mod_quota_module;

typedef struct
{
  char *name;          /* name of user */
  unsigned int hash;   /* hash of username */
  unsigned int hits;   /* number of hits so far */
  unsigned int bytes;  /* number of bytes so far */
  unsigned int qhits;  /* number of hits allowed */
  unsigned int qbytes; /* number of bytes allowed */
} user_entry;

typedef struct
{
  unsigned int hits;    /* number of hits so far */
  unsigned int bytes;   /* number of bytes so far */
  unsigned int qhits;   /* number of hits allowed */
  unsigned int qbytes;  /* number of bytes allowed */
  unsigned int blocked; /* number of blocked requests */
} stats_entry;

typedef struct
{
  unsigned int toturls;          /* total number of urls checked */
  unsigned int tothits;          /* total number of hits checked */
  unsigned int totblocked;       /* total number of hits blocked */
} stats_tot;

typedef struct
{
  unsigned short state;          /* 0=off, 1=on */
  unsigned int nodes;            /* number of machines in cluster */
  unsigned int hitcutoff;        /* hit cutoff for filtering input */
  unsigned int bytecutoff;       /* byte cutoff for filtering input */
  char baseurl[MAX_STRING_LEN];  /* base URL to prepend to direcoty names */
  unsigned int userst;           /* number of users tracked */
  unsigned int usersf;           /* number of users skimmed */
  char filename[MAX_STRING_LEN]; /* filename of quota information */
  array_header *users;           /* pointer to user array */
  stats_entry *stats;            /* pointer to user stats (shared memory) */
  stats_tot *totstats;           /* pointer to total stats (shared memory) */
} conf_mod_quota;

#define QUOTA_DISABLED 0
#define QUOTA_ENABLED  1
#define QUOTA_HITCUTOFF 0
#define QUOTA_BYTECUTOFF 0
#define QUOTA_NODES 1
#define QUOTA_BASEURL "\0"

static char const rcsid[] = "$Header: /usr/home/boydc/Apache/apache_1.2.5/src/modules/mod_quota/RCS/mod_quota.c,v 0.9 1998/02/22 21:39:49 boydc Exp boydc $";

/*****************************************************************************/
/* prototypes                                                                */
/*                                                                           */

const char* cmd_mod_quota(cmd_parms*, void*, int);
const char* cmd_mod_quotafile(cmd_parms*, void*, char*);
const char* cmd_mod_quotacutoff(cmd_parms*, void*, char*, char*);
const char* cmd_mod_quotanodes(cmd_parms*, void*, char*);
const char* cmd_mod_quotabaseurl(cmd_parms*, void*, char*);
void mod_quota_init(server_rec*, pool*);
void* mod_quota_conf_server(pool*, server_rec*);
int mod_quota_handler(request_rec*);
int mod_quota_restrict(request_rec*);

void ap_send_http_header(request_rec*);
int ap_ap_rprintf(request_rec*, const char*, ...);

/*****************************************************************************/
/* cmd_mod_quota: read configuration file option to enable/disable quotas    */
/*                                                                           */

const char *cmd_mod_quota(cmd_parms *cmd, void *perdircfg, int flag)
{
  conf_mod_quota *conf;

  conf = (conf_mod_quota*)ap_get_module_config(cmd->server->module_config, &mod_quota_module);
  conf->state = (int)(flag ? QUOTA_ENABLED : QUOTA_DISABLED);
  return(NULL);

}

/*****************************************************************************/
/* cmd_mod_quotafile: read configuration file option to get quota file name  */
/*                                                                           */

const char* cmd_mod_quotafile(cmd_parms *cmd, void *perdircfg, char *arg)
{
  conf_mod_quota *conf;

  conf = (conf_mod_quota*)ap_get_module_config(cmd->server->module_config, &mod_quota_module);
  strncpy(conf->filename, ap_server_root_relative(cmd->pool, arg), MAX_STRING_LEN);
  return(NULL);

}

/*****************************************************************************/
/* cmd_mod_quotacutoff: read configuration file options to set cutoff        */
/*                                                                           */

const char* cmd_mod_quotacutoff(cmd_parms *cmd, void *perdircfg, char *arg1, char *arg2)
{
  char **ch = NULL;
  conf_mod_quota *conf;
  float wooo;

  conf = (conf_mod_quota*)ap_get_module_config(cmd->server->module_config, &mod_quota_module);
  conf->hitcutoff = (unsigned int) strtol(arg1, ch, 10);
  conf->bytecutoff = (unsigned int) strtol(arg2, ch, 10);
  return(NULL);

}

/*****************************************************************************/
/* cmd_mod_quotanodes: read configuration file options to set number of nodes*/
/*                                                                           */

const char* cmd_mod_quotanodes(cmd_parms *cmd, void *perdircfg, char *arg1)
{
  conf_mod_quota *conf;

  conf = (conf_mod_quota*)ap_get_module_config(cmd->server->module_config, &mod_quota_module);
  conf->nodes = atoi(arg1);
  if ( conf->nodes < 1 )
  {
    fprintf(stderr, "Error: QuotaNodes not positive integer, assuming 1\n", conf->filename);
	conf->nodes = 1;
  }
  return(NULL);

}

/*****************************************************************************/
/* cmd_mod_quotabaseurl: read configuration file options to set base URL     */
/*                                                                           */

const char* cmd_mod_quotabaseurl(cmd_parms *cmd, void *perdircfg, char *arg1)
{
  conf_mod_quota *conf;

  conf = (conf_mod_quota*)ap_get_module_config(cmd->server->module_config, &mod_quota_module);
  if ( strncmp(arg1, "/", MAX_STRING_LEN) == 0 )
    strncpy(conf->baseurl, "\0", MAX_STRING_LEN);
  else
    strncpy(conf->baseurl, arg1, MAX_STRING_LEN);
  return(NULL);

}

/*****************************************************************************/
/* mod_quota_init: read in quota configuration file                          */
/*                                                                           */

void mod_quota_init(server_rec *r, pool *p)
{
  conf_mod_quota *conf;
  configfile_t *fp;
  char *tmpstr;
  char buf[MAX_STRING_LEN], dir[MAX_STRING_LEN];
  user_entry *newuser, *userents;
  stats_entry *statents;
  int ret = 0, i = 0, len = 0, line = 0, hasleading = 0, hastrailing = 0;
  unsigned int hits = 0, bytes = 0, qhits = 0, qbytes = 0;

  conf = (conf_mod_quota*)ap_get_module_config(r->module_config, &mod_quota_module);

  /* scale cutoffs to number of servers */
  /*
  conf->hitcutoff = conf->hitcutoff / conf->nodes;
  conf->bytecutoff = conf->bytecutoff / conf->nodes;
  */

  /* get mod_quota configuration filename */
  tmpstr = ap_server_root_relative(p, conf->filename);
  strcpy(conf->filename, tmpstr);
  
  if (ap_is_directory(conf->filename))
  { 
    fprintf(stderr, "Error: %s is a directory\n", conf->filename);
    exit(-1);
  }

  if ( (fp = ap_pcfg_openfile(p, conf->filename)) == NULL )
  { 
    fprintf(stderr, "Error: Failed to open %s\n", conf->filename);
    exit(-1);
  }

  /* read in the quota file */

  while( (ap_cfg_getline(buf, MAX_STRING_LEN, fp)) == 0 ) 
  {
    line++;
    hasleading = 0;
    hastrailing = 0;

    switch(*buf)
    {
      case '\0': /* blank line */
        continue;

      case '#': /* comment */
        continue;

      case '/': /* line has leading slash */
        hasleading = 1;

      default: 
        if ( (ret = sscanf(buf, "%s %u %u %u %u", dir, &hits, &bytes, &qhits, &qbytes)) == 5 )
        {
/* Use this to quota bytes  */
/* if ( (hits > conf->hitcutoff) || (bytes > conf->bytecutoff) )  */
          if (hits > conf->hitcutoff) /* store entry */
          {
            conf->userst++;
            len = strlen(dir);

            if (dir[len-1] == '/') 
              hastrailing = 1;

            newuser = ap_push_array(conf->users);
            newuser->name = ap_pstrcat(p, (hasleading ? "" : "/"), dir, (hastrailing ? "" : "/"), NULL);
            newuser->hash = 0;
            newuser->hits = hits / conf->nodes;
            newuser->bytes = bytes / conf->nodes;
            newuser->qhits = qhits / conf->nodes;
            newuser->qbytes = qbytes / conf->nodes;

            for (i = 0; i < len; i++)
              newuser->hash += newuser->name[i];
          }
          else /* filtered user */
          {
            conf->usersf++;
          }
        }
        else
          fprintf(stderr, "%s: Syntax error on line %d --- ignoring!\n", conf->filename, line);

    } /* end of switch */

  }/* end of while */
    
  ap_cfg_closefile(fp);

  /* allocate shared memory and copy entries into it */

  conf->stats = (stats_entry*)mmap(NULL, (conf->users->nelts)*sizeof(stats_entry), PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);

  userents = (user_entry*)conf->users->elts;
  statents = conf->stats;

  for (i = 0; i < conf->users->nelts; i++, userents++, statents++)
  {
    statents->hits = userents->hits;
    statents->bytes = userents->bytes;
    statents->qhits = userents->qhits;
    statents->qbytes = userents->qbytes;
    statents->blocked = 0;
  }

  conf->totstats = (stats_tot*)mmap(NULL, sizeof(stats_tot), PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);

  conf->totstats->toturls = 0;
  conf->totstats->tothits = 0;
  conf->totstats->totblocked = 0;

}

/*****************************************************************************/
/* mod_quota_conf_server: allocate data structures and set defaults          */
/*                                                                           */

void* mod_quota_conf_server(pool *p, server_rec *r)
{
  conf_mod_quota *conf;

  conf = (conf_mod_quota*)ap_palloc(p, sizeof(conf_mod_quota));

  conf->state = QUOTA_DISABLED;
  conf->nodes = QUOTA_NODES;
  conf->hitcutoff = QUOTA_HITCUTOFF;
  conf->bytecutoff = QUOTA_BYTECUTOFF;
  strncpy(conf->baseurl, QUOTA_BASEURL, MAX_STRING_LEN);
  conf->userst = 0;
  conf->usersf = 0;
  strcpy(conf->filename, "\0");
  conf->users = ap_make_array(p, 16, sizeof(user_entry));
  conf->stats = NULL;

  return((void*)conf);

}

/*****************************************************************************/
/* mod_quota_handler: handler to print out current config info and stats       */
/*                                                                           */

int mod_quota_handler(request_rec *r)
{

  conf_mod_quota *conf;
  user_entry *userents;
  stats_entry *statents;
  int i = 0;

  conf = (conf_mod_quota*)ap_get_module_config(r->server->module_config, &mod_quota_module);
  userents = (user_entry*)conf->users->elts;
  statents = conf->stats;
  
  r->content_type = "text/html";
  ap_send_http_header(r);

  ap_rprintf(r, "<HTML>\n");
  ap_rprintf(r, "\n");
  ap_rprintf(r, "<HEAD>\n");
  ap_rprintf(r, "<TITLE>Quota Information</TITLE>\n");
  ap_rprintf(r, "</HEAD>\n");
  ap_rprintf(r, "\n");
  ap_rprintf(r, "<BODY>\n");
  ap_rprintf(r, "<H1 ALIGN=CENTER>Quota Information</H1>\n");
  ap_rprintf(r, "\n");
  ap_rprintf(r, "<P ALIGN=CENTER>\n");
  ap_rprintf(r, "<FONT COLOR=RED>Not currently tracking bytes!</FONT>\n");
  ap_rprintf(r, "<HR>\n");
  ap_rprintf(r, "</P>\n");

  ap_rprintf(r, "<TABLE BORDER=0>\n");
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Status:</B></TD><TD>%s</TD></TR>\n", (conf->state == QUOTA_DISABLED) ? "Disabled" : "Enabled");
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Nodes:</B></TD><TD>%u</TD></TR>\n", conf->nodes);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Hits cutoff:</B></TD><TD>%u</TD></TR>\n", conf->hitcutoff);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Bytes cutoff:</B></TD><TD>%u</TD></TR>\n", conf->bytecutoff);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Base URL:</B></TD><TD>%s</TD></TR>\n", conf->baseurl);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Users tracked:</B></TD><TD>%u</TD></TR>\n", conf->userst);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Users filtered:</B></TD><TD>%u</TD></TR>\n", conf->usersf);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Users total:</B></TD><TD>%u</TD></TR>\n", conf->userst + conf->usersf);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Total URLs:</B></TD><TD>%u</TD></TR>\n", conf->totstats->toturls);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Total hits:</B></TD><TD>%u</TD></TR>\n", conf->totstats->tothits);
  ap_rprintf(r, "  <TR><TD ALIGN=RIGHT><B>Total blocked:</B></TD><TD>%u</TD></TR>\n", conf->totstats->totblocked);
  ap_rprintf(r, "</TABLE>\n");

  ap_rprintf(r, "<HR>\n");

  if (conf->state == QUOTA_ENABLED)
  {
    ap_rprintf(r, "\n");

	/*
    ap_rprintf(r, "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=3>\n");
    ap_rprintf(r, "  <TR><TH ALIGN=LEFT>Directory</TH><TH>Hits</TH><TH>Bytes</TH><TH>QHits</TH><TH>QBytes</TH><TH>Blocked</TH></TR>\n");
    for (i = 0; i < conf->users->nelts; i++, userents++, statents++)
      ap_rprintf(r, "  <TR><TD>%s</TD><TD ALIGN=RIGHT>%u</TD><TD ALIGN=RIGHT>%u</TD><TD ALIGN=RIGHT>%u</TD><TD ALIGN=RIGHT>%u</TD><TD ALIGN=RIGHT>%u</TD></TR>\n", userents->name, statents->hits, statents->bytes, statents->qhits, statents->qbytes, statents->blocked);
    ap_rprintf(r, "</TABLE>\n");
	*/

    ap_rprintf(r, "<PRE>\n");
    ap_rprintf(r, "%-16s %10s %10s %10s %10s %10s\n\n", "Directory", "Hits", "Bytes", "QHits", "QBytes", "Blocked");
    for (i = 0; i < conf->users->nelts; i++, userents++, statents++)
      ap_rprintf(r, "<A HREF=\"%s%s\">%-16s</A> %10u %10u %10u %10u %10u\n", conf->baseurl, userents->name, userents->name, statents->hits, statents->bytes, statents->qhits, statents->qbytes, statents->blocked);
    ap_rprintf(r, "</PRE>\n");

    ap_rprintf(r, "\n");
    ap_rprintf(r, "<HR>\n");
    ap_rprintf(r, "\n");
    ap_rprintf(r, "<TABLE BORDER=0>\n");
    ap_rprintf(r, "  <TR><TD><B>Directory</B></TD><TD>Part of URL to apply quotas to</TD>\n");
    ap_rprintf(r, "  <TR><TD><B>Hits</B></TD><TD>Number of hits at startup</TD>\n");
    ap_rprintf(r, "  <TR><TD><B>Bytes</B></TD><TD>Number of bytes at startup</TD>\n");
    ap_rprintf(r, "  <TR><TD><B>QHits</B></TD><TD>Allowed number of hits</TD>\n");
    ap_rprintf(r, "  <TR><TD><B>QBytes</B></TD><TD>Allowed number of bytes</TD>\n");
    ap_rprintf(r, "  <TR><TD><B>Blocked</B></TD><TD>Number of requests blocked by quotas</TD>\n");
    ap_rprintf(r, "</TABLE>\n");
  }

  ap_rprintf(r, "\n");
  ap_rprintf(r, "</BODY>\n");
  ap_rprintf(r, "</HTML>\n");

  return(OK);

}

/*****************************************************************************/
/* mod_quota_restrict: check URI for quota restrictions                        */
/*                                                                           */

int mod_quota_restrict(request_rec *r)
{
  conf_mod_quota *conf;
  user_entry *userents;
  stats_entry *statents;
  stats_tot *totstats;
  int access = OK;
  int i = 0, done = 0;

  conf = (conf_mod_quota*)ap_get_module_config(r->server->module_config, &mod_quota_module);

  if (conf->state == QUOTA_DISABLED)
    access = DECLINED;
  else
  {
    userents = (user_entry*)conf->users->elts;
    statents = conf->stats;
    totstats = conf->totstats;

    i = 0;
    done = 0;
    totstats->toturls++;

    while ( (!done) && (i < conf->users->nelts) )
    {
      if ( strncmp(r->uri, userents->name, strlen(userents->name)) == 0 )
      {
        done = 1;
        statents->hits++;
        totstats->tothits++;

/* Use this to quota bytes  */
/* if ( (statents->hits > userents->qhits) || (statents->bytes > userents->qbytes) ) */

        if (statents->hits > userents->qhits)
        {
          statents->blocked++;
          totstats->totblocked++;
          access = HTTP_SERVICE_UNAVAILABLE;
        }
      }

      i++;
      userents++;
      statents++;

    }
  }

  return(access);

}

/*****************************************************************************/
/* a record of commands                                                      */
/*                                                                           */

static command_rec mod_quota_cmds[] =
{
  {"Quota", cmd_mod_quota, NULL, RSRC_CONF, FLAG, 
   "On or Off to enable or disable (default) user quotas"},
  {"QuotaFile", cmd_mod_quotafile, NULL, RSRC_CONF, TAKE1, 
   "path of quota file"},
  {"QuotaCutoff", cmd_mod_quotacutoff, NULL, RSRC_CONF, TAKE2, 
   "hit cutoff value, and byte cutoff value"},
  {"QuotaNodes", cmd_mod_quotanodes, NULL, RSRC_CONF, TAKE1, 
   "number of machines in cluster"},
  {"QuotaBaseURL", cmd_mod_quotabaseurl, NULL, RSRC_CONF, TAKE1, 
   "base URL for directory names"},
  {NULL}
};

/*****************************************************************************/
/* a record of handlers                                                      */
/*                                                                           */

handler_rec mod_quota_handlers[] =
{
  {"mod_quota-status", mod_quota_handler},
  {NULL}
};

/*****************************************************************************/
/* module structure which glues all this together                            */
/*                                                                           */

module mod_quota_module = {
   STANDARD_MODULE_STUFF,
   mod_quota_init,			/* initializer */
   NULL,				/* dir config creater */
   NULL,				/* dir merger default is to override */
   mod_quota_conf_server,		/* server config */
   NULL,				/* merge server config */
   mod_quota_cmds,			/* command table */
   mod_quota_handlers,			/* handlers */
   NULL,				/* filename translation */
   NULL,				/* check_user_id */
   NULL,				/* check auth */
   mod_quota_restrict,			/* check access */
   NULL,				/* type_checker */
   NULL,				/* fixups */
   NULL,				/* logger */
   NULL					/* header parser */
};
