/* ==================================================================== * 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 */ };