Download | Plain Text | Line Numbers


/*
 * Copyright (C) 2000-2002 David Jao <djao@dominia.org>
 * "MaxConnPerUid", "MaxConnPerVhost" and "MaxLA*" portions by Maxim Chirkov <mc@tyumen.ru>
 * per VHost settings, advanced content-type shm-cache and check, scoreboard dump,
 * optional KeepAlive-Values, code reorganization and apache2 port by Manuel Mausz <manuel@mausz.at>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, this permission notice, and the
 * following disclaimer shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */
 
#define CORE_PRIVATE
#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"
#include <sys/types.h>
 
/* apache2 specific includes */
#include "unixd.h"
#include "ap_mpm.h"
#include "apr_strings.h"
 
#if HAVE_SHMGET || APR_HAS_SHARED_MEMORY
# define CREATE_OWN_SCOREBOARD 1
# include <sys/ipc.h>
# include <sys/shm.h>
#else
# define CREATE_OWN_SCOREBOARD 0
#endif
 
#define MODULE_NAME    "mod_limitipconn"
#define MODULE_VERSION "0.21-vhost"
#define SHM_KEY        IPC_PRIVATE
//#define DEBUG 1
//#define DUMP 1
 
#ifdef DUMP
# include <sys/types.h>
# include <unistd.h>
# include <time.h>
# ifndef DEFAULT_TIME_FORMAT
#  define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
# endif
#endif
 
#define LIPC_LOG(level,errno)  level,APR_FROM_OS_ERROR(errno)
module AP_MODULE_DECLARE_DATA limitipconn_module;
static int server_limit, thread_limit;
 
#if CREATE_OWN_SCOREBOARD
/* apache2 doesn't export ap_sb_handle_t via scoreboard.h */
struct ap_sb_handle_t
{
  int child_num;
  int thread_num;
};
#endif
 
#if CREATE_OWN_SCOREBOARD
/* our own personal scoreboard */
typedef struct {
  uid_t userid;
  char handler[100];
} limit_scoreboard;
 
//static limit_scoreboard *sb = (limit_scoreboard *)-1;
static void *sb = (void *)-1;
static int limit_shmid;
#endif
 
typedef struct
{
  int limit;                /* max number of connections per IP */
  int limit_ka;             /* optional keepalive limit */
#if CREATE_OWN_SCOREBOARD
  int limit_uid;            /* max number of connections per user */
  int limit_uid_ka;         /* optional keepalive limit */
#endif
  int limit_vhost;          /* max number of connections per virtual host */
  int limit_vhost_ka;       /* optional keepalive limit */
  double limit_la1;         /* maximum value of Load Average for 1 min. */
  double limit_la5;         /* maximum value of Load Average for 5 min. */
  double limit_la15;        /* maximum value of Load Average for 15 min. */
 
#if CREATE_OWN_SCOREBOARD
  apr_array_header_t *excl_limit; /* array of MIME types to limit check; all
                                     other types are exempt */
#endif
} limitipconn_config;
 
static void *limitipconn_create_config(apr_pool_t *p, server_rec *s)
{
  limitipconn_config *cfg = (limitipconn_config *)apr_pcalloc(p, sizeof(*cfg));
 
  /* default configuration: no limit, and both arrays are empty */
  cfg->limit          = -1;
  cfg->limit_ka       = -1;
#if CREATE_OWN_SCOREBOARD
  cfg->limit_uid      = -1;
  cfg->limit_uid_ka   = -1;
#endif
  cfg->limit_vhost    = -1;
  cfg->limit_vhost_ka = -1;
  cfg->limit_la1      = -1.0;
  cfg->limit_la5      = -1.0;
  cfg->limit_la15     = -1.0;
#if CREATE_OWN_SCOREBOARD
  cfg->excl_limit     = apr_array_make(p, 0, sizeof(char *));
#endif
 
  return cfg;
}
 
#ifdef DUMP
static const char *ap_scdump_fname = NULL;
 
