/* ====================================================================
* Copyright (c) 1995-1998 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 .
*
*/
/*
* URL Counting
*
* Track all incoming requests for each URL. Maintains a database containing
* each URL accessed, count of times accessed, and last time counter has been
* reset. If the URL is a directory, then the file that it is redirected to
* is counted.
*
* The values for count of times accessed, and last time counter was reset are
* made available to the to the document via variables called URL_COUNT and
* URL_COUNT_RESET, respectively. Another variable URL_COUNT_DB is set to the
* database file used.
*
* Config file directives:
*
* CounterType `txt' or `dbm'
* CounterAutoAdd `on' or `off': Automatically add missing URL's
* CounterFile Path to ASCII or DBM file.
*
* The following pertain to a per server (or virtual server) configuration:
*
* ServerCounterType `txt' or `dbm'
* ServerCounterAutoAdd `on' or `off': Automatically add missing URL's
* ServerCounterFile path to ASCII or DBM file.
*
* ASCII files have the format: URL ### date
*
* Note: The counter files are open/closed for each request to allow
* URLS to be managed by external programs without shutting down the
* web server.
*
* Originally written 1-14-95
* by Brian Kolaci
* bk@galaxy.net
*
* Ported to Apache 1.2b8 16-04-1997,
* to Apache 1.3b4-dev 02-01-1998,
* to Apache 1.3.1 02-Sep-1998
* by Ralf S. Engelschall
* rse@engelschall.com
* www.engelschall.com
*/
/*
* Module definition information - the part between the -START and -END
* lines below is used by Configure. This could be stored in a separate
* instead.
*
* MODULE-DEFINITION-START
* Name: urlcount_module
* ConfigStart
. ./helpers/find-dbm-lib
* ConfigEnd
* MODULE-DEFINITION-END
*/
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) \
&& __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
#include
#else
#include
#endif
module urlcount_module;
/*
* Data structions
*/
typedef enum {
CntrTxt,
CntrDbm
} CounterType;
typedef struct {
unsigned long count;
char *date;
} urlcount_results;
typedef struct {
int urlcount_default;
CounterType urlcount_type;
int urlcount_auto_add;
char *urlcount_file;
} urlcount_config_rec;
#define DEF_CNTRTYPE 1
#define DEF_CNTRAA 2
#define DEF_CNTRFILE 4
#define DEF_ALL (DEF_CNTRTYPE|DEF_CNTRAA|DEF_CNTRFILE)
/*
* Defaults
*/
#define DEFAULT_CNTR_TYPE CntrDbm
#define DEFAULT_TIME_FORMAT "%A, %d-%b-%y %T %Z"
/*
* File locking support
*/
#if defined(USE_FCNTL_SERIALIZED_ACCEPT)
#define USE_FCNTL 1
#include
#endif
#if defined(USE_FLOCK_SERIALIZED_ACCEPT)
#define USE_FLOCK 1
#include
#endif
#if !defined(USE_FCNTL) && !defined(USE_FLOCK)
#define USE_FLOCK 1
#ifndef MPE
#include
#endif
#ifndef LOCK_UN
#undef USE_FLOCK
#define USE_FCNTL 1
#include
#endif
#endif
#ifdef AIX
#undef USE_FLOCK
#define USE_FCNTL 1
#include
#endif
/*
* Create config data structures
*/
static void *urlcount_create_dir_config(pool *p, char *d)
{
urlcount_config_rec *rec =
(urlcount_config_rec *)ap_pcalloc(p, sizeof(urlcount_config_rec));
rec->urlcount_type = DEFAULT_CNTR_TYPE;
rec->urlcount_auto_add = 0;
rec->urlcount_file = NULL;
rec->urlcount_default = DEF_ALL;
return (rec);
}
static void *urlcount_create_srv_config(pool *p, server_rec *d)
{
urlcount_config_rec *rec =
(urlcount_config_rec *) ap_pcalloc(p, sizeof(urlcount_config_rec));
rec->urlcount_type = DEFAULT_CNTR_TYPE;
rec->urlcount_auto_add = 0;
rec->urlcount_file = NULL;
rec->urlcount_default = DEF_ALL;
return (rec);
}
static void *urlcount_merge_config(pool *p, void *parent, void *sub)
{
urlcount_config_rec *par = (urlcount_config_rec *)parent;
urlcount_config_rec *chld = (urlcount_config_rec *)sub;
urlcount_config_rec *mrg = (urlcount_config_rec *)ap_palloc(p, sizeof(*mrg));
if (chld->urlcount_default & DEF_CNTRTYPE)
mrg->urlcount_type = par->urlcount_type;
else
mrg->urlcount_type = chld->urlcount_type;
if (chld->urlcount_default & DEF_CNTRAA)
mrg->urlcount_auto_add = par->urlcount_auto_add;
else
mrg->urlcount_auto_add = chld->urlcount_auto_add;
if (chld->urlcount_default & DEF_CNTRFILE)
mrg->urlcount_file = par->urlcount_file;
else
mrg->urlcount_file = chld->urlcount_file;
mrg->urlcount_default = 0;
return (mrg);
}
static const char *set_urlcount_type(cmd_parms *cmd, void *ct, char *arg)
{
urlcount_config_rec *conf = (urlcount_config_rec *)ct;
if (strcasecmp(arg, "txt") == 0)
conf->urlcount_type = CntrTxt;
else if (strcasecmp(arg, "dbm") == 0)
conf->urlcount_type = CntrDbm;
else
return "CounterType must be `file' or `dbm'";
conf->urlcount_default &= ~DEF_CNTRTYPE;
return NULL;
}
static const char *set_urlcount_autoadd(cmd_parms *cmd, void *ct, int arg)
{
urlcount_config_rec *conf = (urlcount_config_rec *)ct;
conf->urlcount_auto_add = arg;
conf->urlcount_default &= ~DEF_CNTRAA;
return NULL;
}
static const char *set_urlcount_file(cmd_parms *cmd, void *ct, char *arg)
{
urlcount_config_rec *conf = (urlcount_config_rec *)ct;
if (strcmp(arg, "/dev/null") == 0)
conf->urlcount_file = NULL;
else
conf->urlcount_file = ap_server_root_relative(cmd->pool, arg);
conf->urlcount_default &= ~DEF_CNTRFILE;
return NULL;
}
static const char *set_svr_urlcount_type(cmd_parms *cmd, void *ct, char *arg)
{
urlcount_config_rec *conf =
ap_get_module_config(cmd->server->module_config, &urlcount_module);
return (set_urlcount_type(cmd, conf, arg));
}
static const char *set_svr_urlcount_autoadd(cmd_parms *cmd, void *ct, int arg)
{
urlcount_config_rec *conf =
ap_get_module_config(cmd->server->module_config, &urlcount_module);
return (set_urlcount_autoadd(cmd, conf, arg));
}
static const char *set_svr_urlcount_file(cmd_parms *cmd, void *ct, char *arg)
{
urlcount_config_rec *conf =
ap_get_module_config(cmd->server->module_config, &urlcount_module);
return (set_urlcount_file(cmd, conf, arg));
}
#ifdef USE_FCNTL
static struct flock lock_it;
static struct flock unlock_it;
#endif
static int fd_lock(int fd)
{
int rc;
#ifdef USE_FCNTL
lock_it.l_whence = SEEK_SET;
lock_it.l_start = 0;
lock_it.l_len = 0;
lock_it.l_type = F_WRLCK;
lock_it.l_pid = 0;
while (((rc = fcntl(fd, F_SETLKW, &lock_it)) < 0)
&& (errno == EINTR))
continue;
#endif
#ifdef USE_FLOCK
while (((rc = flock(fd, LOCK_EX)) < 0)
&& (errno == EINTR))
continue;
#endif
return rc;
}
static int fd_unlock(int fd)
{
int rc;
#ifdef USE_FCNTL
unlock_it.l_whence = SEEK_SET;
unlock_it.l_start = 0;
unlock_it.l_len = 0;
unlock_it.l_type = F_UNLCK;
unlock_it.l_pid = 0;
rc = fcntl(fd, F_SETLKW, &unlock_it);
#endif
#ifdef USE_FLOCK
rc = flock(fd, LOCK_UN);
#endif
return rc;
}
static char *urlcount_inc_txt(pool *p, urlcount_results *results,
urlcount_config_rec *r, char *uri)
{
char buf[MAX_STRING_LEN];
char buf2[MAX_STRING_LEN];
int found;
int len = strlen(uri);
int buflen = 0;
int buflen2 = 0;
FILE *fp;
long roffset;
long woffset;
long fsize;
/*
* Open counter file
*/
if ((fp = fopen(r->urlcount_file, "r+")) == NULL)
if ((fp = fopen(r->urlcount_file, "w+")) == NULL)
return (ap_pstrcat(p, "Failed to open counter TXT file: ",
r->urlcount_file, NULL));
if (fd_lock(fileno(fp))) {
fclose(fp);
return (ap_pstrcat(p, "Failed to lock counter TXT file: ",
r->urlcount_file, NULL));
}
/*
* Find end of file
*/
fseek(fp, 0, SEEK_END);
fsize = ftell(fp);
fseek(fp, 0, SEEK_SET);
/*
*/
roffset = 0;
woffset = 0;
found = 0;
while (roffset < fsize) {
roffset = ftell(fp);
fgets(buf, sizeof(buf), fp);
buflen = strlen(buf);
/*
* If we already found the URI, manage buffers
* to effectively "push" the strings lower in the file.
*/
if (found) {
roffset = ftell(fp);
/* Go back and write old buffer */
fseek(fp, woffset, SEEK_SET);
fwrite(buf2, buflen2, 1, fp);
woffset = ftell(fp);
/* Are we done ? */
if (roffset >= fsize) {
fwrite(buf, buflen, 1, fp);
break;
}
/* Save copy of last buffer */
strcpy(buf2, buf);
buflen2 = buflen;
/* Move offset back to read next line */
fseek(fp, roffset, SEEK_SET);
continue;
}
/*
* See if we found the matching URI
*/
if (strncmp(uri, buf, len) == 0 && isspace(buf[len])) {
char *ptr = &buf[len];
char *q = strchr(ptr, '\n');
if (q)
*q = '\0';
/* Skip spaces to number */
while (*ptr && isspace(*ptr))
ptr++;
results->count = atol(ptr) + 1;
/* Skip over number */
while (*ptr && !isspace(*ptr))
ptr++;
/* Skip spaces to date */
while (*ptr && isspace(*ptr))
ptr++;
results->date = ap_pstrdup(p, ptr);
found++;
ap_snprintf(buf2, sizeof(buf2), "%s\t%010lu\t%s\n",
uri, results->count, results->date);
/*
* If we don't increase the size of the buffer,
* we can write it out now and get out of the loop.
* If not, we must then "push" all of the items
* down in the file.
*/
buflen2 = strlen(buf2);
woffset = roffset;
if (buflen2 <= buflen) {
buflen2--;
while (buflen2 < buflen - 1)
buf2[buflen2++] = ' ';
buf2[buflen2++] = '\n';
buf2[buflen2++] = '\0';
fseek(fp, -buflen, SEEK_CUR);
fwrite(buf2, buflen, 1, fp);
break;
}
continue;
}
}
/*
* See if we need to append to end of file
*/
if (!found && r->urlcount_auto_add) {
results->count = 1;
results->date = ap_ht_time(p, time(0L), DEFAULT_TIME_FORMAT, 0);
/* We should already be at the bottom of the file */
fprintf(fp, "%s\t%010lu\t%s\n", uri, results->count, results->date);
}
/*
* Close TXT file
*/
fd_unlock(fileno(fp));
fclose(fp);
/*
* And signal that there was no error...
*/
return NULL;
}
static char *urlcount_inc_dbm(pool *p, urlcount_results *results,
urlcount_config_rec *r, char *uri)
{
DBM *dbm;
datum d, q;
int len;
char *ptr;
results->count = 0;
results->date = NULL;
q.dptr = uri;
q.dsize = strlen(q.dptr);
/*
* Try to fetch the count under URL from DBM file
*/
if ((dbm = dbm_open(r->urlcount_file, O_RDWR, 0664)) == NULL)
if ((dbm = dbm_open(r->urlcount_file, O_RDWR|O_CREAT, 0664)) == NULL)
return (ap_pstrcat(p, "Failed to open counter DBM file: ", r->urlcount_file, NULL));
if (fd_lock(dbm_dirfno(dbm))) {
dbm_close(dbm);
return (ap_pstrcat(p, "Failed to lock counter DBM file: ", r->urlcount_file, NULL));
}
d = dbm_fetch(dbm, q);
/*
* See if found, if not check whether we have
* to automatically add it
*/
if (d.dptr != NULL) {
ptr = d.dptr;
results->count = atol(ptr);
/* Skip to time stamp */
while (*ptr && !isspace(*ptr))
ptr++;
while (*ptr && isspace(*ptr))
ptr++;
len = d.dsize - (ptr - d.dptr);
results->date = ap_pcalloc(p, len + 1);
ap_cpystrn(results->date, ptr, len);
results->date[len] = '\0';
}
/*
* Increment count, set default date
*/
results->count++;
if (results->date == NULL)
results->date = ap_ht_time(p, time(0L), DEFAULT_TIME_FORMAT, 0);
/*
* Add or update the record
*/
if (d.dptr != NULL || r->urlcount_auto_add) {
d.dptr = ap_psprintf(p, "%lu\t%s", results->count, results->date);
d.dsize = strlen(d.dptr);
dbm_store(dbm, q, d, DBM_REPLACE);
}
/*
* Close DBM file
*/
fd_unlock(dbm_dirfno(dbm));
dbm_close(dbm);
/*
* And signal that there was no error...
*/
return NULL;
}
static char *urlcount_inc(pool *p, urlcount_results *results,
urlcount_config_rec *r, char *uri)
{
char *puri;
char *ptr;
char *q;
/*
* Normalize the URI stripping out double "//"
*/
puri = ap_pstrdup(p, uri);
ptr = puri;
while (*ptr != '\0') {
if (*ptr == '/' && *(ptr+1) == '/')
for (q = ++ptr; *q != '\0'; )
*q = *(q+1);
else
ptr++;
}
/*
* Now dispatch to the type-dependent routines...
*/
if (r->urlcount_type == CntrTxt)
return urlcount_inc_txt(p, results, r, puri);
else if (r->urlcount_type == CntrDbm)
return urlcount_inc_dbm(p, results, r, puri);
else
return NULL;
}
static int urlcount_update(request_rec *r)
{
urlcount_config_rec *svr, *dir;
urlcount_results *sres, *dres;
urlcount_results *res;
char *dbfile;
char *cp;
/*
* Get to last request, if locally redirected.
*/
while (r->next != NULL)
r = r->next;
/*
* Get each of the counter files
*/
svr = ap_get_module_config(r->server->module_config, &urlcount_module);
dir = ap_get_module_config(r->per_dir_config, &urlcount_module);
/*
* Decline operation if missing URI,
* this is an included request, or this
* is not a regular file, or no counter
* file is configured.
*/
if ( r->uri == NULL
|| strcmp(r->protocol, "INCLUDED") == 0
|| !S_ISREG(r->finfo.st_mode)
|| ( svr->urlcount_file == NULL
&& dir->urlcount_file == NULL) )
return (DECLINED);
/*
* Decline for all images
*/
if (r->content_type != NULL)
if (strlen(r->content_type) > 6 && strncmp(r->content_type, "image/", 6) == 0)
return (DECLINED);
/*
* Allocate result structures
*/
sres = (urlcount_results *)ap_pcalloc(r->pool, sizeof(urlcount_results));
dres = (urlcount_results *)ap_pcalloc(r->pool, sizeof(urlcount_results));
/*
* Increment the server configured counter
*/
if (svr->urlcount_file != NULL)
if ((cp = urlcount_inc(r->pool, sres, svr, r->uri)) != NULL)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "mod_urlcount: %s", cp);
/*
* Increment the directory configured counter
* (using the full filename instead of the URL)
*/
if (dir->urlcount_file != NULL)
if ((cp = urlcount_inc(r->pool, dres, dir, r->filename)) != NULL)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "mod_urlcount: %s", cp);
/*
* Now set the environment variables while taking the server config as
* preference over the directory config.
*/
res = (sres->count) ? sres : dres;
dbfile = (sres->count) ? svr->urlcount_file : dir->urlcount_file;
cp = ap_psprintf(r->pool, "%lu", res->count);
ap_table_set(r->subprocess_env, "URL_COUNT", cp);
ap_table_set(r->subprocess_env, "URL_COUNT_RESET", res->date);
ap_table_set(r->subprocess_env, "URL_COUNT_DB", dbfile);
/*
* And signal the API that all is ok...
*/
return OK;
}
static command_rec urlcount_cmds[] = {
{ "CounterType", set_urlcount_type, NULL, ACCESS_CONF, TAKE1,
"Type of per-directory counter (`file' or `dbm')" },
{ "CounterAutoAdd", set_urlcount_autoadd, NULL, ACCESS_CONF, FLAG,
"Whether new URLs are added automatically to the per-directory counter file (`on' or `off')" },
{ "CounterFile", set_urlcount_file, NULL, ACCESS_CONF, TAKE1,
"Filename of the per-directory counter file (`/path/to/file')" },
{ "ServerCounterType", set_svr_urlcount_type, NULL, RSRC_CONF, TAKE1,
"Type of per-server counter (`file' or `dbm')" },
{ "ServerCounterAutoAdd", set_svr_urlcount_autoadd, NULL, RSRC_CONF, FLAG,
"Whether new URLs are added automatically to the per-server counter file (`on' or `off')" },
{ "ServerCounterFile", set_svr_urlcount_file, NULL, RSRC_CONF, TAKE1,
"Name of per-server counter file or database" },
{ NULL }
};
module MODULE_VAR_EXPORT urlcount_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
urlcount_create_dir_config, /* dir config creater */
urlcount_merge_config, /* dir merger */
urlcount_create_srv_config, /* server config */
urlcount_merge_config, /* merge server config */
urlcount_cmds, /* command table */
NULL, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
urlcount_update, /* fixups */
NULL, /* logger */
NULL, /* header parser */
NULL, /* child_init */
NULL, /* child_exit */
NULL /* post read-request */
};