]> granicus.if.org Git - apache/blob - modules/metadata/mod_usertrack.c
Update callers of apr_explode_gmt to apr_time_exp_gmt. The
[apache] / modules / metadata / mod_usertrack.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  *
54  * Portions of this software are based upon public domain software
55  * originally written at the National Center for Supercomputing Applications,
56  * University of Illinois, Urbana-Champaign.
57  */
58
59 /* User Tracking Module (Was mod_cookies.c)
60  *
61  * *** IMPORTANT NOTE: This module is not designed to generate
62  * *** cryptographically secure cookies.  This means you should not
63  * *** use cookies generated by this module for authentication purposes
64  *
65  * This Apache module is designed to track users paths through a site.
66  * It uses the client-side state ("Cookie") protocol developed by Netscape.
67  * It is known to work on most browsers.
68  *
69  * Each time a page is requested we look to see if the browser is sending
70  * us a Cookie: header that we previously generated.
71  *
72  * If we don't find one then the user hasn't been to this site since
73  * starting their browser or their browser doesn't support cookies.  So
74  * we generate a unique Cookie for the transaction and send it back to
75  * the browser (via a "Set-Cookie" header)
76  * Future requests from the same browser should keep the same Cookie line.
77  *
78  * By matching up all the requests with the same cookie you can
79  * work out exactly what path a user took through your site.  To log
80  * the cookie use the " %{Cookie}n " directive in a custom access log;
81  *
82  * Example 1 : If you currently use the standard Log file format (CLF)
83  * and use the command "TransferLog somefilename", add the line
84  *       LogFormat "%h %l %u %t \"%r\" %s %b %{Cookie}n"
85  * to your config file.
86  *
87  * Example 2 : If you used to use the old "CookieLog" directive, you
88  * can emulate it by adding the following command to your config file
89  *       CustomLog filename "%{Cookie}n \"%r\" %t"
90  *
91  * Mark Cox, mjc@apache.org, 6 July 95
92  *
93  * This file replaces mod_cookies.c
94  */
95
96 #include "apr.h"
97 #include "apr_lib.h"
98 #include "apr_strings.h"
99
100 #define APR_WANT_STRFUNC
101 #include "apr_want.h"
102
103 #include "httpd.h"
104 #include "http_config.h"
105 #include "http_core.h"
106 #include "http_request.h"
107
108
109 module AP_MODULE_DECLARE_DATA usertrack_module;
110
111 typedef struct {
112     int always;
113     int expires;
114 } cookie_log_state;
115
116 typedef enum {
117     CT_UNSET,
118     CT_NETSCAPE,
119     CT_COOKIE,
120     CT_COOKIE2
121 } cookie_type_e;
122
123 typedef struct {
124     int enabled;
125     cookie_type_e style;
126     char *cookie_name;
127     char *cookie_domain;
128 } cookie_dir_rec;
129
130 /* Make Cookie: Now we have to generate something that is going to be
131  * pretty unique.  We can base it on the pid, time, hostip */
132
133 #define COOKIE_NAME "Apache"
134
135 static void make_cookie(request_rec *r)
136 {
137     cookie_log_state *cls = ap_get_module_config(r->server->module_config,
138                                                  &usertrack_module);
139     /* 1024 == hardcoded constant */
140     char cookiebuf[1024];
141     char *new_cookie;
142     const char *rname = ap_get_remote_host(r->connection, r->per_dir_config,
143                                            REMOTE_NAME, NULL);
144     cookie_dir_rec *dcfg;
145
146     dcfg = ap_get_module_config(r->per_dir_config, &usertrack_module);
147
148     /* XXX: hmm, this should really tie in with mod_unique_id */
149     apr_snprintf(cookiebuf, sizeof(cookiebuf), "%s.%qd", rname, apr_time_now());
150
151     if (cls->expires) {
152
153         /* Cookie with date; as strftime '%a, %d-%h-%y %H:%M:%S GMT' */
154         new_cookie = apr_psprintf(r->pool, "%s=%s; path=/",
155                                   dcfg->cookie_name, cookiebuf);
156
157         if ((dcfg->style == CT_UNSET) || (dcfg->style == CT_NETSCAPE)) {
158             apr_time_exp_t tms;
159             apr_time_exp_gmt(&tms, r->request_time 
160                                  + cls->expires * APR_USEC_PER_SEC);
161             new_cookie = apr_psprintf(r->pool,
162                                        "%s; expires=%s, "
163                                        "%.2d-%s-%.2d %.2d:%.2d:%.2d GMT",
164                                        new_cookie, apr_day_snames[tms.tm_wday],
165                                        tms.tm_mday,
166                                        apr_month_snames[tms.tm_mon],
167                                        tms.tm_year % 100,
168                                        tms.tm_hour, tms.tm_min, tms.tm_sec);
169         }
170         else {
171             new_cookie = apr_psprintf(r->pool, "%s; max-age=%d",
172                                       new_cookie, cls->expires);
173         }
174     }
175     else {
176         new_cookie = apr_psprintf(r->pool, "%s=%s; path=/",
177                                   dcfg->cookie_name, cookiebuf);
178     }
179     if (dcfg->cookie_domain != NULL) {
180         new_cookie = apr_pstrcat(r->pool, new_cookie, "; domain=",
181                                  dcfg->cookie_domain,
182                                  (dcfg->style == CT_COOKIE2
183                                   ? "; version=1"
184                                   : ""),
185                                  NULL);
186     }
187
188     apr_table_setn(r->headers_out,
189                    (dcfg->style == CT_COOKIE2 ? "Set-Cookie2" : "Set-Cookie"),
190                    new_cookie);
191     apr_table_setn(r->notes, "cookie", apr_pstrdup(r->pool, cookiebuf));   /* log first time */
192     return;
193 }
194
195 static int spot_cookie(request_rec *r)
196 {
197     cookie_dir_rec *dcfg = ap_get_module_config(r->per_dir_config,
198                                                 &usertrack_module);
199     const char *cookie;
200     const char *value;
201
202     if (!dcfg->enabled) {
203         return DECLINED;
204     }
205
206     if ((cookie = apr_table_get(r->headers_in,
207                                 (dcfg->style == CT_COOKIE2
208                                  ? "Cookie2"
209                                  : "Cookie"))))
210         if ((value = ap_strstr_c(cookie, dcfg->cookie_name))) {
211             char *cookiebuf, *cookieend;
212
213             value += strlen(dcfg->cookie_name) + 1;  /* Skip over the '=' */
214             cookiebuf = apr_pstrdup(r->pool, value);
215             cookieend = strchr(cookiebuf, ';');
216             if (cookieend)
217                 *cookieend = '\0';      /* Ignore anything after a ; */
218
219             /* Set the cookie in a note, for logging */
220             apr_table_setn(r->notes, "cookie", cookiebuf);
221
222             return DECLINED;    /* There's already a cookie, no new one */
223         }
224     make_cookie(r);
225     return OK;                  /* We set our cookie */
226 }
227
228 static void *make_cookie_log_state(apr_pool_t *p, server_rec *s)
229 {
230     cookie_log_state *cls =
231     (cookie_log_state *) apr_palloc(p, sizeof(cookie_log_state));
232
233     cls->expires = 0;
234
235     return (void *) cls;
236 }
237
238 static void *make_cookie_dir(apr_pool_t *p, char *d)
239 {
240     cookie_dir_rec *dcfg;
241
242     dcfg = (cookie_dir_rec *) apr_pcalloc(p, sizeof(cookie_dir_rec));
243     dcfg->cookie_name = COOKIE_NAME;
244     dcfg->cookie_domain = NULL;
245     dcfg->style = CT_UNSET;
246     dcfg->enabled = 0;
247     return dcfg;
248 }
249
250 static const char *set_cookie_enable(cmd_parms *cmd, void *mconfig, int arg)
251 {
252     cookie_dir_rec *dcfg = mconfig;
253
254     dcfg->enabled = arg;
255     return NULL;
256 }
257
258 static const char *set_cookie_exp(cmd_parms *parms, void *dummy,
259                                   const char *arg)
260 {
261     cookie_log_state *cls;
262     time_t factor, modifier = 0;
263     time_t num = 0;
264     char *word;
265
266     cls  = ap_get_module_config(parms->server->module_config,
267                                 &usertrack_module);
268     /* The simple case first - all numbers (we assume) */
269     if (apr_isdigit(arg[0]) && apr_isdigit(arg[strlen(arg) - 1])) {
270         cls->expires = atol(arg);
271         return NULL;
272     }
273
274     /*
275      * The harder case - stolen from mod_expires 
276      *
277      * CookieExpires "[plus] {<num> <type>}*"
278      */
279
280     word = ap_getword_conf(parms->pool, &arg);
281     if (!strncasecmp(word, "plus", 1)) {
282         word = ap_getword_conf(parms->pool, &arg);
283     };
284
285     /* {<num> <type>}* */
286     while (word[0]) {
287         /* <num> */
288         if (apr_isdigit(word[0]))
289             num = atoi(word);
290         else
291             return "bad expires code, numeric value expected.";
292
293         /* <type> */
294         word = ap_getword_conf(parms->pool, &arg);
295         if (!word[0])
296             return "bad expires code, missing <type>";
297
298         factor = 0;
299         if (!strncasecmp(word, "years", 1))
300             factor = 60 * 60 * 24 * 365;
301         else if (!strncasecmp(word, "months", 2))
302             factor = 60 * 60 * 24 * 30;
303         else if (!strncasecmp(word, "weeks", 1))
304             factor = 60 * 60 * 24 * 7;
305         else if (!strncasecmp(word, "days", 1))
306             factor = 60 * 60 * 24;
307         else if (!strncasecmp(word, "hours", 1))
308             factor = 60 * 60;
309         else if (!strncasecmp(word, "minutes", 2))
310             factor = 60;
311         else if (!strncasecmp(word, "seconds", 1))
312             factor = 1;
313         else
314             return "bad expires code, unrecognized type";
315
316         modifier = modifier + factor * num;
317
318         /* next <num> */
319         word = ap_getword_conf(parms->pool, &arg);
320     }
321
322     cls->expires = modifier;
323
324     return NULL;
325 }
326
327 static const char *set_cookie_name(cmd_parms *cmd, void *mconfig,
328                                    const char *name)
329 {
330     cookie_dir_rec *dcfg = (cookie_dir_rec *) mconfig;
331
332     dcfg->cookie_name = apr_pstrdup(cmd->pool, name);
333     return NULL;
334 }
335
336 /*
337  * Set the value for the 'Domain=' attribute.
338  */
339 static const char *set_cookie_domain(cmd_parms *cmd, void *mconfig,
340                                      const char *name)
341 {
342     cookie_dir_rec *dcfg;
343
344     dcfg = (cookie_dir_rec *) mconfig;
345
346     /*
347      * Apply the restrictions on cookie domain attributes.
348      */
349     if (strlen(name) == 0) {
350         return "CookieDomain values may not be null";
351     }
352     if (name[0] != '.') {
353         return "CookieDomain values must begin with a dot";
354     }
355     if (ap_strchr_c(&name[1], '.') == NULL) {
356         return "CookieDomain values must contain at least one embedded dot";
357     }
358
359     dcfg->cookie_domain = apr_pstrdup(cmd->pool, name);
360     return NULL;
361 }
362
363 /*
364  * Make a note of the cookie style we should use.
365  */
366 static const char *set_cookie_style(cmd_parms *cmd, void *mconfig,
367                                     const char *name)
368 {
369     cookie_dir_rec *dcfg;
370
371     dcfg = (cookie_dir_rec *) mconfig;
372
373     if (strcasecmp(name, "Netscape") == 0) {
374         dcfg->style = CT_NETSCAPE;
375     }
376     else if ((strcasecmp(name, "Cookie") == 0)
377              || (strcasecmp(name, "RFC2109") == 0)) {
378         dcfg->style = CT_COOKIE;
379     }
380     else if ((strcasecmp(name, "Cookie2") == 0)
381              || (strcasecmp(name, "RFC2965") == 0)) {
382         dcfg->style = CT_COOKIE2;
383     }
384     else {
385         return apr_psprintf(cmd->pool, "Invalid %s keyword: '%s'",
386                             cmd->cmd->name, name);
387     }
388
389     return NULL;
390 }
391
392 static const command_rec cookie_log_cmds[] = {
393     AP_INIT_TAKE1("CookieExpires", set_cookie_exp, NULL, OR_FILEINFO,
394                   "an expiry date code"),
395     AP_INIT_TAKE1("CookieDomain", set_cookie_domain, NULL, OR_FILEINFO,
396                   "domain to which this cookie applies"),
397     AP_INIT_TAKE1("CookieStyle", set_cookie_style, NULL, OR_FILEINFO,
398                   "'Netscape', 'Cookie' (RFC2109), or 'Cookie2' (RFC2965)"),
399     AP_INIT_FLAG("CookieTracking", set_cookie_enable, NULL, OR_FILEINFO,
400                  "whether or not to enable cookies"),
401     AP_INIT_TAKE1("CookieName", set_cookie_name, NULL, OR_FILEINFO,
402                   "name of the tracking cookie"),
403     {NULL}
404 };
405
406 static void register_hooks(apr_pool_t *p)
407 {
408     ap_hook_fixups(spot_cookie,NULL,NULL,APR_HOOK_MIDDLE);
409 }
410
411 module AP_MODULE_DECLARE_DATA usertrack_module = {
412     STANDARD20_MODULE_STUFF,
413     make_cookie_dir,            /* dir config creater */
414     NULL,                       /* dir merger --- default is to override */
415     make_cookie_log_state,      /* server config */
416     NULL,                       /* merge server configs */
417     cookie_log_cmds,            /* command apr_table_t */
418     register_hooks              /* register hooks */
419 };