static int dump_scoreboard(request_rec *r, const char *reason, int matches[server_limit][thread_limit])
{
  worker_score *ws_record;
  process_score *ps_record;
  char buf[255];
  char *fname;
  FILE *fd;
  time_t nowtime = time(NULL);
  int i, j, dump;
  char *vhost = NULL;
  pid_t worker_pid;
  ap_generation_t worker_generation;
  uid_t userid = 0;
#if CREATE_OWN_SCOREBOARD
  limit_scoreboard (* sb2)[thread_limit];
  sb2 = (limit_scoreboard (*)[thread_limit])sb;
#endif
 
  if (!ap_scdump_fname)
  {
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_ERR, 0), r, "Error: Trying to dump scoreboard, but directive ScoreboardDump isn't set");
    return 1;
  }
 
  sprintf(buf, "%s.%ld", ap_scdump_fname, (long)getpid());
  fname = ap_server_root_relative(r->pool, buf);
  if (!(fd = fopen(fname, "a")))
  {
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_ERR, errno), r, "Error while opening: %s.", fname);
    return 1;
  }
 
  fprintf(fd, "Scoreboard Dump for %s due to %s restriction\n", ap_get_server_name(r), reason);
  fprintf(fd, "Server Version: %s\n", ap_get_server_version());
  fprintf(fd, "Current Time: %s\n", ap_ht_time(r->pool, nowtime, DEFAULT_TIME_FORMAT, 0));
  fprintf(fd, "Request URI: %s [%s]\n", ap_escape_logitem(r->pool, r->unparsed_uri), ap_escape_logitem(r->pool, r->hostname));
  fprintf(fd, "Parent Server Generation: %d\n", (int)ap_my_generation);
  fputs("\n", fd);
  fflush(fd);
 
  fputs("Server Details\n", fd);
  for (i = 0; i < server_limit; i++)
  {
    for (j = 0; j < thread_limit; j++)
    {
      ws_record = ap_get_scoreboard_worker(i, j);
      ps_record = ap_get_scoreboard_process(i);
      if (ps_record->generation == ap_my_generation)
        vhost = ws_record->vhost;
 
      dump = 0;
      switch (ws_record->status)
      {
        case SERVER_BUSY_READ:
        case SERVER_BUSY_WRITE:
        case SERVER_BUSY_KEEPALIVE:
        case SERVER_BUSY_LOG:
        case SERVER_BUSY_DNS:
        case SERVER_GRACEFUL:
          if (matches[i][j])
            dump = 1;
          break;
        default:
          break;
      }
      if (!dump)
        continue;
 
      /* MPM sets per-worker pid and generation */
      if (ws_record->pid)
      {
        worker_pid = ws_record->pid;
        worker_generation = ws_record->generation;
      }
      else
      {
        worker_pid = ps_record->pid;
        worker_generation = ps_record->generation;
      }
 
 
      if (ws_record->status == SERVER_DEAD)
#ifdef TPF
        if (kill(ps_record->pid, 0) == 0)
        {
          /* on TPF show PIDs of the living dead */
          fprintf(fd, "Server %d-%d (%d): [", i, worker_generation, worker_pid);
        }
        else
#endif
        fprintf(fd, "Server %d-%d (-): [", i, worker_generation);
      else
        fprintf(fd, "Server %d-%d (%d): [", i, worker_generation, worker_pid);
 
      switch (ws_record->status)
      {
        case SERVER_READY:
          fputs("Ready", fd);
          break;
        case SERVER_STARTING:
          fputs("Starting", fd);
          break;
        case SERVER_BUSY_READ:
          fputs("Read", fd);
          break;
        case SERVER_BUSY_WRITE:
          fputs("Write", fd);
          break;
        case SERVER_BUSY_KEEPALIVE:
          fputs("Keepalive", fd);
          break;
        case SERVER_BUSY_LOG:
          fputs("Logging", fd);
          break;
        case SERVER_BUSY_DNS:
          fputs("DNS lookup", fd);
          break;
        case SERVER_DEAD:
          fputs("Dead", fd);
          break;
        case SERVER_GRACEFUL:
          fputs("Graceful", fd);
          break;
        default:
          fputs("?STATE?", fd);
          break;
      }
 
#if CREATE_OWN_SCOREBOARD
      userid = sb2[i][j].userid;
#endif
 
      fprintf(fd, "] %s {%s %s} [%s - %d]\n",
        ap_escape_html(r->pool, ws_record->client),
        ap_escape_html(r->pool, ap_escape_logitem(r->pool, ws_record->request)),
#if CREATE_OWN_SCOREBOARD
        sb2[i][j].handler,
#else
        "(unavailable)",
#endif
        vhost ? ap_escape_html(r->pool, vhost) : "(unavailable)",
        userid);
    }
  }
 
  fputs("\n-------------------------------------------------------------------------------\n\n", fd);
 
  fclose(fd);
  ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_ERR, 0), r, "Wrote scoreboard dump to: %s", fname);
 
  return 0;
}
#endif
 
