--- /dev/null
+/* ====================================================================
+ * 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/>.
+ *
+ */
+
+/****************************************************************************
+ * Title : Bandwidth management
+ * File : mod_bandwidth.c
+ * Author : Yann Stettler (stettler@cohprog.com)
+ * Date : 17 July 1999
+ * Version : 2.0.3 for Apache 1.3+
+ *
+ * Description :
+ * Provide bandwidth usage limitation either on the whole server or
+ * one a per connection basis based on the size of files, directory
+ * location or remote domain/IP.
+ *
+ * Revision : 08/04/97 - "-1" value for LargeFileLimit to disable any
+ * limits for that kind of files.
+ * 01/26/98 - Include in_domain() and in_ip() in this file and
+ * make them static so it will work with Apache 1.3x
+ * 03/29/98 - Change to set bandwidth in VirtualHost directive
+ * 07/17/99 - Minor changes to remove warnings at compil time
+ *
+ * Allow the use of the network/mask format when
+ * setting a bandwidth for a remote host/network.
+ * (Thanks to Sami Kuhmonen for the patch)
+ *
+ * New directive BandWidthPulse
+ *
+ * 10/14/00 - Minor bug fixed
+ * 12/15/00 - Bug fix when using mmap
+ * 08/29/01 - Add a directive to change the data directory
+ * (Thanks to Awesome Walrus <walrus@amur.ru> )
+ *
+ ***************************************************************************
+ * Copyright (c)1997 Yann Stettler and CohProg SaRL. All rights reserved.
+ * Written for the Apache Group by :
+ *
+ * Yann Stettler
+ * stettler@cohprog.com
+ * http://www.cohprog.com/
+ * http://www.animanga.com/
+ *
+ * Based on the original default_handler module and on bw_module 0.2 from
+ * Etienne BERNARD (eb@via.ecp.fr) at VIA Centrale Reseaux, ECP, France
+ *
+ * Many thanks to Colba Net Inc (Montreal) for their sponsoring of
+ * improvements to this module.
+ *
+ ***************************************************************************/
+
+
+/*
+ * Instruction :
+ * -------------
+ *
+ * Note : this module was writen for Apache 1.3.x and tested on it
+ *
+ * Installation :
+ *
+ * 1) Insert the following line at the end of the "Configuration" file :
+ * Module bandwidth_module mod_bandwidth.o
+ *
+ * WARNING : This behaviour is not the same between the various main versions
+ * of Apache. Please, read the instruction on our website for the
+ * instructions concerning the latest release :
+ * http://www.cohprog.com/v3/bandwidth/doc-en.html
+ *
+ * 2) Run the "Configure" program and re-compile the server with "make".
+ *
+ * 3) Create the following directories with "rwx" permission to everybody :
+ * (or rwx for the user under which Apache run : Usualy "nobody")
+ * /tmp/apachebw
+ * /tmp/apachebw/link
+ * /tmp/apachebw/master
+ *
+ * /==== by Awesome Walrus <walrus@amur.ru> =====================\
+ * Or you may change this name by using BandWidthDataDir global
+ * configuration directive. See below for details.
+ * \==== by Awesome Walrus <walrus@amur.ru> =====================/
+ *
+ * Note that if any of those directories doesn't exist, or if they can't
+ * be accessed by the server, the module is totaly disabled except for
+ * logging an error message in the logfile.
+ *
+ * Be careful that on some systems the content of the /tmp directory
+ * is deleted at boot time or every so often by a cronjob. If that the
+ * case, either disable this feature or change the location of the
+ * directories used by the module in the sources bellow.
+ *
+ * Server configuration directive :
+ * --------------------------------
+ *
+ * /==== by Awesome Walrus <walrus@amur.ru> =====================\
+ * - BandWidthDataDir
+ * Syntax : BandWidthDataDir <directory>
+ * Default : "/tmp/apachebw"
+ * Context : server config
+ *
+ * Sets the name of the directory used by mod_bandwidth to store
+ * its internal temporary information.
+ * \==== by Awesome Walrus <walrus@amur.ru> =====================/
+ *
+ * - BandWidthModule
+ * Syntax : BandWidthModule <On|Off>
+ * Default : Off
+ * Context : per server config
+ *
+ * Enable or disable totaly the whole module. By default, the module is
+ * disable so it is safe to compile it in the server anyway.
+ *
+ * PLEASE, NOTE THAT IF YOU SET A BANDWIDTH LIMIT INSIDE A VIRTUALHOST
+ * BLOCK, YOU ALSO __NEED__ TO PUT THE "BandWidthModule On" DIRECTIVE
+ * INSIDE THAT VIRTUALHOST BLOCK !
+ *
+ * IF YOU SET BANDWIDTH LIMITS INSIDE DIRECTORY BLOCKS (OUTSIDE OF
+ * ANY VIRTUALHOST BLOCK), YOU ONLY NEED TO PUT THE "BandWidthModule On"
+ * DIRECTIVE ONCE, OUTSIDE OF ANY VIRTUALHOST OR DIRECTORY BLOCK.
+ *
+ * - BandWidthPulse
+ * Syntax : BandWidthPulse <microseconds>
+ * Default :
+ * Context : per server config
+ *
+ * Change the algorithm used to calculate bandwidth and transmit data.
+ * In normal mode (old mode), the module try to transmit data in packets
+ * of 1KB. That mean that if the bandwidth available is of 512B, the
+ * module will transmit 1KB, wait 2 seconds, transmit another 1KB and
+ * so one.
+ *
+ * Seting a value with "BandWidthPulse", will change the algorithm so
+ * that the server will always wait the same amount of time between
+ * sending packets but the size of the packets will change.
+ * The value is in microseconds.
+ * For example, if you set "BandWidthPulse 1000000" (1 sec) and the
+ * bandwidth available is of 512B, the sever will transmit 512B,
+ * wait 1 second, transmit 512B and so on.
+ *
+ * The advantage is a smother flow of data. The disadvantage is
+ * a bigger overhead of data transmited for packet header.
+ * Setting too small a value (bellow 1/5 of a sec) is not realy
+ * useful and will put more load on the system and generate more
+ * traffic for packet header.
+ *
+ * Note also that the operating system may do some buffering on
+ * it's own and so defeat the purpose of setting small values.
+ *
+ * This may be very useful on especialy crowded network connection :
+ * In normal mode, several seconds may happen between the sending of
+ * a full packet. This may lead to timeout or people may believe that
+ * the connection is hanging. Seting a value of 1000000 (1 sec) would
+ * guarantee that some data are sent every seconds...
+ *
+ * Directory / Server / Virtual Server configuration directive :
+ * -------------------------------------------------------------
+ *
+ * - BandWidth
+ * Syntax : BandWidth <domain|ip|all> <rate>
+ * Default : none
+ * Context : per directory, .htaccess
+ *
+ * Limit the bandwidth for files in this directory and
+ * sub-directories based on the remote host <domain> or
+ * <ip> address or for <all> remote hosts.
+ *
+ * Ip addresses may now be specified in the network/mask format.
+ * (Ie: 192.168.0.0/21 )
+ *
+ * The <rate> is in Bytes/second.
+ * A <rate> of "0" means no bandwidth limit.
+ *
+ * Several BandWidth limits can be set for the same
+ * directory to set different limits for different
+ * hosts. In this case, the order of the "BandWidth"
+ * keywords is important as the module will take the
+ * first entry which matches the client address.
+ *
+ * Example :
+ * <Directory /home/www>
+ * BandWidth ecp.fr 0
+ * BandWidth 138.195 0
+ * BandWidth all 1024
+ * </Directory>
+ *
+ * This will limit the bandwith for directory /home/www and
+ * all it's subdirectories to 1024Bytes/sec, except for
+ * *.ecp.fr or 138.195.*.* where no limit is set.
+ *
+ * - LargeFileLimit
+ * Syntax : LargeFileLimit <filesize> <rate>
+ * Default : none
+ * Context : per directory, .htaccess
+ *
+ * Set a maximal <rate> (in bytes/sec) to use when transfering
+ * a file of <filesize> KBytes or more.
+ *
+ * Several "LargeFileLimit" can be set for various files sizes
+ * to create range. The rate used for a given file size will be
+ * the one of the matching range.
+ *
+ * A <rate> of "0" mean that there isn't any limit based on
+ * the size.
+ *
+ * A <rate> of "-1" mean that there isn't any limit for that type
+ * of file. It's override even a BandWidth limit. I found this usefull
+ * to give priority to very small files (html pages, very small pictures)
+ * while seting limits for larger files... (users with their video files
+ * can go to hell ! :)
+ *
+ * Example :
+ * If the following limits are set :
+ * LargeFileLimit 200 3072
+ * LargeFileLimit 1024 2048
+ *
+ * That's mean that a file of less than 200KBytes won't be
+ * limited based on his size. A file with a size between
+ * 200KBytes (included) and 1023Kbytes (included) will be
+ * limited to 3072Bytes/sec and a file of 1024Kbytes or more
+ * will be limited to 2048Bytes/sec.
+ *
+ * - MinBandWidth
+ * Syntax : MinBandWidth <domain|ip|all> <rate>
+ * Default : all 256
+ * Context : per directory, .htaccess
+ *
+ * Set a minimal bandwidth to use for transfering data. This
+ * over-ride both BandWidth and LargeFileLimit rules as well
+ * as the calculated rate based on the number of connections.
+ *
+ * The first argument is used in the same way as the first
+ * argument of BandWidth.
+ *
+ * <rate> is in bytes per second.
+ *
+ * A rate of "0" explicitly means to use the default minimal
+ * value (256 Bytes/sec).
+ *
+ * A rate of "-1" means that the minimal rate is equal to the
+ * actual rate defined by BandWidth and LargeFileLimit.
+ * In fact, that means that the final rate won't depend
+ * of the number of connections but only on what was defined.
+ *
+ * Example :
+ * If BandWidth is set to "3072" (3KBytes/sec) and MinBandWidth
+ * is set to "1024" (1KBytes/sec) that means :
+ * - if there is one connection, the file will be transfered
+ * at 3072 Bytes/sec.
+ * - if there is two connections, each files will be transfered
+ * at 1536 Bytes/sec.
+ * - if there is three or more connections, each files will be
+ * transfered at 1024 Bytes/sec. (Minimal of 1024 Bytes/sec).
+ *
+ * If MinBandWidth is set to "-1" that means :
+ * - if there is one connection, the file will be transfered
+ * at 3072 Bytes/sec.
+ * - if there is two or more connections, each files will be
+ * transfered at 3072 Bytes/sec. In effect, the rate doesn't
+ * depend anymore on the number of connections but only on
+ * the configuration values.
+ *
+ * Note that the total transfer rate will never exceed your physical
+ * bandwidth limitation.
+ *
+ * Note : If both a "BandWidth" and a "LargeFileLimit" limit apply,
+ * the lowest one will be used. (But never lower than the
+ * "MinBandWidth" rate)
+ *
+ * If both a virtual server limit is defined and another
+ * apply for a directory under this virtual server, the
+ * directory limit will over-ride it.
+ *
+ * If a limit is defined outside a Directory or VirtualHost
+ * directive, it will act as default on a per virtual server
+ * basis. (Ie: each virtual server will have that limit,
+ * _independantly_ of the other servers)
+ *
+ * Implementation notes :
+ * ----------------------
+ *
+ * 1) This module isn't called when serving a cgi-script. In this
+ * case, it's the functions in mod_cgi.c that handle the connection.
+ *
+ * That's not a bug : I didn't want to change the cgi_module and
+ * I was too lazy to do it anyway.
+ *
+ * 2) The rate of data transmission is only calculated. It isn't
+ * measured. Which mean that this module calculates the final
+ * rate that should apply for a file and simply divides this
+ * number by the number of actual connections subject to the
+ * same "per directory" directives.
+ *
+ * 3) On the "+" side, the module regulate the speed taking
+ * into account the actual time that was needed to send the
+ * data. Which also mean that if data are read by the client
+ * at a slowest rate than the limit, no time will be lost and
+ * data will be sent as fast as the client can read them (but
+ * no faster than the limited rate :) ...
+ *
+ * 4) Some kind of buffering is done as side effect. Data are
+ * sent in packet of 1024 Bytes which seems a good value
+ * for TCP/IP.
+ * If another packet size is wanted, change the value of
+ * "#define PACKET" in the codes bellow.
+ *
+ * 5) The default value for MinBandWidth is defined by :
+ * "#define MIN_BW_DEFAULT" (in Bytes/sec)
+ *
+ * 6) Don't define "BWDEBUG" for a production server :
+ * this would log a _lot_ of useless informations for
+ * debuging purpose.
+ *
+ */
+
+#include <time.h>
+#include "httpd.h"
+#include "http_core.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "http_main.h"
+
+#define MIN_BW_DEFAULT 256 /* Minimal bandwidth defaulted to 256Bps */
+#define PACKET 1024 /* Sent packet of 1024 bytes */
+
+/* #define MASTER_DIR "/tmp/apachebw/master"
+ * #define LINK_DIR "/tmp/apachebw/link"
+ */
+
+#define MASTER_DIR "master"
+#define LINK_DIR "link"
+
+/* Define BWDEBUG for debuging purpose only ! */
+/* #define BWDEBUG */
+#ifdef BWDEBUG
+#undef BWDEBUG
+#endif
+
+#define BANDWIDTH_DISABLED 1<<0
+#define BANDWIDTH_ENABLED 1<<1
+
+#ifdef USE_MMAP_FILES
+#include <unistd.h>
+#include <sys/mman.h>
+
+/* mmap support for static files based on ideas from John Heidemann's
+ * patch against 1.0.5. See
+ * <http://www.isi.edu/~johnh/SOFTWARE/APACHE/index.html>.
+ */
+
+/* Files have to be at least this big before they're mmap()d. This is to
+ * deal with systems where the expense of doing an mmap() and an munmap()
+ * outweighs the benefit for small files.
+ */
+#ifndef MMAP_THRESHOLD
+#ifdef SUNOS4
+#define MMAP_THRESHOLD (8*1024)
+#else
+#define MMAP_THRESHOLD 0
+#endif
+#endif
+#endif
+
+/* Limits for bandwidth and minimal bandwidth based on directory */
+typedef struct {
+ char *from;
+ long rate;
+} bw_entry;
+
+/* Limits for bandwidth based on file size */
+typedef struct {
+ long size;
+ long rate;
+} bw_sizel;
+
+/* Per directory configuration structure */
+typedef struct {
+ array_header *limits;
+ array_header *minlimits;
+ array_header *sizelimits;
+ char *directory;
+} bandwidth_config;
+
+/* Per server configuration structure */
+typedef struct {
+ int state;
+} bandwidth_server_config;
+
+int bw_handler (request_rec *);
+
+module bandwidth_module;
+
+static long int BWPulse=0;
+
+char bandwidth_data_dir[MAX_STRING_LEN]="/tmp/apachebw";
+
+/***************************************************************************
+ * Configuration functions *
+ ***************************************************************************/
+
+static void *create_bw_config(pool *p, char *path) {
+ bandwidth_config *new=
+ (bandwidth_config *) ap_palloc(p, sizeof(bandwidth_config));
+ new->limits=ap_make_array(p,20,sizeof(bw_entry));
+ new->minlimits=ap_make_array(p,20,sizeof(bw_entry));
+ new->sizelimits=ap_make_array(p,10,sizeof(bw_sizel));
+ new->directory=ap_pstrdup(p, path);
+ return (void *)new;
+}
+
+static void *create_bw_server_config(pool *p, server_rec *s) {
+ bandwidth_server_config *new;
+
+ new = (bandwidth_server_config *)ap_pcalloc(p, sizeof(bandwidth_server_config));
+
+ new->state = BANDWIDTH_DISABLED;
+
+ return (void *)new;
+}
+
+/***************************************************************************
+ * Directive functions *
+ ***************************************************************************/
+
+static const char *bandwidthmodule(cmd_parms *cmd, bandwidth_config *dconf, int flag) {
+ bandwidth_server_config *sconf;
+
+ sconf = (bandwidth_server_config *)ap_get_module_config(cmd->server->module_config, &bandwidth_module);
+ sconf->state = (flag ? BANDWIDTH_ENABLED : BANDWIDTH_DISABLED);
+
+ return NULL;
+}
+
+static const char *set_bandwidth_data_dir(cmd_parms *cmd, void *dummy, char *arg) {
+ arg = ap_os_canonical_filename(cmd->pool, arg);
+
+ if (!ap_is_directory(arg)) {
+ return "BandWidthDataDir must be a valid directory";
+ }
+ ap_cpystrn(bandwidth_data_dir, arg, sizeof(bandwidth_data_dir));
+ return NULL;
+}
+
+static const char *setpulse(cmd_parms *cmd, bandwidth_config *dconf, char *pulse) {
+ long int temp;
+
+ if (pulse && *pulse && isdigit(*pulse))
+ temp = atol(pulse);
+ else
+ return "Invalid argument";
+
+ if (temp<0)
+ return "BandWidth must be a number of bytes/s";
+
+ BWPulse=temp;
+
+ return NULL;
+}
+
+static const char *bandwidth(cmd_parms *cmd, void *s, char *from, char *bw) {
+ bandwidth_config *conf=(bandwidth_config *)s;
+ bw_entry *a;
+ long int temp;
+
+ if (bw && *bw && isdigit(*bw))
+ temp = atol(bw);
+ else
+ return "Invalid argument";
+
+ if (temp<0)
+ return "BandWidth must be a number of bytes/s";
+
+ a = (bw_entry *)ap_push_array(conf->limits);
+ a->from = ap_pstrdup(cmd->pool,from);
+ a->rate = temp;
+ return NULL;
+}
+
+static const char *minbandwidth(cmd_parms *cmd, void *s, char *from, char *bw) {
+ bandwidth_config *conf=(bandwidth_config *)s;
+ bw_entry *a;
+ long int temp;
+
+ if (bw && *bw && (*bw=='-' || isdigit(*bw)))
+ temp = atol(bw);
+ else
+ return "Invalid argument";
+
+ a = (bw_entry *)ap_push_array(conf->minlimits);
+ a->from = ap_pstrdup(cmd->pool,from);
+ a->rate = temp;
+ return NULL;
+}
+
+static const char *largefilelimit(cmd_parms *cmd, void *s, char *size, char *bw) {
+ bandwidth_config *conf=(bandwidth_config *)s;
+ bw_sizel *a;
+ long int temp, tsize;
+
+ if (bw && *bw && (*bw=='-' || isdigit(*bw)))
+ temp = atol(bw);
+ else
+ return "Invalid argument";
+
+ if (size && *size && isdigit(*size))
+ tsize = atol(size);
+ else
+ return "Invalid argument";
+
+ /*
+ if (temp<0)
+ return "BandWidth must be a number of bytes/s";
+ */
+
+ if (tsize<0)
+ return "File size must be a number of Kbytes";
+
+ a = (bw_sizel *)ap_push_array(conf->sizelimits);
+ a->size = tsize;
+ a->rate = temp;
+ return NULL;
+}
+
+static command_rec bw_cmds[] = {
+{ "BandWidth", bandwidth, NULL, RSRC_CONF | OR_LIMIT, TAKE2,
+ "a domain (or ip, or all for all) and a bandwidth limit (in bytes/s)" },
+{ "MinBandWidth", minbandwidth, NULL, RSRC_CONF | OR_LIMIT, TAKE2,
+ "a domain (or ip, or all for all) and a minimal bandwidth limit (in bytes/s)" },
+{ "LargeFileLimit", largefilelimit, NULL, RSRC_CONF | OR_LIMIT, TAKE2,
+ "a filesize (in Kbytes) and a bandwidth limit (in bytes/s)" },
+{ "BandWidthModule", bandwidthmodule, NULL, OR_FILEINFO, FLAG,
+ "On or Off to enable or disable (default) the whole bandwidth module" },
+{ "BandWidthPulse", setpulse, NULL, OR_FILEINFO, TAKE1,
+ "a number of microseconds" },
+{ "BandWidthDataDir", set_bandwidth_data_dir, NULL, RSRC_CONF, TAKE1,
+ "A writable directory where temporary bandwidth info is to be stored" },
+{ NULL }
+};
+
+/***************************************************************************
+ * Internal functions *
+ ***************************************************************************/
+
+#ifdef USE_MMAP_FILES
+struct mmap {
+ void *mm;
+ size_t length;
+};
+
+static void mmap_cleanup (void *mmv)
+{
+ struct mmap *mmd = mmv;
+
+ munmap(mmd->mm, mmd->length);
+}
+#endif
+
+static int in_domain(const char *domain, const char *what) {
+ int dl=strlen(domain);
+ int wl=strlen(what);
+
+ if((wl-dl) >= 0) {
+ if (strcasecmp(domain,&what[wl-dl]) != 0) return 0;
+
+ /* Make sure we matched an *entire* subdomain --- if the user
+ * said 'allow from good.com', we don't want people from nogood.com
+ * to be able to get in.
+ */
+
+ if (wl == dl) return 1; /* matched whole thing */
+ else return (domain[0] == '.' || what[wl - dl - 1] == '.');
+ } else
+ return 0;
+}
+
+static int in_ip(char *domain, char *what) {
+
+ /* Check a similar screw case to the one checked above ---
+ * "allow from 204.26.2" shouldn't let in people from 204.26.23
+ */
+ int a, b, c, d, e;
+ unsigned long in, out, inmask, outmask;
+
+ if (sscanf(domain, "%i.%i.%i.%i/%i", &a, &b, &c, &d, &e) < 5) {
+ int l = strlen(domain);
+ if (strncmp(domain,what,l) != 0) return 0;
+ if (domain[l - 1] == '.') return 1;
+ return (what[l] == '\0' || what[l] == '.');
+ }
+ in = (a<<24)|(b<<16)|(c<<8)|d;
+ inmask = 0xFFFFFFFF ^ ((1<<(32-e))-1);
+
+ if (sscanf(what, "%i.%i.%i.%i", &a, &b, &c, &d) < 4)
+ return 0;
+
+ out = (a<<24)|(b<<16)|(c<<8)|d;
+
+ return ((in&inmask) == (out&inmask));
+}
+
+static int is_ip(const char *host)
+{
+ while ((*host == '.') || (*host=='/') || isdigit(*host))
+ host++;
+ return (*host == '\0');
+}
+
+static long get_bw_rate(request_rec *r, array_header *a) {
+ bw_entry *e = (bw_entry *)a->elts;
+ const char *remotehost = NULL;
+ int i;
+
+ remotehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_HOST);
+
+ for (i=0; i< a->nelts; i++) {
+ if (!strcmp(e[i].from,"all"))
+ return e[i].rate;
+
+ if (in_ip(e[i].from, r->connection->remote_ip))
+ return e[i].rate;
+ if ((remotehost!=NULL) && !is_ip(remotehost)) {
+ if (in_domain(e[i].from,remotehost))
+ return e[i].rate;
+ }
+ }
+ return 0;
+}
+
+static long get_bw_filesize(request_rec *r, array_header *a, off_t filesize) {
+ bw_sizel *e = (bw_sizel *)a->elts;
+ int i;
+ long int tmpsize=0, tmprate=0;
+
+ if (!filesize)
+ return(0);
+
+ filesize /= 1024;
+
+ for (i=0; i< a->nelts; i++) {
+ if (e[i].size <= filesize )
+ if (tmpsize <= e[i].size) {
+ tmpsize=e[i].size;
+ tmprate=e[i].rate;
+ }
+ }
+
+ return(tmprate);
+}
+
+static int current_connection(char *filename) {
+ struct stat mdata;
+
+ /*
+ * Here, we will do the main work. And that won't take
+ * long !
+ */
+
+ if (stat(filename, &mdata) < 0)
+ return(1); /* strange... should not occure */
+
+ return(mdata.st_nlink-1);
+
+ /*
+ * I told you it wouldn't be long... :)
+ */
+}
+
+static struct timeval timediff(struct timeval *a, struct timeval *b)
+{
+ struct timeval rslt, tmp;
+
+ tmp=*a;
+
+ if ((rslt.tv_usec = tmp.tv_usec - b->tv_usec) < 0) {
+ rslt.tv_usec += 1000000;
+ --(tmp.tv_sec);
+ }
+ if ((rslt.tv_sec = tmp.tv_sec - b->tv_sec) < 0) {
+ rslt.tv_usec=0;
+ rslt.tv_sec=0;
+ }
+ return(rslt);
+}
+
+/***************************************************************************
+ * Main function, module core *
+ ***************************************************************************/
+
+static int handle_bw(request_rec *r) {
+ int rangestatus, errstatus;
+ FILE *f;
+ bandwidth_config *conf =
+ (bandwidth_config *)ap_get_module_config(r->per_dir_config, &bandwidth_module);
+ bandwidth_server_config *sconf =
+ (bandwidth_server_config *)ap_get_module_config(r->server->module_config, &bandwidth_module);
+ long int bw_rate=0, bw_min=0, bw_f_rate=0, cur_rate;
+ int bwlast=0, fd;
+ long int tosend, bytessent, filelength;
+ struct stat fdata;
+ struct timeval opt_time, last_time, now, timespent, timeout;
+ char masterfile[128], filelink[128], *directory;
+
+#ifdef USE_MMAP_FILES
+ caddr_t mm;
+#endif
+
+ /* This handler has no use for a request body (yet), but we still
+ * need to read and discard it if the client sent one.
+ */
+ if ((errstatus = ap_discard_request_body(r)) != OK)
+ return errstatus;
+
+ /* Return as fast as possible if request is not a GET or if module not
+ enabled */
+ if (r->method_number != M_GET || sconf->state == BANDWIDTH_DISABLED)
+ return DECLINED;
+
+ if (!conf->directory) {
+ directory=(char *)ap_document_root(r);
+ } else {
+ directory=conf->directory;
+ }
+
+ bw_rate=get_bw_rate(r,conf->limits);
+ bw_min=get_bw_rate(r, conf->minlimits);
+ bw_f_rate=get_bw_filesize(r, conf->sizelimits , r->finfo.st_size);
+
+ if ((bw_rate==0 && bw_f_rate==0) || bw_f_rate < 0 || !directory) return DECLINED;
+
+ if (r->finfo.st_mode == 0 || (r->path_info && *r->path_info)) {
+ ap_log_reason("File does not exist", r->filename, r);
+ return NOT_FOUND;
+ }
+
+ ap_update_mtime (r, r->finfo.st_mtime);
+ ap_set_last_modified(r);
+ ap_set_etag(r);
+ if (((errstatus = ap_meets_conditions(r)) != OK)
+ || (errstatus = ap_set_content_length (r, r->finfo.st_size))) {
+ return errstatus;
+ }
+
+ /*
+ * Create name of the master file.
+ * Will use the inode and device number of the "limited"
+ * directory.
+ */
+
+ if (stat(directory, &fdata) < -1) {
+ /* Dunno if this may happen... but well... */
+ return DECLINED;
+ }
+ sprintf(masterfile,"%s/%s/%d:%ld", bandwidth_data_dir, MASTER_DIR, (short int)fdata.st_dev, (long int)fdata.st_ino);
+
+ /*
+ * Check if master file already exist, else create it.
+ * Then we create a hardlink to it using the pid as name.
+ */
+ if ((fd=open(masterfile, O_RDONLY|O_CREAT, 0777)) < 0) {
+ ap_log_printf(r->server, "mod_bandwidth : Can't create/access master file %s",masterfile);
+ return DECLINED;
+ }
+ close(fd);
+
+ sprintf(filelink,"%s/%s/%d", bandwidth_data_dir, LINK_DIR, getpid());
+ if (link(masterfile, filelink) < 0) {
+ ap_log_printf(r->server, "mod_bandwidth : Can't create hard link %s", filelink);
+ return DECLINED;
+ }
+
+ f = ap_pfopen(r->pool, r->filename,"r");
+
+ if (f == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
+ "file permissions deny server access: %s", r->filename);
+ return FORBIDDEN;
+ }
+
+ /*
+ * Calculate bandwidth for this file based on location limits and
+ * file size limit.
+ *
+ * The following rules applies :
+ * 1) File size limit over-rule location limit if it's slower.
+ * 2) Whatever the resuling limit and number of connections, never
+ * go bellow the minimal limit for this location.
+ * 3) A file size limit of zero mean that we don't care about the
+ * size of the file for this purpose.
+ *
+ */
+
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Directory : %s Rate : %d Minimum : %d Size rate : %d", directory, bw_rate, bw_min, bw_f_rate);
+#endif
+
+ if (bw_f_rate && (bw_rate > bw_f_rate || !bw_rate))
+ bw_rate=bw_f_rate;
+
+ if (bw_min < 0)
+ bw_min=bw_rate;
+ else if (! bw_min)
+ bw_min=MIN_BW_DEFAULT;
+
+#ifdef USE_MMAP_FILES
+ ap_block_alarms();
+ if ((r->finfo.st_size >= MMAP_THRESHOLD)
+ && ( !r->header_only)) {
+ /* we need to protect ourselves in case we die while we've got the
+ * file mmapped */
+ mm = mmap (NULL, r->finfo.st_size, PROT_READ, MAP_PRIVATE,
+ fileno(f), 0);
+ } else {
+ mm = (caddr_t)-1;
+ }
+
+ if (mm == (caddr_t)-1) {
+ ap_unblock_alarms();
+
+ if (r->finfo.st_size >= MMAP_THRESHOLD) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, r->server,
+ "mmap_handler: mmap failed: %s", r->filename);
+ }
+#endif
+
+ rangestatus = ap_set_byterange(r);
+ ap_send_http_header(r);
+
+ if (!r->header_only) {
+ if (!rangestatus) {
+ filelength=r->finfo.st_size;
+ bytessent=0;
+ bwlast=0;
+ while(!bwlast) {
+
+ cur_rate=(long int)bw_rate / current_connection(masterfile);
+ if (cur_rate < bw_min)
+ cur_rate=bw_min;
+
+ if (BWPulse <= 0) {
+ /*
+ * I feel rather foolish to use select and use the
+ * 1/1000000 of sec for my calculation. But as long as I
+ * am at it, let's do it well...
+ *
+ * Note that my loop don't take into account the time
+ * spent outside the send_fd_length function... but in
+ * this case, I feel that I have the moral right to do so :)
+ */
+
+ opt_time.tv_sec=(long int) PACKET / cur_rate;
+ opt_time.tv_usec=(long int)PACKET*1000000/cur_rate-opt_time.tv_sec*1000000;
+
+ tosend=PACKET;
+ if (tosend+bytessent >= filelength) {
+ tosend=filelength-bytessent;
+ bwlast=1;
+ }
+ } else {
+ opt_time.tv_sec=(long int)BWPulse/1000000;
+ opt_time.tv_usec=BWPulse-opt_time.tv_sec*1000000;
+
+ tosend=(long int)((double)BWPulse/(double)1000000*(double)cur_rate);
+ if (tosend+bytessent >= filelength) {
+ tosend=filelength-bytessent;
+ bwlast=1;
+ }
+ }
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Current rate : %d [%d/%d] Min : %d",
+ cur_rate, bw_rate, current_connection(masterfile), bw_min);
+#endif
+
+ gettimeofday(&last_time, (struct timezone *) 0);
+
+ ap_send_fd_length(f, r, tosend);
+ bytessent += tosend;
+ if (r->connection->aborted)
+ break;
+ if (!bwlast) {
+ /* We sleep... */
+ gettimeofday(&now, (struct timezone *) 0);
+ timespent=timediff(&now, &last_time);
+ timeout=timediff(&opt_time, ×pent);
+
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Sleep : %ld/%ld (Op time : %ld/%ld Spent : %ld/%ld)",
+ timeout.tv_sec, timeout.tv_usec, opt_time.tv_sec, opt_time.tv_usec,
+ timespent.tv_sec, timespent.tv_usec);
+#endif
+
+ select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);
+ }
+ }
+ } else {
+ long offset, length;
+ while (ap_each_byterange(r, &offset, &length)) {
+ fseek(f, offset, SEEK_SET);
+
+ filelength=length;
+ bytessent=0;
+ bwlast=0;
+ while(!bwlast) {
+
+ cur_rate=(long int)bw_rate / current_connection(masterfile);
+ if (cur_rate < bw_min)
+ cur_rate=bw_min;
+
+ if (BWPulse <= 0) {
+ opt_time.tv_sec=(long int) PACKET / cur_rate;
+ opt_time.tv_usec=(long int)PACKET*1000000/cur_rate-opt_time.tv_sec*1000000;
+
+ tosend=PACKET;
+ if (tosend+bytessent >= filelength) {
+ tosend=filelength-bytessent;
+ bwlast=1;
+ }
+ } else {
+ opt_time.tv_sec=(long int)BWPulse/1000000;
+ opt_time.tv_usec=BWPulse-opt_time.tv_sec*1000000;
+
+ tosend=(long int)((double)BWPulse/(double)1000000*(double)cur_rate);
+ if (tosend+bytessent >= filelength) {
+ tosend=filelength-bytessent;
+ bwlast=1;
+ }
+ }
+
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Current rate : %d [%d/%d] Min : %d",
+ cur_rate, bw_rate, current_connection(masterfile), bw_min);
+#endif
+ gettimeofday(&last_time, (struct timezone *) 0);
+
+ ap_send_fd_length(f, r, tosend);
+ bytessent += tosend;
+ if (r->connection->aborted)
+ break;
+ if (!bwlast) {
+ /* We sleep... */
+ gettimeofday(&now, (struct timezone *) 0);
+ timespent=timediff(&now, &last_time);
+ timeout=timediff(&opt_time, ×pent);
+
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Sleep : %ld/%ld (Op time : %ld/%ld Spent : %ld/%ld)",
+ timeout.tv_sec, timeout.tv_usec, opt_time.tv_sec, opt_time.tv_usec,
+ timespent.tv_sec, timespent.tv_usec);
+#endif
+ select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);
+ }
+ }
+ }
+ }
+ }
+
+#ifdef USE_MMAP_FILES
+ } else {
+ struct mmap *mmd;
+
+ mmd = ap_palloc (r->pool, sizeof (*mmd));
+ mmd->mm = mm;
+ mmd->length = r->finfo.st_size;
+ ap_register_cleanup (r->pool, (void *)mmd, mmap_cleanup, mmap_cleanup);
+ ap_unblock_alarms();
+
+ rangestatus = ap_set_byterange(r);
+ ap_send_http_header (r);
+
+ if (!r->header_only) {
+ if (!rangestatus) {
+ filelength=r->finfo.st_size;
+ bytessent=0;
+ bwlast=0;
+ while(!bwlast) {
+
+ cur_rate=(long int)bw_rate / current_connection(masterfile);
+ if (cur_rate < bw_min)
+ cur_rate=bw_min;
+
+ if (BWPulse <= 0) {
+ /*
+ * I feel rather foolish to use select and use the
+ * 1/1000000 of sec for my calculation. But as long as I
+ * am at it, let's do it well...
+ *
+ * Note that my loop don't take into account the time
+ * spent outside the send_fd_length function... but in
+ * this case, I feel that I have the moral right to do so :)
+ */
+
+ opt_time.tv_sec=(long int) PACKET / cur_rate;
+ opt_time.tv_usec=(long int)PACKET*1000000/cur_rate-opt_time.tv_sec*1000000;
+
+ tosend=PACKET;
+ if (tosend+bytessent >= filelength) {
+ tosend=filelength-bytessent;
+ bwlast=1;
+ }
+ } else {
+ opt_time.tv_sec=(long int)BWPulse/1000000;
+ opt_time.tv_usec=BWPulse-opt_time.tv_sec*1000000;
+
+ tosend=(long int)((double)BWPulse/(double)1000000*(double)cur_rate);
+ if (tosend+bytessent >= filelength) {
+ tosend=filelength-bytessent;
+ bwlast=1;
+ }
+ }
+
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Current rate : %d [%d/%d] Min : %d Sending : %d",
+ cur_rate, bw_rate, current_connection(masterfile), bw_min, tosend);
+#endif
+ gettimeofday(&last_time, (struct timezone *) 0);
+
+ ap_send_mmap(mm, r, bytessent, tosend);
+ bytessent += tosend;
+ if (r->connection->aborted)
+ break;
+ if (!bwlast) {
+ /* We sleep... */
+ gettimeofday(&now, (struct timezone *) 0);
+ timespent=timediff(&now, &last_time);
+ timeout=timediff(&opt_time, ×pent);
+
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Sleep : %ld/%ld (Op time : %ld/%ld Spent : %ld/%ld)",
+ timeout.tv_sec, timeout.tv_usec, opt_time.tv_sec, opt_time.tv_usec,
+ timespent.tv_sec, timespent.tv_usec);
+#endif
+ select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);
+ }
+ }
+ } else {
+ long offset, length;
+ while (ap_each_byterange(r, &offset, &length)) {
+
+ filelength=length;
+ bytessent=0;
+ bwlast=0;
+ while(!bwlast) {
+
+ cur_rate=(long int)bw_rate / current_connection(masterfile);
+ if (cur_rate < bw_min)
+ cur_rate=bw_min;
+
+ if (BWPulse <= 0) {
+ opt_time.tv_sec=(long int) PACKET / cur_rate;
+ opt_time.tv_usec=(long int)PACKET*1000000/cur_rate-opt_time.tv_sec*1000000;
+
+ tosend=PACKET;
+ if (tosend+bytessent >= filelength) {
+ tosend=filelength-bytessent;
+ bwlast=1;
+ }
+ } else {
+ opt_time.tv_sec=(long int)BWPulse/1000000;
+ opt_time.tv_usec=BWPulse-opt_time.tv_sec*1000000;
+
+ tosend=(long int)((double)BWPulse/(double)1000000*(double)cur_rate);
+ if (tosend+bytessent >= filelength) {
+ tosend=filelength-bytessent;
+ bwlast=1;
+ }
+ }
+
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Current rate : %d [%d/%d] Min : %d Sending : $d",
+ cur_rate, bw_rate, current_connection(masterfile), bw_min , tosend);
+#endif
+ gettimeofday(&last_time, (struct timezone *) 0);
+
+ ap_send_mmap(mm, r, offset+bytessent, tosend);
+ bytessent += tosend;
+ if (r->connection->aborted)
+ break;
+ if (!bwlast) {
+ /* We sleep... */
+ gettimeofday(&now, (struct timezone *) 0);
+ timespent=timediff(&now, &last_time);
+ timeout=timediff(&opt_time, ×pent);
+
+#ifdef BWDEBUG
+ ap_log_printf(r->server, "mod_bandwidth : Sleep : %ld/%ld (Op time : %ld/%ld Spent : %ld/%ld)",
+ timeout.tv_sec, timeout.tv_usec, opt_time.tv_sec, opt_time.tv_usec,
+ timespent.tv_sec, timespent.tv_usec);
+#endif
+ select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout);
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ ap_pfclose(r->pool, f);
+
+ /* Remove the hardlink */
+ unlink(filelink);
+
+ return OK;
+}
+
+static handler_rec bw_handlers[] = {
+{ "*/*", handle_bw },
+{ NULL }
+};
+
+module bandwidth_module = {
+ STANDARD_MODULE_STUFF,
+ NULL, /* initializer */
+ create_bw_config, /* bw config creater */
+ NULL, /* bw merger --- default is to override */
+ create_bw_server_config, /* server config */
+ NULL, /* merge server config */
+ bw_cmds, /* command table */
+ bw_handlers, /* handlers */
+ NULL, /* filename translation */
+ NULL, /* check_user_id */
+ NULL, /* check auth */
+ NULL, /* check access */
+ NULL, /* type_checker */
+ NULL, /* fixups */
+ NULL /* logger */
+};