/* * Copyright (C) 2000-2002 David Jao * * 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. * */ #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 "ap_mpm.h" #include "apr_strings.h" #include "scoreboard.h" #define MODULE_NAME "mod_limitipconn" #define MODULE_VERSION "0.22" module AP_MODULE_DECLARE_DATA limitipconn_module; int server_limit, thread_limit; typedef struct { signed int limit; /* max number of connections per IP */ apr_array_header_t *no_limit; /* array of MIME types exempt from limit checking */ apr_array_header_t *excl_limit; /* array of MIME types to limit check; all other types are exempt */ } limitipconn_dir_config; static void *limitipconn_create_dir_config(apr_pool_t *p, char *path) { limitipconn_dir_config *cfg = (limitipconn_dir_config *) apr_pcalloc(p, sizeof (*cfg)); /* default configuration: no limit, and both arrays are empty */ cfg->limit = 0; cfg->no_limit = apr_array_make(p, 0, sizeof(char *)); cfg->excl_limit = apr_array_make(p, 0, sizeof(char *)); return (void *) cfg; } static int limitipconn_handler(request_rec *r) { /* get configuration information */ limitipconn_dir_config *cfg = (limitipconn_dir_config *) ap_get_module_config(r->per_dir_config, &limitipconn_module); /* convert Apache arrays to normal C arrays */ char **nolim = (char **) cfg->no_limit->elts; char **exlim = (char **) cfg->excl_limit->elts; const char *address; /* loop index variables */ int i; int j; /* running count of number of connections from this address */ int ip_count = 0; /* Content-type of the current request */ const char *content_type; /* scoreboard data structure */ worker_score *ws_record; /* We decline to handle subrequests: otherwise, in the next step we * could get into an infinite loop. */ if (!ap_is_initial_req(r)) { return DECLINED; } /* Look up the Content-type of this request. We need a subrequest * here since this module might be called before the URI has been * translated into a MIME type. */ content_type = ap_sub_req_lookup_uri(r->uri, r, NULL)->content_type; /* If there's no Content-type, use the default. */ if (!content_type) { content_type = ap_default_type(r); } #ifdef RECORD_FORWARD if ((address = apr_table_get(r->headers_in, "X-Forwarded-For")) == NULL) #endif address = r->connection->remote_ip; /* A limit value of 0 by convention means no limit. */ if (cfg->limit == 0) { return OK; } /* Cycle through the exempt list; if our content_type is exempt, * return OK */ for (i = 0; i < cfg->no_limit->nelts; i++) { if ((ap_strcasecmp_match(content_type, nolim[i]) == 0) || (strncmp(nolim[i], content_type, strlen(nolim[i])) == 0)) { return OK; } } /* Cycle through the exclusive list, if it exists; if our MIME type * is not present, bail out */ if (cfg->excl_limit->nelts) { int excused = 1; for (i = 0; i < cfg->excl_limit->nelts; i++) { if ((ap_strcasecmp_match(content_type, exlim[i]) == 0) || (strncmp(exlim[i], content_type, strlen(exlim[i])) == 0)) { excused = 0; } } if (excused) { return OK; } } /* Count up the number of connections we are handling right now from * this IP address */ for (i = 0; i < server_limit; ++i) { for (j = 0; j < thread_limit; ++j) { ws_record = ap_get_scoreboard_worker(i, j); switch (ws_record->status) { case SERVER_BUSY_READ: case SERVER_BUSY_WRITE: case SERVER_BUSY_KEEPALIVE: case SERVER_BUSY_DNS: case SERVER_GRACEFUL: if ((strcmp(address, ws_record->client) == 0) #ifdef RECORD_FORWARD || (strcmp(address, ws_record->fwdclient) == 0) #endif ) { ip_count++; } break; case SERVER_DEAD: case SERVER_READY: case SERVER_STARTING: case SERVER_BUSY_LOG: case SERVER_IDLE_KILL: case SERVER_CLOSING: break; } } } if ((ip_count > cfg->limit) && (cfg->limit)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Rejecting client at %s", address); /* set an environment variable */ apr_table_setn(r->subprocess_env, "LIMITIP", "1"); /* return 503 */ return HTTP_SERVICE_UNAVAILABLE; } else { return OK; } } /* Parse the MaxConnPerIP directive */ static const char *limit_config_cmd(cmd_parms *parms, void *mconfig, const char *arg) { limitipconn_dir_config *cfg = (limitipconn_dir_config *) mconfig; signed long int limit = strtol(arg, (char **) NULL, 10); /* No reasonable person would want more than 2^16. Better would be to use LONG_MAX but that causes portability problems on win32 */ if ((limit > 65535) || (limit < 0)) { return "Integer overflow or invalid number"; } cfg->limit = limit; return NULL; } /* Parse the NoIPLimit directive */ static const char *no_limit_config_cmd(cmd_parms *parms, void *mconfig, const char *arg) { limitipconn_dir_config *cfg = (limitipconn_dir_config *) mconfig; *(char **) apr_array_push(cfg->no_limit) = apr_pstrdup(parms->pool, arg); return NULL; } /* Parse the OnlyIPLimit directive */ static const char *excl_limit_config_cmd(cmd_parms *parms, void *mconfig, const char *arg) { limitipconn_dir_config *cfg = (limitipconn_dir_config *) mconfig; *(char **) apr_array_push(cfg->excl_limit) = apr_pstrdup(parms->pool, arg); return NULL; } /* Array describing structure of configuration directives */ static command_rec limitipconn_cmds[] = { AP_INIT_TAKE1("MaxConnPerIP", limit_config_cmd, NULL, OR_LIMIT, "maximum simultaneous connections per IP address"), AP_INIT_ITERATE("NoIPLimit", no_limit_config_cmd, NULL, OR_LIMIT, "MIME types for which limit checking is disabled"), AP_INIT_ITERATE("OnlyIPLimit", excl_limit_config_cmd, NULL, OR_LIMIT, "restrict limit checking to these MIME types only"), {NULL}, }; /* Set up startup-time initialization */ static int limitipconn_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, MODULE_NAME " " MODULE_VERSION " started."); ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit); return OK; } static void register_hooks(apr_pool_t *p) { ap_hook_access_checker(limitipconn_handler,NULL,NULL,APR_HOOK_MIDDLE); ap_hook_post_config(limitipconn_init, NULL, NULL, APR_HOOK_MIDDLE); } module AP_MODULE_DECLARE_DATA limitipconn_module = { STANDARD20_MODULE_STUFF, limitipconn_create_dir_config, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ limitipconn_cmds, /* table of config file commands */ register_hooks };