/* Simple merge: Per vhost entries overrides main server entries */
static void *limitipconn_merge_config(apr_pool_t *p, void *BASE, void *ADD)
{
  limitipconn_config *base = BASE;
  limitipconn_config *add  = ADD;
  limitipconn_config *cfg  = (limitipconn_config *)apr_pcalloc(p, sizeof(*cfg));
 
  cfg->limit          = (add->limit == -1)            ? base->limit          : add->limit;
  cfg->limit_ka       = (add->limit_ka == -1)         ? base->limit_ka       : add->limit_ka;
#if CREATE_OWN_SCOREBOARD
  cfg->limit_uid      = (add->limit_uid == -1)        ? base->limit_uid      : add->limit_uid;
  cfg->limit_uid_ka   = (add->limit_uid_ka == -1)     ? base->limit_uid_ka   : add->limit_uid_ka;
#endif
  cfg->limit_vhost    = (add->limit_vhost == -1)      ? base->limit_vhost    : add->limit_vhost;
  cfg->limit_vhost_ka = (add->limit_vhost_ka == -1)   ? base->limit_vhost_ka : add->limit_vhost_ka;
  cfg->limit_la1      = (add->limit_la1  == -1.0)     ? base->limit_la1      : add->limit_la1;
  cfg->limit_la5      = (add->limit_la5  == -1.0)     ? base->limit_la5      : add->limit_la5;
  cfg->limit_la15     = (add->limit_la15 == -1.0)     ? base->limit_la15     : add->limit_la15;
#if CREATE_OWN_SCOREBOARD
  cfg->excl_limit     = (add->excl_limit->nelts == 0) ? base->excl_limit     : add->excl_limit;
#endif
 
  return cfg;
}
 
static int limitipconn_handler(request_rec *r)
{
  limitipconn_config *cfg = (limitipconn_config *)ap_get_module_config(r->server->module_config, &limitipconn_module);
#if CREATE_OWN_SCOREBOARD
  limit_scoreboard (* sb2)[thread_limit];
  limit_scoreboard *sbrec = NULL;
  char **exlim = (char **)cfg->excl_limit->elts;
  int found;
  ap_unix_identity_t *identity;
  ap_sb_handle_t *sbh;
#endif
  double current_la[3];
  int ip_req_count    = 0;
  const char *address = NULL;
#if CREATE_OWN_SCOREBOARD
  int uid_req_count   = 0;
  uid_t current_uid   = 0;
#endif
  int vhost_req_count = 0;
  const char *current_vhost = NULL;
  worker_score *ws_record;
  process_score *ps_record;
  int i, j, k;
#ifdef DUMP
  int matches[server_limit][thread_limit];
#endif
 
  /* A limit value of 0 by convention means no limit. */
  if (cfg->limit <= 0 && cfg->limit_ka <= 0
#if CREATE_OWN_SCOREBOARD
      && cfg->limit_uid <= 0 && cfg->limit_uid_ka <= 0
#endif
      && cfg->limit_vhost <= 0 && cfg->limit_vhost_ka <= 0
      && cfg->limit_la1 <= 0.0 && cfg->limit_la5 <= 0.0 && cfg->limit_la15 <= 0.0)
  {
#ifdef DEBUG
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_DEBUG, 0), r, "mod_limitipconn: OK: No limit");
#endif
    return OK;
  }
 
