/*****************************************************************************/
/*
                                  Digest.c

This module implements the DIGEST authentication functionality.  The actual 
RSA MD5 digest code is contained in its own module MD5.c, and is almost 
exclusively the example implementation code presented in RFC1321.  This module
implements the digest access authentication as proposed in the HTTP Working
Group's, IETF draft, RFC2069, January 1997.

NOTE:  Digest authentication relies on the storage of specially digested
password being available for 'reverse' generation into an MD5 digest and
subsequent comparison with the digest supplied from the browser.  Therefore,
authenication sources that cannot provide this digested password cannot use
Digest authorization.  This most certainly includes SYSUAF and authorization
agents.  For such environments and circumstances where both Basic and Digest
methods are available a Digest challenge SHOULD NOT BE generated and returned
to the browser indicating that Digest method is applicable to this request.

The nonce is generated using three 8 digit, zero-filled, hexadecimal numbers.
The client's 32 bit IP address, and each of the least and most significant 
longwords of the request's binary time.  The nonce therefore comprises a 
string of 24 hexadecimal digits.  It does not rely on any non-public value 
or format for its operation.  

This format allows the returned nonce to have the IP address and timestamp 
easily extracted and checked.  If the client's IP address and the nonce's 
IP address do not match then it is rejected.  If the timestamp indicates 
an unacceptable period between generation and use of the nonce has occured 
then it is rejected with a 'stale nonce' flag.  The period is varied for
different methods.  Very much shorter for PUT and POST methods, etc., longer 
for GET, etc.

BOOM!  Tweaked and revalidated (28-AUG-2012) using Chrome 21.0, Firefox 15.0,
MSIE 9.0, Opera 12.0 and Safari 6.0.  All comply with RFC2069 which WASD is
based on, not the more recent RFC2617.

NOTE!  Checked (27-JUL-2003) for compliance against Mozilla 1.4 (one of the
very few agents supporting Digest authentication).

WARNING!  To date (09-JUL-1996) this functionality has only been tested with
NCSA X Mosaic 2.7-4 .. This version of Mosaic seems a bit flakey with digest
authentication.

NCSA X Mosaic 2.7-4 seems to require the 'opaque' header field to generate a
correct digest response.  I thought it was optional, but I'll put in a dummy
anyway.

VERSION HISTORY
---------------
28-AUG-2012  MGD  (yes - all but a decade on) numerious tweaks
27-JUL-2003  MGD  (overdue) revision (against Mozilla 1.4)
04-AUG-2001  MGD  support module WATCHing
29-APR-2000  MGD  proxy authorization
30-OCT-1997  MGD  a little maintenance
01-JUL-1996  MGD  initial development of "Digest" authentication
*/
/*****************************************************************************/

#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

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <libdef.h>
#include <libdtdef.h>
#include <ssdef.h>
#include <stsdef.h>

/* application related header files */
#include "wasd.h"

#define WASD_MODULE "DIGEST"

#define DefaultNonceLifeTimeSeconds 60

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

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

extern char  SoftwareID[];

extern CONFIG_STRUCT  Config;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/**********************/
/* external functions */
/**********************/

Md5Digest (char*, int, MD5_HASH*);
Md5HexString (char*, int, char*);

/*****************************************************************************/
/*
Convert the octets to a hexadecimal string.
*/

DigestHexString
(
unsigned char *cptr,
int cnt,
unsigned char *sptr
)
{
   static char  HexDigits [] = "0123456789abcdef";

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestHexString()");

   while (cnt--)
   {
      *sptr++ = HexDigits[(*cptr & 0xf0) >> 4];
      *sptr++ = HexDigits[*cptr++ & 0x0f];
   }
   *sptr = '\0';
}

/*****************************************************************************/
/*
Create an MD5 16 byte digest of the username, realm and password.
*/

