/*****************************************************************************/
/*
                                  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, dated June 6th 1996.  Functions herein are called from the
Auth.c module.

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.

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 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.

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

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

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

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "DigestHexString()");

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

/*****************************************************************************/
/*
Create an MD5 16 byte digest (RFC 1321 and RFC????) of the username, realm and
password.
*/

int DigestA1
(
char *UserName,
char *Realm,
char *Password,
unsigned char *A1Digest
)
{
   static $DESCRIPTOR (A1FaoDsc, "!AZ:!AZ:!AZ");
   static $DESCRIPTOR (StringDsc, "");

   int  status;
   char  A1String [128];
   unsigned short  Length;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "DigestA1() !&Z !&Z !#**",
                 UserName, Realm, strlen(Password));

   StringDsc.dsc$a_pointer = A1String;
   StringDsc.dsc$w_length = sizeof(A1String)-1;
   if (VMSnok (status =
       sys$fao (&A1FaoDsc, &Length, &StringDsc,
                UserName, Realm, Password)))
   {
      memset (A1Digest, 0, 16);
      return (status);
   }
   Md5Digest (A1String, Length, (MD5_HASH*)A1Digest);
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Create an MD5 hex digest (RFC 1321 and RFC????) of the username, realm and
password for use in HTTP Digest authentication.
*/

int DigestA1Hex
(
char *UserName,
char *Realm,
char *Password,
char *A1HexDigest
)
{
   static $DESCRIPTOR (A1FaoDsc, "!AZ:!AZ:!AZ");
   static $DESCRIPTOR (StringDsc, "");

   int  status;
   char  A1String [128];
   unsigned short  Length;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "DigestA1Hex() !&Z !&Z !#**",
                 UserName, Realm, strlen(Password));

   StringDsc.dsc$a_pointer = A1String;
   StringDsc.dsc$w_length = sizeof(A1String)-1;
   if (VMSnok (status =
       sys$fao (&A1FaoDsc, &Length, &StringDsc,
                UserName, Realm, Password)))
   {
      A1HexDigest[0] = '\0';
      return (status);
   }
   Md5HexString (A1String, Length, A1HexDigest);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!&Z", A1HexDigest);

   return (SS$_NORMAL);
}

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