#if CREATE_OWN_SCOREBOARD
  sb2 = (limit_scoreboard (*)[thread_limit])sb;
  sbh = r->connection->sbh;
  if (sbh == NULL)
  {
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_ERR, 0), r, "mod_limitipconn: Warning: Request has no reference to scoreboard!");
    return OK;
  }
  sbrec = &sb2[sbh->child_num][sbh->thread_num];
 
  /* Cycle through the exclusive list, if it exists; if our handler
   * is not present, bail out */
  if (cfg->excl_limit->nelts > 0)
  {
    /* always update our scoreboard */
    if (r->handler)
      apr_cpystrn(sbrec->handler, r->handler, sizeof(sbrec->handler) - 1);
    else
      sbrec->handler[0] = 0;
 
# ifdef DEBUG
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_DEBUG, 0), r, "mod_limitipconn: uri: %s handler: %s", r->uri, r->handler);
# endif
 
    found = 0;
    for (i = 0; r->handler && i < cfg->excl_limit->nelts && !found; i++)
    {
      if (strncmp(exlim[i], r->handler, strlen(exlim[i])) == 0)
        found = 1;
    }
 
    if (!found)
    {
# ifdef DEBUG
      ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_DEBUG, 0), r, "mod_limitipconn: OK: %s not checked", r->handler);
# endif
      return OK;
    }
  }
#endif /* CREATE_OWN_SCOREBOARD */
 
  /* Check Load Average overflow */
  if (cfg->limit_la1 > 0.0 || cfg->limit_la5 > 0.0 || cfg->limit_la15 > 0.0)
  {
    if (getloadavg(current_la, 3) != -1)
    {
      if ((current_la[0] >= cfg->limit_la1) && (current_la[1] >= cfg->limit_la5) && (current_la[2] >= cfg->limit_la15))
      {
        r->connection->keepalive = -1;
        ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_ERR, 0), r, "Load Average limit exceeded (limit: %.2f, %.2f, %.2f)",
            cfg->limit_la1, cfg->limit_la5, cfg->limit_la15);
        return HTTP_SERVICE_UNAVAILABLE;
      }
    }
  }
 
  /* get address */
  if (cfg->limit > 0 || cfg->limit_ka > 0)
  {
#ifdef RECORD_FORWARD
    if ((address = ap_table_get(r->headers_in, "X-Forwarded-For")) == NULL)
#endif
      address = r->connection->remote_ip;
  }
 
  /* Get uid of current virual host for future use */
  if (r->server->is_virtual)
  {
#if CREATE_OWN_SCOREBOARD
    if (cfg->limit_uid > 0 || cfg->limit_uid_ka > 0)
    {
      if ((identity = ap_run_get_suexec_identity(r)))
        current_uid = identity->uid;
    }
    sbrec->userid = current_uid;
#endif
    if (cfg->limit_vhost > 0 || cfg->limit_vhost_ka > 0)
      current_vhost = r->server->server_hostname;
  }
 
  /* Count up the number of connections we are handling right now from
   * this IP address */
  for (j = 0; j < server_limit; j++)
  {
    for (k = 0; k < thread_limit; k++)
    {
#ifdef DUMP
      matches[j][k] = 0;
#endif
      ws_record = ap_get_scoreboard_worker(j, k);
      ps_record = ap_get_scoreboard_process(i);
      switch (ws_record->status)
      {
        case SERVER_BUSY_READ:
        case SERVER_BUSY_WRITE:
        case SERVER_BUSY_KEEPALIVE:
        case SERVER_BUSY_LOG:
        case SERVER_BUSY_DNS:
        case SERVER_GRACEFUL:
#if CREATE_OWN_SCOREBOARD
          /* Cycle through the exclusive list, if it exists; if our handler
           * is not present, skip */
          if (cfg->excl_limit->nelts > 0)
          {
            /* skip if no handler is set */
            if (!sb2[j][k].handler)
              continue;
 
            found = 0;
            for (i = 0; i < cfg->excl_limit->nelts && !found; i++)
            {
              if (strncmp(exlim[i], sb2[j][k].handler, strlen(exlim[i])) == 0)
                found = 1;
            }
            if (!found)
              continue;
          }
#endif
 
          if (address && (strcmp(address, ws_record->client) == 0)
#ifdef RECORD_FORWARD
              || (strcmp(address, ws_record->fwdclient) == 0)
#endif
          )
          {
            ip_req_count++;
#ifdef DUMP
            matches[j][k] = 1;
#endif
          }
 
#if CREATE_OWN_SCOREBOARD
          if ((ps_record->generation == ap_my_generation))
          {
            if (current_uid && current_uid != unixd_config.user_id && sb2[j][k].userid == current_uid)
              uid_req_count++;
            if (current_vhost && (strcmp(ws_record->vhost, current_vhost) == 0))
              vhost_req_count++;
          }
#endif
          break;
        default:
          break;
      }
    }
  }
 
  /* turn off keepalive if keepalive-limit reached */
  if ((cfg->limit_ka > 0 && ip_req_count > cfg->limit_ka)
#if CREATE_OWN_SCOREBOARD
      || (cfg->limit_uid_ka > 0 && uid_req_count > cfg->limit_uid_ka)
#endif
      || (cfg->limit_vhost_ka > 0 && vhost_req_count > cfg->limit_vhost_ka))
  {
#ifdef DEBUG
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_DEBUG, 0), r, "mod_limitipconn: keepalive-limit reached. deactivating keepalive.");
#endif
    r->connection->keepalive = -1;
  }
 
  /* hard limit reached */
  if (cfg->limit > 0 && ip_req_count > cfg->limit)
  {
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_ERR, 0), r, "Rejected, too many connections from address %s (limit: %d).",
        address, cfg->limit);
    apr_table_setn(r->subprocess_env, "LIMITIP", "1");
