/*****************************************************************************/ /* track.c Track site usage by individual browser sessions. This parallels the functionality of the Apache "mod_usertrack" module in that it provides the capability to track a user's path through a site or domain of sites using cookie technology. The track is accomplished by creating a unique ID for an initial request. This is a globally and temporally unique string (see GenerateUniqueId() in SUPPORT.C module). This string is then placed into the site's access logs (either as the common format remote ID field, or in a custom log position) and can then be tracked through the site or across multiple WASD sites in an organisation (if a domain is specified). By default the track is confined to the single (virtual) server and to a single browser session (terminated by the browser being closed). If the configuration specifies multiple sessions a cookie expiry date well in the future is added to "encourage" the browser to keep the cookie between sessions. Also, if the configuration specifies a domain the cookie will also include a domain specification, ensuring the same cookie (and unique ID) is sent with all requests to servers within that domain. As it is not possible to insert response header fields (i.e. the cookie) into non-parsed header script responses (those that supply a full HTTP response) this approach cannot be used to *set* track cookies from such responses, but if already set in the browser these responses will be tracked like any other. Note also that this is a Netscape-compatible (non RFC) cookie format. For administrative/experimental/investigative purposes a track cookie may cancelled (expired) by accessesing the path "/httpd/-/track/cancel". CONFIGURATION DIRECTIVES ------------------------ [Track] enables/disables per-server tracking [TrackMultiSession] enables/enables tracking across multiple sessions [TrackDomain] in the form ".org.domain" (two periods) for the major top-level domains (e.g. ".com", ".edu", etc.), or ".org.group.domain" (three periods) for others By default non-proxy services are enabled, proxy services disabled. The per-service ";track"/";notrack" parameters further allow the enabling/disabling of tracking on a per-service basis. VERSION HISTORY --------------- 04-AUG-2001 MGD support module WATCHing 07-MAY-2000 MGD initial (I really just wanted to fool around with cookies ;^) */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif #include #include #include "wasd.h" #define WASD_MODULE "TRACK" /******************/ /* global storage */ /******************/ char TrackCookieName [] = TRACK_COOKIE_NAME; int TrackCookieNameLength = sizeof(TrackCookieName)-1; /********************/ /* external storage */ /********************/ extern CONFIG_STRUCT Config; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Generate a globally unique ID for this request and create a "WASDtrack" cookie to contain it. */ TrackSetCookie (REQUEST_STRUCT *rqptr) { int idx; unsigned short Length; char *cptr; char Buffer [256]; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "TrackSetCookie()"); /* this fits, they're both based on UNIQUE_ID_SIZE */ strcpy (rqptr->TrackId, GenerateUniqueId(rqptr)); FaoToBuffer (Buffer, sizeof(Buffer), &Length, "!AZ=!AZ; path=/;!&@!&@", TrackCookieName, rqptr->TrackId, Config.cfTrack.Domain[0] ? " domain=!AZ;" : "!+", Config.cfTrack.Domain, Config.cfTrack.MultiSession ? " expires=Wed, 13 Jan 2038 14:00:00 GMT;" : ""); cptr = VmGetHeap (rqptr, Length+1); for (idx = 0; idx < RESPONSE_COOKIE_MAX; idx++) { if (!rqptr->rqResponse.CookiePtr[idx]) { memcpy (rqptr->rqResponse.CookiePtr[idx] = cptr, Buffer, Length+1); break; } } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "TRACK set \"!AZ\"", rqptr->TrackId); } /*****************************************************************************/ /* Search the request cookie header field for the "WASDtrack" name-value pair. If found copy the value into the request structures track ID field after some simple integrity checking. */ BOOL TrackGetCookie (REQUEST_STRUCT *rqptr) { char *cptr, *mptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "TrackGetCookie()"); if (!(cptr = rqptr->rqHeader.CookiePtr)) { if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "TRACK no cookie(s)"); return (false); } while (*cptr) { while (*cptr && *cptr != TrackCookieName[0]) cptr++; if (!*cptr) break; if (MATCH0 (cptr, TrackCookieName, TrackCookieNameLength)) { cptr += TrackCookieNameLength; if (*cptr == '=') { mptr = ++cptr; zptr = (sptr = rqptr->TrackId) + sizeof(rqptr->TrackId)-1; while (*cptr && *cptr != ';' && !isspace(*cptr) && sptr < zptr) *sptr++ = *cptr++; /* scan to end of cookie value (just checking) */ while (*cptr && *cptr != ';' && !isspace(*cptr)) cptr++; /* if it wasn't the correct size for some reason then flag that */ if (cptr - mptr != sizeof(rqptr->TrackId)-1) { *sptr = '\0'; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) { WatchThis (rqptr, FI_LI, WATCH_REQUEST, "TRACK cookie problem", rqptr->TrackId); WatchData (rqptr->rqHeader.CookiePtr, strlen(rqptr->rqHeader.CookiePtr)); } return (false); } else { if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "TRACK found \"!AZ\"", rqptr->TrackId); *sptr = '\0'; return (true); } } } if (*cptr) cptr++; } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "TRACK not found"); return (false); } /*****************************************************************************/ /* Cancel (expire) the request's current track cookie. */ TrackCancelCookie ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction ) { int idx; unsigned short Length; char *cptr; char Buffer [256]; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "TrackCancelCookie() !&A", NextTaskFunction); if (!Config.cfTrack.Enabled) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } FaoToBuffer (Buffer, sizeof(Buffer), &Length, "!AZ=; path=/;!&@ expires=Fri, 13 Jan 1978 14:00:00 GMT;", TrackCookieName, Config.cfTrack.Domain[0] ? " domain=!AZ;" : "!+", Config.cfTrack.Domain); cptr = VmGetHeap (rqptr, Length+1); for (idx = 0; idx < RESPONSE_COOKIE_MAX; idx++) { if (!rqptr->rqResponse.CookiePtr[idx]) { memcpy (rqptr->rqResponse.CookiePtr[idx] = cptr, Buffer, Length+1); break; } } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_REQUEST)) WatchThis (rqptr, FI_LI, WATCH_REQUEST, "TRACK cancel \"!AZ\"", rqptr->TrackId); ReportSuccess (rqptr, "Track cookie \"!AZ\" cancelled.", rqptr->TrackId); SysDclAst (NextTaskFunction, rqptr); } /*****************************************************************************/