int DigestResponse
(
unsigned char *A1HexDigest,
char *Nonce,
char *Method,
char *Uri,
char *ResponseHexDigest
)
{
   static $DESCRIPTOR (A2FaoDsc, "!AZ:!AZ");
   static $DESCRIPTOR (ResponseFaoDsc, "!AZ:!AZ:!AZ");
   static $DESCRIPTOR (StringDsc, "");

   int  status;
   char  A2HexDigest [33],
         A2String [256],
         ResponseString [256];
   unsigned short  Length;

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

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

   ResponseHexDigest[0] = '\0';

   StringDsc.dsc$a_pointer = A2String;
   StringDsc.dsc$w_length = sizeof(A2String)-1;
   if (VMSnok (status =
       sys$fao (&A2FaoDsc, &Length, &StringDsc,
                Method, Uri)))
      return (status);
   Md5HexString (A2String, Length, A2HexDigest);

   StringDsc.dsc$a_pointer = ResponseString;
   StringDsc.dsc$w_length = sizeof(ResponseString)-1;
   if (VMSnok (status =
       sys$fao (&ResponseFaoDsc, &Length, &StringDsc,
                A1HexDigest, Nonce, A2HexDigest)))
      return (status);

   Md5HexString (ResponseString, Length, ResponseHexDigest);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, 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".

NCSA 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.
*/ 
 
int DigestChallenge
(
REQUEST_STRUCT *rqptr,
char *Other
)
{
   static $DESCRIPTOR (StringDsc, "");
   static $DESCRIPTOR (DigestChallengeFaoDsc,
          "!AZ: Digest realm=\"!AZ\", nonce=\"!AZ\"!AZ!AZ");
   static $DESCRIPTOR (NonceFaoDsc, "!#XL!8XL!8XL");

   int  status;
   unsigned short  Length;
   char  Nonce [25],
         String [1024];
   char  *OpaquePtr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "DigestChallenge()");

   /* NCSA Mosaic 2.7-4 seems to "require" an opaque parameter */
   if (rqptr->rqHeader.UserAgentPtr &&
       strstr (rqptr->rqHeader.UserAgentPtr, "Mosaic"))
      OpaquePtr = ", opaque=\"*\"";
   else
      OpaquePtr = "";

   StringDsc.dsc$a_pointer = Nonce;
   StringDsc.dsc$w_length = sizeof(Nonce)-1;

   sys$fao (&NonceFaoDsc, &Length, &StringDsc,
            IPADDRESS_SIZE(&rqptr->rqClient.IpAddress),
            IPADDRESS_ADR4(&rqptr->rqClient.IpAddress),
            rqptr->rqTime.Vms64bit[0],
            rqptr->rqTime.Vms64bit[1]);
   Nonce[Length] = '\0';
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!&Z", Nonce);

   StringDsc.dsc$a_pointer = String;
   StringDsc.dsc$w_length = sizeof(String)-1;

   sys$fao (&DigestChallengeFaoDsc, &Length, &StringDsc,
            rqptr->ServicePtr->ProxyAuthRequired ?
               "Proxy-Authenticate" : "WWW-Authenticate",
            rqptr->rqAuth.RealmDescrPtr, Nonce, OpaquePtr, Other);
   String[Length] = '\0';
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!&Z", String);

   /* allocate heap memory */
   rqptr->rqAuth.DigestChallengePtr = VmGetHeap (rqptr, Length+1);
   memcpy (rqptr->rqAuth.DigestChallengePtr, String, Length+1);

   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  status,
        AlgorithmSize,
        NonceSize,
        OpaqueSize,
        RealmSize,
        ResponseSize,
        UriSize,
        UserNameSize;
   int  *SizePtr;      
   char  Algorithm [16],
         Nonce [25],
         Opaque [33],
         Realm [16],
         Response [33],
         Uri [256],
         UserName [16];
   char  *cptr, *lptr, *sptr, *zptr,
         *RequestUriPtr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, 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 (NULL, FI_LI, WATCH_MOD__OTHER, "!&Z", cptr);
**/
      if (toupper(*cptr) == 'A' && strsame (cptr, "algorithm=", 10))
      {   
         cptr += 10;
         zptr = (lptr = sptr = Algorithm) + sizeof(Algorithm);
         SizePtr = &AlgorithmSize;
      }
      else   
      if (toupper(*cptr) == 'N' && strsame (cptr, "nonce=", 6))
      {   
         cptr += 6;
         zptr = (lptr = sptr = Nonce) + sizeof(Nonce);
         SizePtr = &NonceSize;
      }
      else   
      if (toupper(*cptr) == 'O' && strsame (cptr, "opaque=", 7))
      {   
         cptr += 7;
         zptr = (lptr = sptr = Opaque) + sizeof(Opaque);
         SizePtr = &OpaqueSize;
      }
      else   
      if (toupper(*cptr) == 'R' && strsame (cptr, "realm=", 6))
      {   
         cptr += 6;
         zptr = (lptr = sptr = Realm) + sizeof(Realm);
         SizePtr = &RealmSize;
      }
      else   
      if (toupper(*cptr) == 'R' && strsame (cptr, "response=", 9))
      {   
         cptr += 9;
         zptr = (lptr = sptr = Response) + sizeof(Response);
         SizePtr = &ResponseSize;
      }
      else   
      if (toupper(*cptr) == 'U' && strsame (cptr, "uri=", 4))
      {   
         cptr += 4;
         zptr = (lptr = sptr = Uri) + sizeof(Uri);
         SizePtr = &UriSize;
      }
      else   
      if (toupper(*cptr) == 'U' && strsame (cptr, "username=", 9))
      {   
         cptr += 9;
         zptr = (lptr = sptr = UserName) + sizeof(UserName);
         SizePtr = &UserNameSize;
      }
      else   
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
         return (STS$K_ERROR);
      }
      
      if (*cptr == '\"')
      {   
         cptr++;
         while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++;
      }
      if (sptr >= zptr || !*cptr)
      {
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
         return (STS$K_ERROR);
      }
      *sptr++ = '\0';
      /* note that the size INCLUDES the terminating null */
      *SizePtr = sptr - lptr;
      cptr++;
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, 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 */
   /****************/

   /* NCSA Mosaic 2.7-4 seems to lop off the leading slash */
   if (Uri[0] == '/')
      RequestUriPtr = rqptr->rqHeader.RequestUriPtr;
   else
   if (rqptr->rqHeader.RequestUriPtr[0])
      RequestUriPtr = rqptr->rqHeader.RequestUriPtr+1;
   else
      RequestUriPtr = rqptr->rqHeader.RequestUriPtr;

   if ((Algorithm[0] && !strsame (Algorithm, "MD5", -1)) ||
       !Nonce[0] ||
       !Realm[0] ||
       !Response[0] ||
       !Uri[0] ||
       !UserName[0] ||
       strcmp (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, NonceSize);
   memcpy (rqptr->rqAuth.DigestNoncePtr, Nonce, NonceSize);

   rqptr->rqAuth.RealmDescrPtr = VmGetHeap (rqptr, RealmSize);
   memcpy (rqptr->rqAuth.RealmDescrPtr, Realm, RealmSize);

   rqptr->rqAuth.DigestResponsePtr = VmGetHeap (rqptr, ResponseSize);
   memcpy (rqptr->rqAuth.DigestResponsePtr, Response, ResponseSize);

   rqptr->rqAuth.DigestUriPtr = VmGetHeap (rqptr, UriSize);
   memcpy (rqptr->rqAuth.DigestUriPtr, Uri, UriSize);

   if (UserNameSize > 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++ = toupper(*cptr++));
   *sptr = '\0';
   rqptr->RemoteUserLength = sptr - rqptr->RemoteUser;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !&Z",
                 rqptr->RemoteUserLength, 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 (NULL, FI_LI, WATCH_MOD__OTHER, "DigestCheckNonce()");

   /* lib$cvt_htb() returns a 1 for OK or a 0 for conversion error */
   if (status = lib$cvt_htb (IPADDRESS_SIZE(&ClientIpAddress),
                             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 (NULL, FI_LI, WATCH_MOD__OTHER, "!&S !-%!&M", 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 (NULL, FI_LI, WATCH_MOD__OTHER, "!&X !&X !%D",
                 IPADDRESS_ADR4(&ClientIpAddress),
                 IPADDRESS_ADR4(&rqptr->rqClient.IpAddress),
                 &NonceBinaryTime);

   if (memcmp (IPADDRESS_ADR4(&ClientIpAddress),
               IPADDRESS_ADR4(&rqptr->rqClient.IpAddress),
               IPADDRESS_SIZE(&rqptr->rqClient.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.Vms64bit);

   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 (NULL, FI_LI, WATCH_MOD__OTHER,
                 "SecondOfYear:!UL Nonce:!UL LifeTime:!UL Stale:!&B",
                 SecondOfYear, NonceSecondOfYear, NonceLifeTimeSeconds,
                 (NonceSecondOfYear+NonceLifeTimeSeconds<SecondOfYear));

   if (NonceSecondOfYear + NonceLifeTimeSeconds < SecondOfYear)
   {
      /*
          Nonce is stale.
          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 (STS$K_ERROR);
   }

   return (SS$_NORMAL);
}

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