#ifdef DUMP
    dump_scoreboard(r, "IP-Limit", matches);
#endif
    return HTTP_SERVICE_UNAVAILABLE;
  }
#if CREATE_OWN_SCOREBOARD
  else if (cfg->limit_uid > 0 && uid_req_count > cfg->limit_uid)
  {
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_ERR, 0), r, "Rejected, too many connections for uid %u (limit: %d)",
        current_uid, cfg->limit_uid);
#ifdef DUMP
    dump_scoreboard(r, "UID-Limit", matches);
#endif
    return HTTP_SERVICE_UNAVAILABLE;
  }
#endif
  else if (cfg->limit_vhost > 0 && vhost_req_count > cfg->limit_vhost)
  {
    ap_log_rerror(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_ERR, 0), r, "Rejected, too many connections for vhost %s (limit: %d)",
        current_vhost, cfg->limit_vhost);
#ifdef DUMP
    dump_scoreboard(r, "Vhost-Limit", matches);
#endif
    return HTTP_SERVICE_UNAVAILABLE;
  }
 
  return OK;
}
 
/* Parse the MaxConnPerIP directive */
static const char *limit_config_cmd(cmd_parms *parms, void *mconfig, const char *arg1, const char *arg2)
{
  limitipconn_config *cfg = (limitipconn_config *)ap_get_module_config(parms->server->module_config, &limitipconn_module);
  long int limit, limit_ka;
 
  limit = strtol(arg1, (char **)NULL, 10);
  if (limit < 0 || limit > INT_MAX)
    return "Integer overflow or invalid number";
 
  if (arg2)
  {
    limit_ka = strtol(arg2, (char **)NULL, 10);
    if (limit_ka < 0 || limit_ka > INT_MAX)
      return "Integer overflow or invalid number";
    if (limit > 0 && limit_ka > limit)
      return "KeepAlive-limit has to be smaller than hard limit.";
    cfg->limit_ka = limit_ka;
  }
 
  cfg->limit = limit;
  return NULL;
}
 
#if CREATE_OWN_SCOREBOARD
/* Parse the IPLimit directive */
static const char *excl_limit_config_cmd(cmd_parms *parms, void *mconfig, const char *arg)
{
  limitipconn_config *cfg = (limitipconn_config *)ap_get_module_config(parms->server->module_config, &limitipconn_module);
  *(char **)apr_array_push(cfg->excl_limit) = apr_pstrdup(parms->pool, arg);
  return NULL;
}
#endif
 
