/* * 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 "scoreboard.h" #define MODULE_NAME "mod_limitipconn" #define MODULE_VERSION "0.05" module MODULE_VAR_EXPORT limitipconn_module; typedef struct { unsigned int limit; /* max number of connections per IP */ array_header *no_limit; /* array of MIME types exempt from limit checking */ array_header *excl_limit; /* array of MIME types to limit check; all other types are exempt */ } limitipconn_dir_config; static void *limitipconn_create_dir_config(pool *p, char *path) { limitipconn_dir_config *cfg = (limitipconn_dir_config *) ap_pcalloc(p, sizeof (*cfg)); /* default configuration: no limit, and both arrays are empty */ cfg->limit = 0; cfg->no_limit = ap_make_array(p, 0, sizeof(char *)); cfg->excl_limit = ap_make_array(p, 0, sizeof(char *)); return (void *) cfg; } static int is_limitable(limitipconn_dir_config *cfg, request_rec *r, char *uri) { /* Content-type of the current request */ const char *content_type; /* convert Apache arrays to normal C arrays */ char **nolim = (char **) cfg->no_limit->elts; char **exlim = (char **) cfg->excl_limit->elts; /* loop index variable */ int i; /* 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(uri, r)->content_type; /* If there's no Content-type, use the default. */ if (!content_type) { content_type = ap_default_type(r); } /* Cycle through the exempt list; if our content_type is exempt, * return 0 */ 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 0; } } /* 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 0; } } return 1; } 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); const char *address; /* loop index variable */ int i; /* running count of number of connections from this address */ int ip_count = 0; /* scoreboard data structure */ short_score score_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; } #ifdef RECORD_FORWARD if ((address = ap_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; } /* If the content-type isn't limitable, return OK */ if (!is_limitable(cfg, r, r->uri)) { return OK; } /* Count up the number of connections we are handling right now from * this IP address */ for (i = 0; i < HARD_SERVER_LIMIT; ++i) { score_record = ap_scoreboard_image->servers[i]; switch (score_record.status) { case SERVER_BUSY_READ: case SERVER_BUSY_WRITE: case SERVER_BUSY_KEEPALIVE: case SERVER_BUSY_DNS: case SERVER_GRACEFUL: if ((strcmp(address, score_record.client) == 0) #ifdef RECORD_FORWARD || (strcmp(address, score_record.fwdclient) == 0) #endif ) { /* Try to determine the URI from what's stored of the request in the scoreboard. Hopefully we'll get enough of it to be able to determine the content-type. */ /* A copy of the HTTP request from the scoreboard */ char request[64]; /* The request's URI */ char *uri; /* A temporary pointer used to find the end of the URI */ char *c; /* One if we were unable to find a full URI in the request, zero otherwise */ int full_request = 0; /* Get a copy of the request line from the scoreboard */ strncpy(request, score_record.request, 64); request[63] = 0; /* Separate out the method */ for (uri = request; *uri; uri++) { if (*uri == ' ') { uri++; break; } } /* Find the space which separates the URI from the HTTP version */ for (c = uri; *c; c++) { if (*c == ' ') { *c = 0; full_request = 1; break; } } /* If we don't see the full request string, then be stringy and assume that the request is limitable. This was the behavior before the module was modified to try to be smarter. */ if (*uri && !full_request || is_limitable(cfg, r, uri)) { ip_count++; } } break; case SERVER_DEAD: case SERVER_READY: case SERVER_STARTING: case SERVER_BUSY_LOG: break; } } if ((ip_count > cfg->limit) && (cfg->limit)) { #ifdef RECORD_FORWARD ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "Rejecting client at %s", address); #endif ap_log_reason("Client exceeded connection limit.", r->uri, r); /* set an environment variable */ ap_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; unsigned long int limit = strtol(arg, (char **) NULL, 10); if (limit == LONG_MAX) { 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 **) ap_push_array(cfg->no_limit) = ap_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 **) ap_push_array(cfg->excl_limit) = ap_pstrdup(parms->pool, arg); return NULL; } /* Array describing structure of configuration directives */ static command_rec limitipconn_cmds[] = { {"MaxConnPerIP", limit_config_cmd, NULL, OR_LIMIT, TAKE1, "maximum simultaneous connections per IP address"}, {"NoIPLimit", no_limit_config_cmd, NULL, OR_LIMIT, ITERATE, "MIME types for which limit checking is disabled"}, {"OnlyIPLimit", excl_limit_config_cmd, NULL, OR_LIMIT, ITERATE, "restrict limit checking to these MIME types only"}, {NULL}, }; /* Emit an informational-level log message on startup */ static void limitipconn_init(server_rec *s, pool *p) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s, MODULE_NAME " " MODULE_VERSION " started."); } module MODULE_VAR_EXPORT limitipconn_module = { STANDARD_MODULE_STUFF, limitipconn_init, /* module initializer */ 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 */ 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_ */ limitipconn_handler, /* [#3] check access by host address */ NULL, /* [#6] determine MIME type */ NULL, /* [#7] pre-run fixups */ NULL, /* [#9] log a transaction */ NULL, /* [#2] header parser */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* [#0] post read-request */ };