mod_usertrack.c revision ff250fa78a491f4dcf3e673f122f2d9942a7411a
2N/A/* ====================================================================
2N/A * The Apache Software License, Version 1.1
2N/A *
2N/A * Copyright (c) 2000 The Apache Software Foundation. All rights
2N/A * reserved.
2N/A *
2N/A * Redistribution and use in source and binary forms, with or without
2N/A * modification, are permitted provided that the following conditions
2N/A * are met:
2N/A *
2N/A * 1. Redistributions of source code must retain the above copyright
2N/A * notice, this list of conditions and the following disclaimer.
2N/A *
2N/A * 2. Redistributions in binary form must reproduce the above copyright
2N/A * notice, this list of conditions and the following disclaimer in
2N/A * the documentation and/or other materials provided with the
2N/A * distribution.
2N/A *
2N/A * 3. The end-user documentation included with the redistribution,
2N/A * if any, must include the following acknowledgment:
2N/A * "This product includes software developed by the
2N/A * Apache Software Foundation (http://www.apache.org/)."
2N/A * Alternately, this acknowledgment may appear in the software itself,
2N/A * if and wherever such third-party acknowledgments normally appear.
2N/A *
2N/A * 4. The names "Apache" and "Apache Software Foundation" must
2N/A * not be used to endorse or promote products derived from this
2N/A * software without prior written permission. For written
2N/A * permission, please contact apache@apache.org.
2N/A *
2N/A * 5. Products derived from this software may not be called "Apache",
12N/A * nor may "Apache" appear in their name, without prior written
12N/A * permission of the Apache Software Foundation.
2N/A *
2N/A * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
2N/A * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2N/A * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26N/A * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
26N/A * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2N/A * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26N/A * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
38N/A * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26N/A * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26N/A * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26N/A * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26N/A * SUCH DAMAGE.
26N/A * ====================================================================
26N/A *
26N/A * This software consists of voluntary contributions made by many
26N/A * individuals on behalf of the Apache Software Foundation. For more
26N/A * information on the Apache Software Foundation, please see
26N/A * <http://www.apache.org/>.
26N/A *
26N/A * Portions of this software are based upon public domain software
26N/A * originally written at the National Center for Supercomputing Applications,
26N/A * University of Illinois, Urbana-Champaign.
26N/A */
26N/A
26N/A/* User Tracking Module (Was mod_cookies.c)
26N/A *
26N/A * This Apache module is designed to track users paths through a site.
26N/A * It uses the client-side state ("Cookie") protocol developed by Netscape.
2N/A * It is known to work on Netscape browsers, Microsoft Internet
26N/A * Explorer and others currently being developed.
26N/A *
26N/A * Each time a page is requested we look to see if the browser is sending
26N/A * us a Cookie: header that we previously generated.
26N/A *
26N/A * If we don't find one then the user hasn't been to this site since
26N/A * starting their browser or their browser doesn't support cookies. So
26N/A * we generate a unique Cookie for the transaction and send it back to
26N/A * the browser (via a "Set-Cookie" header)
26N/A * Future requests from the same browser should keep the same Cookie line.
26N/A *
26N/A * By matching up all the requests with the same cookie you can
26N/A * work out exactly what path a user took through your site. To log
2N/A * the cookie use the " %{Cookie}n " directive in a custom access log;
26N/A *
26N/A * Example 1 : If you currently use the standard Log file format (CLF)
27N/A * and use the command "TransferLog somefilename", add the line
27N/A * LogFormat "%h %l %u %t \"%r\" %s %b %{Cookie}n"
27N/A * to your config file.
26N/A *
26N/A * Example 2 : If you used to use the old "CookieLog" directive, you
12N/A * can emulate it by adding the following command to your config file
30N/A * CustomLog filename "%{Cookie}n \"%r\" %t"
26N/A *
26N/A * Notes:
26N/A * 1. This code now logs the initial transaction (the one that created
2N/A * the cookie to start with).
26N/A * 2. This module has been designed to not interfere with other Cookies
30N/A * your site may be using; just avoid sending out cookies with
26N/A * the name "Apache=" or things will get confused.
2N/A * 3. If you want you can modify the Set-Cookie line so that the Cookie
7N/A * never expires. You would then get the same Cookie each time the
27N/A * user revisits your site.
27N/A *
27N/A * Mark Cox, mark@ukweb.com, 6 July 95
38N/A *
38N/A * This file replaces mod_cookies.c
38N/A */
21N/A
7N/A#include "apr.h"
26N/A#include "apr_lib.h"
26N/A#include "apr_strings.h"
38N/A
26N/A#define APR_WANT_STRFUNC
26N/A#include "apr_want.h"
26N/A
30N/A#include "httpd.h"
26N/A#include "http_config.h"
46N/A#include "http_core.h"
46N/A#include "http_request.h"
46N/A
26N/A
26N/Amodule AP_MODULE_DECLARE_DATA usertrack_module;
46N/A
46N/Atypedef struct {
46N/A int always;
2N/A int expires;
46N/A} cookie_log_state;
46N/A
46N/Atypedef struct {
46N/A int enabled;
46N/A char *cookie_name;
26N/A} cookie_dir_rec;
46N/A
46N/A/* Make Cookie: Now we have to generate something that is going to be
46N/A * pretty unique. We can base it on the pid, time, hostip */
46N/A
46N/A#define COOKIE_NAME "Apache"
26N/A
46N/Astatic void make_cookie(request_rec *r)
46N/A{
46N/A cookie_log_state *cls = ap_get_module_config(r->server->module_config,
46N/A &usertrack_module);
46N/A /* 1024 == hardcoded constant */
46N/A char cookiebuf[1024];
46N/A char *new_cookie;
2N/A const char *rname = ap_get_remote_host(r->connection, r->per_dir_config,
46N/A REMOTE_NAME);
38N/A cookie_dir_rec *dcfg;
46N/A
46N/A dcfg = ap_get_module_config(r->per_dir_config, &usertrack_module);
46N/A
38N/A /* XXX: hmm, this should really tie in with mod_unique_id */
46N/A apr_snprintf(cookiebuf, sizeof(cookiebuf), "%s.%qd", rname, apr_time_now());
46N/A
26N/A if (cls->expires) {
26N/A apr_exploded_time_t tms;
46N/A
2N/A apr_explode_gmt(&tms, r->request_time + cls->expires * APR_USEC_PER_SEC);
38N/A
26N/A /* Cookie with date; as strftime '%a, %d-%h-%y %H:%M:%S GMT' */
2N/A new_cookie = apr_psprintf(r->pool,
32N/A "%s=%s; path=/; expires=%s, %.2d-%s-%.2d %.2d:%.2d:%.2d GMT",
32N/A dcfg->cookie_name, cookiebuf, apr_day_snames[tms.tm_wday],
32N/A tms.tm_mday, apr_month_snames[tms.tm_mon],
32N/A tms.tm_year % 100,
32N/A tms.tm_hour, tms.tm_min, tms.tm_sec);
32N/A }
32N/A else {
32N/A new_cookie = apr_psprintf(r->pool, "%s=%s; path=/",
32N/A dcfg->cookie_name, cookiebuf);
32N/A }
38N/A
38N/A apr_table_setn(r->headers_out, "Set-Cookie", new_cookie);
38N/A apr_table_setn(r->notes, "cookie", apr_pstrdup(r->pool, cookiebuf)); /* log first time */
38N/A return;
38N/A}
38N/A
38N/Astatic int spot_cookie(request_rec *r)
38N/A{
38N/A cookie_dir_rec *dcfg = ap_get_module_config(r->per_dir_config,
38N/A &usertrack_module);
38N/A const char *cookie;
38N/A const char *value;
38N/A
26N/A if (!dcfg->enabled) {
26N/A return DECLINED;
26N/A }
26N/A
26N/A if ((cookie = apr_table_get(r->headers_in, "Cookie")))
26N/A if ((value = ap_strstr_c(cookie, dcfg->cookie_name))) {
26N/A char *cookiebuf, *cookieend;
26N/A
26N/A value += strlen(dcfg->cookie_name) + 1; /* Skip over the '=' */
32N/A cookiebuf = apr_pstrdup(r->pool, value);
32N/A cookieend = strchr(cookiebuf, ';');
32N/A if (cookieend)
32N/A *cookieend = '\0'; /* Ignore anything after a ; */
32N/A
32N/A /* Set the cookie in a note, for logging */
32N/A apr_table_setn(r->notes, "cookie", cookiebuf);
32N/A
38N/A return DECLINED; /* There's already a cookie, no new one */
38N/A }
38N/A make_cookie(r);
32N/A return OK; /* We set our cookie */
}
static void *make_cookie_log_state(apr_pool_t *p, server_rec *s)
{
cookie_log_state *cls =
(cookie_log_state *) apr_palloc(p, sizeof(cookie_log_state));
cls->expires = 0;
return (void *) cls;
}
static void *make_cookie_dir(apr_pool_t *p, char *d)
{
cookie_dir_rec *dcfg;
dcfg = (cookie_dir_rec *) apr_pcalloc(p, sizeof(cookie_dir_rec));
dcfg->cookie_name = COOKIE_NAME;
dcfg->enabled = 0;
return dcfg;
}
static const char *set_cookie_enable(cmd_parms *cmd, void *mconfig, int arg)
{
cookie_dir_rec *dcfg = mconfig;
dcfg->enabled = arg;
return NULL;
}
static const char *set_cookie_exp(cmd_parms *parms, void *dummy, const char *arg)
{
cookie_log_state *cls = ap_get_module_config(parms->server->module_config,
&usertrack_module);
time_t factor, modifier = 0;
time_t num = 0;
char *word;
/* The simple case first - all numbers (we assume) */
if (apr_isdigit(arg[0]) && apr_isdigit(arg[strlen(arg) - 1])) {
cls->expires = atol(arg);
return NULL;
}
/*
* The harder case - stolen from mod_expires
*
* CookieExpires "[plus] {<num> <type>}*"
*/
word = ap_getword_conf(parms->pool, &arg);
if (!strncasecmp(word, "plus", 1)) {
word = ap_getword_conf(parms->pool, &arg);
};
/* {<num> <type>}* */
while (word[0]) {
/* <num> */
if (apr_isdigit(word[0]))
num = atoi(word);
else
return "bad expires code, numeric value expected.";
/* <type> */
word = ap_getword_conf(parms->pool, &arg);
if (!word[0])
return "bad expires code, missing <type>";
factor = 0;
if (!strncasecmp(word, "years", 1))
factor = 60 * 60 * 24 * 365;
else if (!strncasecmp(word, "months", 2))
factor = 60 * 60 * 24 * 30;
else if (!strncasecmp(word, "weeks", 1))
factor = 60 * 60 * 24 * 7;
else if (!strncasecmp(word, "days", 1))
factor = 60 * 60 * 24;
else if (!strncasecmp(word, "hours", 1))
factor = 60 * 60;
else if (!strncasecmp(word, "minutes", 2))
factor = 60;
else if (!strncasecmp(word, "seconds", 1))
factor = 1;
else
return "bad expires code, unrecognized type";
modifier = modifier + factor * num;
/* next <num> */
word = ap_getword_conf(parms->pool, &arg);
}
cls->expires = modifier;
return NULL;
}
static const char *set_cookie_name(cmd_parms *cmd, void *mconfig, const char *name)
{
cookie_dir_rec *dcfg = (cookie_dir_rec *) mconfig;
dcfg->cookie_name = apr_pstrdup(cmd->pool, name);
return NULL;
}
static const command_rec cookie_log_cmds[] = {
AP_INIT_TAKE1("CookieExpires", set_cookie_exp, NULL, RSRC_CONF,
"an expiry date code"),
AP_INIT_FLAG("CookieTracking", set_cookie_enable, NULL, OR_FILEINFO,
"whether or not to enable cookies"),
AP_INIT_TAKE1("CookieName", set_cookie_name, NULL, OR_FILEINFO,
"name of the tracking cookie"),
{NULL}
};
static void register_hooks(apr_pool_t *p)
{
ap_hook_fixups(spot_cookie,NULL,NULL,APR_HOOK_MIDDLE);
}
module AP_MODULE_DECLARE_DATA usertrack_module = {
STANDARD20_MODULE_STUFF,
make_cookie_dir, /* dir config creater */
NULL, /* dir merger --- default is to override */
make_cookie_log_state, /* server config */
NULL, /* merge server configs */
cookie_log_cmds, /* command apr_table_t */
register_hooks /* register hooks */
};