/* Parse the MaxConnPerVhost directive */
static const char *limit_vhost_config_cmd(cmd_parms *parms, void *mconfig, const char *arg1, const char *arg2)
{
  limitipconn_config *cfg = (limitipconn_config *)ap_get_module_config(parms->server->module_config, &limitipconn_module);
  long int limit, limit_ka;
 
  limit = strtol(arg1, (char **)NULL, 10);
  if (limit < 0 || limit > INT_MAX)
    return "Integer overflow or invalid number";
 
  if (arg2)
  {
    limit_ka = strtol(arg2, (char **)NULL, 10);
    if (limit_ka < 0 || limit_ka > INT_MAX)
      return "Integer overflow or invalid number";
    if (limit > 0 && limit_ka > limit)
      return "KeepAlive-limit has to be smaller than hard limit.";
    cfg->limit_vhost_ka = limit_ka;
  }
 
  cfg->limit_vhost = limit;
  return NULL;
}
 
#if CREATE_OWN_SCOREBOARD
/* Parse the MaxConnPerUid directive */
static const char *limit_uid_config_cmd(cmd_parms *parms, void *mconfig, const char *arg1, const char *arg2)
{
  limitipconn_config *cfg = (limitipconn_config *)ap_get_module_config(parms->server->module_config, &limitipconn_module);
  long int limit, limit_ka;
 
  limit = strtol(arg1, (char **)NULL, 10);
  if (limit < 0 || limit > INT_MAX)
    return "Integer overflow or invalid number";
 
  if (arg2)
  {
    limit_ka = strtol(arg2, (char **)NULL, 10);
    if (limit_ka < 0 || limit_ka > INT_MAX)
      return "Integer overflow or invalid number";
    if (limit > 0 && limit_ka > limit)
      return "KeepAlive-limit has to be smaller than hard limit.";
    cfg->limit_uid_ka = limit_ka;
  }
 
  cfg->limit_uid = limit;
  return NULL;
}
#endif
 
/* Parse the MaxLA1 directive */
static const char *limit_la1_config_cmd(cmd_parms *parms, void *mconfig, const char *arg)
{
  limitipconn_config *cfg = (limitipconn_config *)ap_get_module_config(parms->server->module_config, &limitipconn_module);
  double limit = strtod(arg, (char **)NULL);
 
  if (limit < 0.0)
    return "Invalid LA1 value";
 
  cfg->limit_la1 = limit;
  return NULL;
}
 
/* Parse the MaxLA5 directive */
static const char *limit_la5_config_cmd(cmd_parms *parms, void *mconfig, const char *arg)
{
  limitipconn_config *cfg = (limitipconn_config *)ap_get_module_config(parms->server->module_config, &limitipconn_module);
  double limit = strtod(arg, (char **)NULL);
 
  if (limit < 0.0)
    return "Invalid LA5 value";
 
  cfg->limit_la5 = limit;
  return NULL;
}
 
/* Parse the MaxLA15 directive */
static const char *limit_la15_config_cmd(cmd_parms *parms, void *mconfig, const char *arg)
{
  limitipconn_config *cfg = (limitipconn_config *)ap_get_module_config(parms->server->module_config, &limitipconn_module);
  double limit = strtod(arg, (char **)NULL);
 
  if (limit < 0.0)
    return "Invalid LA15 value";
 
  cfg->limit_la15 = limit;
  return NULL;
}
 
#ifdef DUMP
static const char *set_scoreboard_dump(cmd_parms *parms, void *mconfig, const char *arg)
{
  const char *err = ap_check_cmd_context(parms, GLOBAL_ONLY);
  if (err != NULL)
    return err;
 
  if (parms->server->is_virtual)
    return "ScoreboardDump directive not allowed in <VirtualHost>";
 
  ap_scdump_fname = arg;
  return NULL;
}
#endif
 
