/*****************************************************************************/
/*
                                 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_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

#include <stdio.h>
#include <ctype.h>

#include "wasd.h"

#define WASD_MODULE "TRACK"

/******************/
/* global storage */
/******************/

char  TrackCookieName [] = TRACK_COOKIE_NAME;
int  TrackCookieNameLength = sizeof(TrackCookieName)-1;

/********************/
/* external storage */
/********************/

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

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));
   WriteFao (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);
   }

   if (Debug) fprintf (stdout, "cptr |%s|\n", cptr);
   while (*cptr)
   {
      while (*cptr && *cptr != TrackCookieName[0]) cptr++;
      if (!*cptr) break;
      if (!memcmp (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;
   }

   WriteFao (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);
} 

/*****************************************************************************/