int DigestA1
(
char *UserName,
char *Realm,
char *Password,
unsigned char *A1Digest
)
{
   char  *cptr, *sptr, *zptr;
   char  A1String [256];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestA1() !&Z !&Z !UL",
                 UserName, Realm, strlen(Password));

   zptr = (sptr = A1String) + sizeof(A1String)-1;
   for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = Realm; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = Password; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   Md5Digest (A1String, sptr-A1String, (MD5_HASH*)A1Digest);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Generate the request-digest/response string.
*/

int DigestResponse
(
unsigned char *A1HexDigest,
char *Nonce,
char *Method,
char *Uri,
char *ResponseHexDigest
)
{
   char  *cptr, *sptr, *zptr;
   char  A2HexDigest [33],
         A2String [1024],
         ResponseString [256];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "DigestResponse() !&Z !&Z !&Z !&Z",
                 A1HexDigest, Nonce, Method, Uri);

   ResponseHexDigest[0] = '\0';

   zptr = (sptr = A2String) + sizeof(A2String)-1;
   for (cptr = Method; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = Uri; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   Md5HexString (A2String, sptr-A2String, A2HexDigest);

   zptr = (sptr = ResponseString) + sizeof(ResponseString)-1;
   for (cptr = A1HexDigest; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = Nonce; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = A2HexDigest; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   Md5HexString (ResponseString, sptr-ResponseString, ResponseHexDigest);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z", ResponseHexDigest);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Generate a basic DIGEST challenge string, comprising the authentication realm
and the nonce.  Store this null-terminated string in dynamically allocated 
memory pointed to by 'rqptr->rqAuth.DigestChallengePtr'.  The 'Other' allows
other digest fields to be included, for example ", stale=true".
*/ 
 
int DigestChallenge
(
REQUEST_STRUCT *rqptr,
char *Other
)
{
   static $DESCRIPTOR (NonceFaoDsc, "!8XL!8XL!8XL");

   int  size;
   ulong  *qtime;
   char  Nonce [25],
         AuthHeader [1024];
   char  *cptr, *sptr, *zptr;
   $DESCRIPTOR (NonceDsc, Nonce);

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestChallenge()");

   qtime = &rqptr->rqTime.BeginTime64;
   sys$fao (&NonceFaoDsc, 0, &NonceDsc,
            IPADDRESS_ADR4(&rqptr->ClientPtr->IpAddress), qtime[0], qtime[1]);
   Nonce[sizeof(Nonce)-1] = '\0';

   zptr = (sptr = AuthHeader) + sizeof(AuthHeader)-1;
   if (rqptr->ServicePtr->ProxyAuthRequired)
      cptr = "Proxy-Authenticate";
   else
      cptr = "WWW-Authenticate";
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   for (cptr = ": Digest realm=\""; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = rqptr->rqAuth.RealmDescrPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = "\", nonce=\""; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = Nonce; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = '\"';
   for (cptr = Other; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   /* allocate heap memory */
   rqptr->rqAuth.DigestChallengePtr = VmGetHeap (rqptr, size = sptr-AuthHeader+1);
   strzcpy (rqptr->rqAuth.DigestChallengePtr, AuthHeader, size);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Parse the "Digest" authorization HTTP header field.  Extract and store the
digest response, nonce, realm, username, and digest URI.
*/ 
 
int DigestAuthorization (REQUEST_STRUCT *rqptr)

{
   int  size, status;
   char  Algorithm [16],
         Nonce [25],
         Opaque [33],
         Realm [16],
         Response [33],
         Uri [1024],
         UserName [33];
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestAuthorization() !&Z",
                 rqptr->rqAuth.RequestAuthorizationPtr);

   rqptr->rqAuth.Scheme = rqptr->rqAuth.ChallengeScheme = AUTH_SCHEME_DIGEST;

   cptr = rqptr->rqAuth.RequestAuthorizationPtr;
   /* skip across the authorization scheme name ("digest") */
   while (*cptr && !ISLWS(*cptr)) cptr++;
   /* skip over white-space between scheme and digest parameters */
   while (*cptr && ISLWS(*cptr)) cptr++;

   /****************/
   /* parse values */
   /****************/

   Algorithm[0] = Nonce[0] = Opaque[0] = Realm[0] =
      Response[0] = Uri[0] = UserName[0] = '\0';

   while (*cptr)
   {
      while (ISLWS(*cptr) || *cptr == ',') cptr++;
      if (WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z", cptr);

      if (to_upper(*cptr) == 'A' && strsame (cptr, "algorithm=", 10))
         zptr = (sptr = Algorithm) + sizeof(Algorithm)-1;
      else   
      if (to_upper(*cptr) == 'N' && strsame (cptr, "nonce=", 6))
         zptr = (sptr = Nonce) + sizeof(Nonce)-1;
      else   
      if (to_upper(*cptr) == 'O' && strsame (cptr, "opaque=", 7))
         zptr = (sptr = Opaque) + sizeof(Opaque)-1;
      else   
      if (to_upper(*cptr) == 'R' && strsame (cptr, "realm=", 6))
         zptr = (sptr = Realm) + sizeof(Realm)-1;
      else   
      if (to_upper(*cptr) == 'R' && strsame (cptr, "response=", 9))
         zptr = (sptr = Response) + sizeof(Response)-1;
      else   
      if (to_upper(*cptr) == 'U' && strsame (cptr, "uri=", 4))
         zptr = (sptr = Uri) + sizeof(Uri)-1;
      else   
      if (to_upper(*cptr) == 'U' && strsame (cptr, "username=", 9))
         zptr = (sptr = UserName) + sizeof(UserName)-1;
      else   
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
         return (STS$K_ERROR);
      }

      while (*cptr && *cptr != '=') cptr++;
      if (*cptr) cptr++;
      
      if (*cptr == '\"')
      {   
         cptr++;
         while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++;
         if (*cptr) cptr++;
      }
      else
         while (*cptr && !ISLWS(*cptr) && *cptr != ',' && sptr < zptr)
             *sptr++ = *cptr++;

      if (sptr > zptr)
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
         return (STS$K_ERROR);
      }
      *sptr = '\0';
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
"algorithm:!&Z nonce:!&Z opaque:!&Z realm:!&Z \
response:!&Z username:!&Z uri:!&Z",
         Algorithm, Nonce, Opaque, Realm, Response, UserName, Uri);

   /****************/
   /* check values */
   /****************/

   if ((Algorithm[0] && !strsame (Algorithm, "MD5", -1)) ||
       !Nonce[0] ||
       !Realm[0] ||
       !Response[0] ||
       !Uri[0] ||
       !UserName[0] ||
       strcmp (rqptr->rqHeader.RequestUriPtr, Uri) ||
       strcmp (rqptr->rqAuth.RealmDescrPtr, Realm) ||
       strlen(Nonce) != 24)
   {
      rqptr->rqResponse.HttpStatus = 401;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_LI);
      return (STS$K_ERROR);
   }

   if (VMSnok (status = DigestCheckNonce (rqptr, Nonce)))
      return (status);

   /****************/
   /* store values */
   /****************/

   rqptr->rqAuth.DigestNoncePtr = VmGetHeap (rqptr, size = strlen(Nonce)+1);
   strzcpy (rqptr->rqAuth.DigestNoncePtr, Nonce, size);

   rqptr->rqAuth.RealmDescrPtr = VmGetHeap (rqptr, size = strlen(Realm)+1);
   strzcpy (rqptr->rqAuth.RealmDescrPtr, Realm, size);

   rqptr->rqAuth.DigestResponsePtr = VmGetHeap (rqptr, size = strlen(Response)+1);
   strzcpy (rqptr->rqAuth.DigestResponsePtr, Response, size);

   rqptr->rqAuth.DigestUriPtr = VmGetHeap (rqptr, size = strlen(Uri)+1);
   strzcpy (rqptr->rqAuth.DigestUriPtr, Uri, size);

   if (strlen(UserName) >= sizeof(rqptr->RemoteUser))
   {
      rqptr->rqResponse.HttpStatus = 401;
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (STS$K_ERROR);
   }
   /* user names are always upper case for us! */
   sptr = rqptr->RemoteUser;
   for (cptr = UserName; *cptr; *sptr++ = to_upper(*cptr++));
   *sptr = '\0';
   rqptr->RemoteUserLength = sptr - rqptr->RemoteUser;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z", rqptr->RemoteUser);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
The nonce comprises three 8 digit, zero-filled, hexadecimal numbers. The
client's 32 bit IP address, and each of the least and most significant 
longwords of the request's binary time.  The nonce is therefore a string of 24
hexadecimal digits.

This format allows the returned nonce to have the IP address and timestamp 
easily extracted and checked.  If the client's IP address and the nonce's 
IP address do not match then it is rejected.  If the timestamp indicates 
an unacceptable period between generation and use of the nonce has occured 
then it is rejected with a 'stale nonce' flag.
*/ 
 
int DigestCheckNonce
(
REQUEST_STRUCT *rqptr,
char *Nonce
)
{
   static unsigned long  LibSecondOfYear = LIB$K_SECOND_OF_YEAR;

   int  status;
   unsigned long  NonceLifeTimeSeconds,
                  NonceSecondOfYear,
                  SecondOfYear;
   unsigned long  NonceBinaryTime [2];
   IPADDRESS  ClientIpAddress;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestCheckNonce()");

   /* lib$cvt_htb() returns a 1 for OK or a 0 for conversion error */
   if (status = lib$cvt_htb (8, Nonce, IPADDRESS_ADR4(&ClientIpAddress)))
      if (status = lib$cvt_htb (8, Nonce+8, &NonceBinaryTime[0]))
         status = lib$cvt_htb (8, Nonce+16, &NonceBinaryTime[1]);

   if (status != 1)
   {
      if (WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&S", status);
      rqptr->rqResponse.HttpStatus = 401;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_LI);
      return (STS$K_ERROR);
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&X !&X !%D",
                 IPADDRESS_ADR4(&ClientIpAddress),
                 IPADDRESS_ADR4(&rqptr->ClientPtr->IpAddress),
                 &NonceBinaryTime);

   if (!MATCH0 (IPADDRESS_ADR4(&ClientIpAddress),
                IPADDRESS_ADR4(&rqptr->ClientPtr->IpAddress),
                IPADDRESS_SIZE(&rqptr->ClientPtr->IpAddress)))
   {
      rqptr->rqResponse.HttpStatus = 401;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_LI);
      return (STS$K_ERROR);
   }

   lib$cvt_from_internal_time (&LibSecondOfYear, &SecondOfYear,
                               &rqptr->rqTime.BeginTime64);

   lib$cvt_from_internal_time (&LibSecondOfYear, &NonceSecondOfYear,
                               &NonceBinaryTime);

   if ((rqptr->rqHeader.Method & HTTP_METHOD_GET) ||
       (rqptr->rqHeader.Method & HTTP_METHOD_HEAD))
      NonceLifeTimeSeconds = Config.cfAuth.DigestNonceGetLifeTime;
   else
      NonceLifeTimeSeconds = Config.cfAuth.DigestNoncePutLifeTime;
   if (!NonceLifeTimeSeconds)
      NonceLifeTimeSeconds = DefaultNonceLifeTimeSeconds;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "SecondOfYear:!UL Nonce:!UL LifeTime:!UL Stale:!&B",
                 SecondOfYear, NonceSecondOfYear, NonceLifeTimeSeconds,
                 (NonceSecondOfYear+NonceLifeTimeSeconds<SecondOfYear));

   if (NonceSecondOfYear + NonceLifeTimeSeconds < SecondOfYear)
   {
      /*
          Nonce is stale.  But otherwise fine.
          Now I know this test will break once a year :^(
          (midnight, New Year's Eve) but too bad, it's simple :^)
      */

      /* generate a special "stale nonce" challenge */
      rqptr->rqAuth.Scheme = rqptr->rqAuth.ChallengeScheme = AUTH_SCHEME_DIGEST;

      DigestChallenge (rqptr, ", stale=true");

      rqptr->rqResponse.HttpStatus = 401;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_LI);
   }

   return (SS$_NORMAL);
}

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