static command_rec limitipconn_cmds[] = {
  AP_INIT_TAKE12("MaxConnPerIP", limit_config_cmd, NULL, RSRC_CONF,
    "maximum simultaneous connections per IP address. Second value is optional. If reached, KeepAlive will be turned off."),
#if CREATE_OWN_SCOREBOARD
  AP_INIT_ITERATE("IPLimit", excl_limit_config_cmd, NULL, RSRC_CONF,
    "restrict limit checking to these handlers/MIME types only."),
#endif
#if CREATE_OWN_SCOREBOARD
  AP_INIT_TAKE12("MaxConnPerUid", limit_uid_config_cmd, NULL, RSRC_CONF,
    "maximum simultaneous connections per user. Second value is optional. If reached, KeepAlive will be turned off."),
#endif
  AP_INIT_TAKE12("MaxConnPerVhost", limit_vhost_config_cmd, NULL, RSRC_CONF,
    "maximum simultaneous connections per virtual host. Second value is optional. If reached, KeepAlive will be turned off."),
  AP_INIT_TAKE1("MaxLA1", limit_la1_config_cmd, NULL, RSRC_CONF,
    "maximum Load Overage value for the past 1 minute."),
  AP_INIT_TAKE1("MaxLA5", limit_la5_config_cmd, NULL, RSRC_CONF,
    "maximum Load Overage value for the past 5 minutes."),
  AP_INIT_TAKE1("MaxLA15", limit_la15_config_cmd, NULL, RSRC_CONF,
    "maximum Load Overage value for the past 15 minutes."),
#ifdef DUMP
  AP_INIT_TAKE1("ScoreboardDump", set_scoreboard_dump, NULL, RSRC_CONF,
    "The filename of the scoreboard dumps. Will be appended by the process id."),
#endif
  { NULL },
};
 
#if CREATE_OWN_SCOREBOARD
static apr_status_t limit_detach_shm(void *data)
{
  shmdt(data);
  return APR_SUCCESS;
}
#endif
 
static int limitipconn_init(apr_pool_t *p, apr_pool_t *plog,
    apr_pool_t *ptemp, server_rec *s)
{
  void *data;
  const char *userdata_key = "limitipconn_init";
 
  /* initialize_module() will be called twice, and if it's a DSO
   * then all static data from the first call will be lost. Only
   * set up our static data on the second call. */
  apr_pool_userdata_get(&data, userdata_key, s->process->pool);
  if (!data)
  {
    apr_pool_userdata_set((const void *)1, userdata_key,
                          apr_pool_cleanup_null, s->process->pool);
    return APR_SUCCESS;
  }
 
  ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
  ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit);
 
#if CREATE_OWN_SCOREBOARD
  if ((limit_shmid = shmget(SHM_KEY, sizeof(limit_scoreboard) * server_limit * thread_limit, IPC_CREAT | SHM_R | SHM_W)) == -1)
  {
    ap_log_error(APLOG_MARK, LIPC_LOG(APLOG_EMERG, errno), s, "could not initialize shared memory");
    exit(1);
  }
 
  ap_log_error(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_NOTICE, 0), s, "mod_limitipconn: initialized shared memory.");
 
  //if ((sb = (limit_scoreboard *) shmat(limit_shmid, NULL, 0)) == (limit_scoreboard *) -1)
  if ((sb = (void *)shmat(limit_shmid, NULL, 0)) == (void *) -1)
    ap_log_error(APLOG_MARK, LIPC_LOG(APLOG_EMERG, errno), s, "shmat error");
    /* exit later after marking the segment to remove itself */
  else
    apr_pool_cleanup_register(p, (void *)sb, limit_detach_shm, apr_pool_cleanup_null);
 
  /* Mark the segment for deletion once all attachments are detached */
  if (shmctl(limit_shmid, IPC_RMID, NULL) == -1)
    ap_log_error(APLOG_MARK, LIPC_LOG(APLOG_EMERG, errno), s, "Could not mark shared segment for deletion, you must manually clean it up");
 
  /* exit if we didn't attach successfully */
  if (sb == (void *) -1)
    exit(1);
#endif
 
  ap_log_error(APLOG_MARK, LIPC_LOG(APLOG_NOERRNO|APLOG_NOTICE, 0), s, MODULE_NAME " " MODULE_VERSION " started.");
  return APR_SUCCESS;
}
 
static void register_hooks(apr_pool_t * p)
{
  ap_hook_post_config(limitipconn_init, NULL, NULL, APR_HOOK_MIDDLE);
  ap_hook_fixups(limitipconn_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
 
module AP_MODULE_DECLARE_DATA limitipconn_module =
{
  STANDARD20_MODULE_STUFF,
  NULL,                           /* per-directory config creator */
  NULL,                           /* dir config merger */
  limitipconn_create_config,      /* server config creator */
  limitipconn_merge_config,       /* server config merger */
  limitipconn_cmds,               /* command table */
  register_hooks,                 /* set up other request processing hooks */
};