/*****************************************************************************/
/*
                               AuthAgent.c

Licensed under the Apache License, Version 2.0 (the License);
you may not use this software except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an AS IS BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

This module interfaces with an authentication agent script.  These are CGIplus
scripts that perform a username/password validation role (authentication) or a
group membership role (authorization by virtue of group membership).  In this
environment the script has basically all the resources available to a CGIplus
script and must communicate with the server (to pass back the authorization
results) using "escaped" CGIplus data (see the DCL module for further detail).

See AUTH.C for overall detail on the WASD authorization environment.

The name of the script to be activated is derived from the realm or group name
(passed as 'AgentName') and the script directory contained in 'AUTH_AGENT_PATH'
(currently "/cgiauth-bin/", and could be remapped using HTTPD$MAP of course).

The WATCH facility is a valuable adjunct in understanding/debugging agent
script behaviour.


AUTHENTICATING A USERNAME/PASSWORD
----------------------------------
The transaction details are found in the following CGI variables.

WWW_AUTH_AGENT .................. "REALM" or other parameter
WWW_AUTH_PASSWORD ............... user supplied case-sensitive password
WWW_AUTH_REALM .................. realm name (same as agent name)
WWW_AUTH_REALM_DESCRIPTION ...... realm description user is prompted with
WWW_REMOTE_USER ................. case-sensitive username

Valid responses (digits and 'access' are mandatory, other text is optional):

'000 any text' ........... ignored by the server, provides WATCHable trace info 
'100 AUTHAGENT-CALLOUT' .. ignored by the server, DCL.C will barf on this
'100 BODY' ............... transfer the request body to the agent
'100 LIFETIME integer' ... set script's CGIplus lifetime (zero makes infinite)
'100 NOCACHE' ............ do not cache the results of this authorization
'100 NOTICED text' ....... 'error' noticed (a la ErrorNoticed())
'100 OPCOM text' ......... send 'text' to [OpcomAuthorization] and server log
'100 REASON any text' .... reason authentication was denied
'100 REMOTE-USER name .... provide user name (authenticated some non-401 way)
'100 SCRIPT-META text' ... CGI-like variable
'100 SET-COOKIE cookie' .. RFC2109 cookie (generates "Set-Cookie:" header)
'100 USER any text' ...... provide user details (only after 200 response)
'100 VMS-USER name ....... this username is a VMS username (see note below)
'200 access' ............. username/password verified
                           access: "READ", "WRITE", "READ+WRITE", "FULL"
'302 location ............ redirect to specified location
                           e.g. http://the.host.name/the/path
                                //the.host.name/the/path
                                ///the/path
'401 reason' ............. username/password did not verify
'401 "any-text"' ......... (quoted) used as the browser authorization prompt
'403 reason' ............. access is forbidden
'500 description' ........ script error to be reported via server

VMS-USER issues: when a VMS-USER is passed back to the server the username
undergoes all VMS authorization processing (e.g. ID, prime days, etc) - except
password checking, it is assumed the agent has authenticated the username.  The
access level (R, R+W, etc.) is derived from the SYSUAF information - unless the
agent *subsequently* provides a "200 access" callout.  The user details come
from the SYSUAF - unless the agent *subsequently* provides a "100 USER details"
callout.


ESTABLISHING GROUP MEMBERSHIP
-----------------------------
The transaction details are found in the following CGI variables.

WWW_AUTH_AGENT .................. "GROUP"
WWW_AUTH_GROUP .................. name of group
WWW_REMOTE_USER ................. case-sensitive username

Valid responses (digits are mandatory, other text is optional):

'000 any text' ........... ignored by the server, provides WATCHable trace info 
'100 BODY' ............... transfer the request body to the agent
'100 LIFETIME integer' ... set script's CGIplus lifetime (zero makes infinite)
'100 NOCACHE' ............ do not cache the results of this authorization
'100 SET-COOKIE cookie' .. RFC2109 cookie (generates "Set-Cookie:" header)
'200 any text' ........... indicates group membership
'302 location ............ redirect to specified location
                           e.g. http://the.host.name/the/path
                                //the.host.name/the/path
                                ///the/path
'403 reason' ............. indicates not a group member
'500 description' ........ script error to be reported via server


REQUEST REDACTION
-----------------
In common with DCL.C callout processing an authentication agent may redact
(completely rewrite and restart) a request.  Note the trailing colons.

'100 REDACT:<opaque>' ........
'100 REDACT-SIZE:integer' ....


VERSION HISTORY
---------------
27-MAY-2021  MGD  using v12... agent functionality
                  !CGI: and !DICT: callouts
07-FEB-2011  MGD  callout BODY implemented (finally, for PAPI)
20-NOV-2007  MGD  callout REDACT: and REDACT-SIZE: (notice the colons)
                  callout SCRIPT-META
                  callout NOTICED
                  callout OPCOM
11-MAY-2007  MGD  AUTHAGENT-CALLOUT belt and braces
15-JUL-2006  MGD  buffer browser-supplied username in AUTH_REMOTE_USER 
                  when VMS-USER or REMOTE-USER are called-out
08-MAR-2003  MGD  add '100 REASON any text'
04-AUG-2001  MGD  support module WATCHing
02-AUG-2001  MGD  bugfix; allow agent to accept 'CGIPLUS:' directive
07-DEC-2000  MGD  agent can now '100 SET-COOKIE rfc2109-cookie'
27-NOV-2000  MGD  bugfix; ensure a mapping rule exists for the agent
09-JUN-2000  MGD  allow "302 location" redirection response
28-AUG-1999  MGD  initial, for v6.1
*/
/*****************************************************************************/

#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 <descrip.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "AUTHAGENT"

#if WATCH_MOD
#define FI_NOLI WASD_MODULE, __LINE__
#else
/* in production let's keep the exact line to ourselves! */
#define FI_NOLI WASD_MODULE, 0
#endif

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

extern int  OpcomMessages;
extern char  ErrorSanityCheck[];
extern ACCOUNTING_STRUCT  *AccountingPtr;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initiate an authenication agent script.  After calling this function all
authentication processing occurs asynchronously.
*/ 

void AuthAgentBegin
(
REQUEST_STRUCT *rqptr,
char *AgentName,
REQUEST_AST AgentCalloutFunction
)
{
   char *cptr;
   char  AgentFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         AgentScriptName [SCRIPT_NAME_SIZE+1],
         Scratch [256];

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthAgentBegin() !&Z !&X",
                 AgentName, AgentCalloutFunction);

   /* after calling this function authorization completes asynchronously! */
   rqptr->rqAuth.AstFunction = rqptr->rqAuth.AstFunctionBuffer;
   rqptr->rqAuth.FinalStatus = AUTH_PENDING;

   strzcpy (Scratch, AUTH_AGENT_PATH, sizeof(Scratch));
   strzcat (Scratch, AgentName, sizeof(Scratch));

   AgentFileName[0] = AgentScriptName[0] = '\0';
   cptr = MapUrl_Map (Scratch, 0,
                      NULL, 0,
                      AgentScriptName, sizeof(AgentScriptName),
                      AgentFileName, sizeof(AgentFileName),
                      NULL, 0,
                      NULL, rqptr, NULL);
   if (AgentScriptName[0] == '+') AgentScriptName[0] = '/';
   if ((!cptr[0] && cptr[1]) || !AgentScriptName[0] || !AgentFileName[0])
   {
      /* either mapping error or no rule to map the agent */
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_AGENT_MAPPING), FI_LI);
      SysDclAst (AgentCalloutFunction, rqptr);
      return;
   }

   /* this is a v12... agent */
   rqptr->AgentRequestPtr = "*";

   DclBegin (rqptr, AgentCalloutFunction, NULL,
             AgentScriptName, NULL, AgentFileName, NULL, &AuthAgentCallout);
}

/*****************************************************************************/
/*
This is the function called each time the agent script outputs escaped data to
the server.  It must check for beginning and end of agent processing (indicated
by various states of the the 'OutputPtr' and 'OutputCount' storage), and
appropriately process the status responses output by the agent.
*/ 

void AuthAgentCallout (REQUEST_STRUCT *rqptr)

{
   int  idx,
        status,
        CgiPlusLifeTime,
        Length,
        OutputCount;
   char  *cptr, *sptr, *zptr,
         *OutputPtr;
   char  Scratch [1024];

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthAgentCallout() !&Z",
                 rqptr->rqAuth.PathParameterPtr);

   OutputPtr = rqptr->rqCgi.CalloutOutputPtr;
   OutputCount = rqptr->rqCgi.CalloutOutputCount;

   if (!OutputPtr && OutputCount == -1)
   {
      /***************/
      /* agent begin */
      /***************/

      if (WATCHING (rqptr, WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                    "CALLOUT \"!AZ\" begin", rqptr->rqAuth.PathParameterPtr);
      return;
   }

   if (!OutputPtr && OutputCount == 0)
   {
      /*************/
      /* agent end */
      /*************/

      if (WATCHPNT(rqptr) &&
          WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                    "CALLOUT \"!AZ\" end", rqptr->rqAuth.PathParameterPtr);

      /* always ensure any authentication agent information is cancelled! */
      rqptr->rqAuth.PathParameterPtr = "";
      rqptr->rqAuth.PathParameterLength = 0;

      return;
   }

   /**************/
   /* agent data */
   /**************/

   if (WATCHPNT(rqptr) &&
       WATCH_CATEGORY(WATCH_AUTH))
   {
      WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                 "CALLOUT \"!AZ\" !UL bytes",
                 rqptr->rqAuth.PathParameterPtr, OutputCount);
      WatchDataDump (OutputPtr, OutputCount);
      /* if it's trace information then provide it slightly more readably */
      if (!strncmp (OutputPtr, "000 ", 4)) WatchData (OutputPtr, OutputCount);
   }

   if (strsame (OutputPtr, "!CGIPLUS:", 9))
   {
      /************/
      /* cgiplus: */
      /************/

      for (cptr = OutputPtr+9; *cptr && isspace(*cptr); cptr++);
      if (strsame (cptr, "STRUCT", 6))
         rqptr->DclTaskPtr->CgiPlusVarStruct = true;
      else
      if (strsame (cptr, "RECORD", 6))
         rqptr->DclTaskPtr->CgiPlusVarStruct = false;
      else
         AuthAgentCalloutResponseError (rqptr);
      return;
   }

   if (strsame (OutputPtr, "!AGENT-BEGIN:", 13))
   {
      /****************/
      /* AGENT-BEGIN: */
      /****************/

      if (!rqptr->AgentRequestPtr) AuthAgentCalloutResponseError (rqptr);
      return;
   }

   if (strsame (OutputPtr, "!AGENT-END:", 11))
   {
      /**************/
      /* AGENT-END: */
      /**************/

      if (!rqptr->AgentRequestPtr) AuthAgentCalloutResponseError (rqptr);
      rqptr->AgentResponsePtr = rqptr->AgentRequestPtr;
      return;
   }

   cptr = NULL;
   if (strsame (OutputPtr, "!CGI:", 5))
   {
      /********/
      /* CGI: */
      /********/

      zptr = (sptr = Scratch) + sizeof(Scratch)-1;
      *sptr++ = DICT_TYPE_CONFIG[0];
      for (cptr = OutputPtr+5; *cptr && isspace(*cptr); cptr++);
      if (*cptr == '!') *sptr++ = '!';
      for (cptr = "cgi_"; *cptr; *sptr++ = *cptr++);
      for (cptr = OutputPtr+5; *cptr && isspace(*cptr); cptr++);
      if (*cptr == '!') cptr++;
   }
   else
   if (strsame (OutputPtr, "!DICT:", 6))
   {
      /*********/
      /* DICT: */
      /*********/

      zptr = (sptr = Scratch) + sizeof(Scratch)-1;
      *sptr++ = DICT_TYPE_CONFIG[0];
      for (cptr = OutputPtr+6; *cptr && isspace(*cptr); cptr++);
   }
   if (cptr)
   {
      /*****************/
      /* DICT: or CGI: */
      /*****************/

      /* bit too clever, or prudent, I wonder? */
      if (!*cptr) return;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';

      MetaConDictionary (rqptr, Scratch);
      if (WATCHPNT(rqptr) && (WATCH_CATEGORY(WATCH_AUTH) &&
                              WATCH_CATEGORY(WATCH_INTERNAL)))
         DictWatch (rqptr->rqDictPtr, DICT_TYPE_CONFIG, "*");
      return;
   }

   /******************/
   /* agent response */
   /******************/

   if (OutputCount <= 4 ||
       !isdigit(OutputPtr[0]) ||
       !isdigit(OutputPtr[1]) ||
       !isdigit(OutputPtr[2]) ||
       OutputPtr[3] != ' ')
   {
      /* agent response error */
      AuthAgentCalloutResponseError (rqptr);
      return;
   }

   if (!strcmp (rqptr->rqAuth.PathParameterPtr, "GROUP"))
   {
      if (!strncmp (OutputPtr, "200 ", 4))
      {
         /* a group member */
         if (rqptr->rqAuth.FinalStatus != STS$K_ERROR)
            rqptr->rqAuth.FinalStatus = SS$_NORMAL;
         return;
      }
      if (!strncmp (OutputPtr, "403 ", 4))
      {
         /* not a group member */
         rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_GROUP;
         return;
      }
   }

   if (!strncmp (OutputPtr, "000 ", 4))
   {
      /* just WATCHable debug information */
      return;
   }

   if (!strncmp (OutputPtr, "100 ", 4))
   {
      /* informational */
      if (strsame (OutputPtr+4, "AUTHAGENT-CALLOUT", 17))
      {
         /* avoid auth agents being run as scripts, DCL.C will barf on this */
         return;
      }
      if (strsame (OutputPtr+4, "BODY", 4))
      {
         if (rqptr->rqHeader.Method == HTTP_METHOD_PUT ||
             rqptr->rqHeader.Method == HTTP_METHOD_POST)
         {
            /* transfer the body of the request to the agent */
            BodyReadBegin (rqptr, &DclHttpInput, NULL);
            rqptr->DclTaskPtr->QueuedClientRead++;
         }
         else
            AuthAgentCalloutResponseError (rqptr);
         return;
      }
      if (strsame (OutputPtr+4, "LIFETIME ", 9))
      {
         /* set lifetime of agent script */
         for (cptr = OutputPtr+13; *cptr && !isdigit(*cptr); cptr++);
         if (isdigit(*cptr))
         {
            /* zero makes the script immune to supervisor purging */
            if ((CgiPlusLifeTime = atoi(cptr)) == 0)
               CgiPlusLifeTime = DCL_DO_NOT_DISTURB;
            rqptr->DclTaskPtr->LifeTimeSecond = CgiPlusLifeTime;
            return;
         }               
      }
      if (strsame (OutputPtr+4, "NOCACHE", 7))
      {
         /* do not cache this authorization */
         rqptr->rqAuth.NoCache = true;
         return;
      }
      if (strsame (OutputPtr+4, "NOTICED ", 8))
      {
         /* 'error' noticed by agent */
         for (cptr = OutputPtr+12; *cptr && ISLWS(*cptr); cptr++);
         for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++);
         *sptr = '\0';
         ErrorNoticed (rqptr, 0, cptr, FI_LI);
         return;
      }
      if (strsame (OutputPtr+4, "OPCOM ", 6))
      {
         for (cptr = OutputPtr+10; *cptr && ISLWS(*cptr); cptr++);
         for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++);
         *sptr = '\0';

         FaoToStdout (
"%HTTPD-W-AUTHOPCOM, !20%D, !AZ\n\
-AUTHOPCOM-I-SERVICE, !AZ//!AZ\n\
-AUTHOPCOM-I-CLIENT, !AZ\n\
-AUTHOPCOM-I-USERNAME, \"!AZ\" in \"!AZ\"\n\
-AUTHOPCOM-I-URI, !AZ !AZ\n",
            0, cptr,
            rqptr->ServicePtr->RequestSchemeNamePtr,
            rqptr->ServicePtr->ServerHostPort,
            ClientHostString(rqptr),
            rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr,
            rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

         if (OpcomMessages & OPCOM_AUTHORIZATION)
            FaoToOpcom (
"%HTTPD-W-AUTHOPCOM, !AZ\r\n\
-AUTHOPCOM-I-SERVICE, !AZ//!AZ\r\n\
-AUTHOPCOM-I-CLIENT, !AZ\r\n\
-AUTHOPCOM-I-USERNAME, \"!AZ\" in \"!AZ\"\r\n\
-AUTHOPCOM-I-URI, !AZ !AZ",
               cptr,
               rqptr->ServicePtr->RequestSchemeNamePtr,
               rqptr->ServicePtr->ServerHostPort,
               ClientHostString(rqptr),
               rqptr->RemoteUser, rqptr->rqAuth.RealmDescrPtr,
               rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr);

         return;
      }
      if (strsame (OutputPtr+4, "REASON ", 7))
      {
         /* reason for authentication failure (included in message) */
         for (cptr = OutputPtr+11; *cptr && ISLWS(*cptr); cptr++);
         for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++);
         *sptr = '\0';
         rqptr->rqAuth.ReasonLength = Length = sptr - cptr + 1;
         rqptr->rqAuth.ReasonPtr = VmGetHeap (rqptr, Length+1);
         strzcpy (rqptr->rqAuth.ReasonPtr, cptr, Length+1);
         return;
      }
      if (strsame (OutputPtr+4, "REDACT:", 7))
      {
         RequestRedact (rqptr, OutputPtr+4, OutputCount-4, false);
         return;
      }
      if (strsame (OutputPtr+4, "REDACT-SIZE:", 12))
      {
         RequestRedact (rqptr, OutputPtr+4, OutputCount-4, false);
         return;
      }
      if (strsame (OutputPtr+4, "REMOTE-USER ", 12))
      {
         /* buffer the original remote user (the browser-supplied user id) */
         strzcpy (rqptr->rqAuth.RemoteUser, rqptr->RemoteUser,
                  sizeof(rqptr->rqAuth.RemoteUser));
         rqptr->rqAuth.RemoteUserLength = rqptr->RemoteUserLength;

         /* explicitly set the remote-user */
         if (OutputPtr[OutputCount-1] == '\n') OutputPtr[--OutputCount] = '\0'; 
         zptr = (sptr = rqptr->RemoteUser) + sizeof(rqptr->RemoteUser)-1;
         for (cptr = OutputPtr+16; *cptr && ISLWS(*cptr); cptr++);
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr)
            *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            rqptr->RemoteUser[0] = '\0';
            rqptr->rqAuth.FinalStatus = STS$K_ERROR;
            ErrorGeneralOverflow (rqptr, FI_LI);
            return;
         }
         *sptr = '\0';
         rqptr->RemoteUserLength = sptr - rqptr->RemoteUser;
         return;
      }
      if (strsame (OutputPtr+4, "SCRIPT-META ", 12))
      {
         /* add a CGI-type variable <NAME>=<value> to any subsequent script */
         if (OutputPtr[OutputCount-1] == '\n') OutputPtr[--OutputCount] = '\0'; 
         for (cptr = OutputPtr+16; *cptr && ISLWS(*cptr); cptr++);
         for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++);
         *sptr = '\0';
         Length = sptr - cptr;
         rqptr->rqAuth.ScriptMetaPtr =
            VmReallocHeap (rqptr, rqptr->rqAuth.ScriptMetaPtr,
                           rqptr->rqAuth.ScriptMetaLength+Length+2, FI_LI);
         sptr = rqptr->rqAuth.ScriptMetaPtr + rqptr->rqAuth.ScriptMetaLength;
         zptr = sptr + Length;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr++ = '\0';
         rqptr->rqAuth.ScriptMetaLength = sptr - rqptr->rqAuth.ScriptMetaPtr;
         /* terminating empty string */
         *sptr = '\0';
         return;
      }
      if (strsame (OutputPtr+4, "SET-COOKIE ", 11))
      {
         /* add a cookie to the header */
         if (OutputPtr[OutputCount-1] == '\n') OutputPtr[--OutputCount] = '\0'; 
         for (cptr = OutputPtr+15; *cptr && ISLWS(*cptr); cptr++);
         for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++);
         *sptr = '\0';
         Length = sptr - cptr + 1;
         for (idx = 0; idx < RESPONSE_COOKIE_MAX; idx++)
         {
            if (!rqptr->rqResponse.CookiePtr[idx])
            {
               rqptr->rqResponse.CookiePtr[idx] = VmGetHeap (rqptr, Length);
               strzcpy (rqptr->rqResponse.CookiePtr[idx], cptr,
                        sizeof(rqptr->rqResponse.CookiePtr[idx]));
               return;
            }
         }
      }
      if (strsame (OutputPtr+4, "USER ", 5))
      {
         /* user details */
         for (cptr = OutputPtr+9; *cptr && ISLWS(*cptr); cptr++);
         for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++);
         *sptr = '\0';
         rqptr->rqAuth.UserDetailsLength = Length = sptr - cptr + 1;
         rqptr->rqAuth.UserDetailsPtr = VmGetHeap (rqptr, Length+1);
         strzcpy (rqptr->rqAuth.UserDetailsPtr, cptr, Length+1);
         return;
      }
      if (strsame (OutputPtr+4, "VMS-USER ", 9))
      {
         /* buffer the original remote user (the browser-supplied user id) */
         strzcpy (rqptr->rqAuth.RemoteUser, rqptr->RemoteUser,
                  sizeof(rqptr->rqAuth.RemoteUser));
         rqptr->rqAuth.RemoteUserLength = rqptr->RemoteUserLength;

         /* now replace the remote user with that supplied by the agent */
         zptr = (sptr = rqptr->RemoteUser) + sizeof(rqptr->RemoteUser)-1;
         for (cptr = OutputPtr+13; *cptr && ISLWS(*cptr); cptr++);
         while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr)
            *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            rqptr->RemoteUser[0] = '\0';
            rqptr->rqAuth.FinalStatus = STS$K_ERROR;
            ErrorGeneralOverflow (rqptr, FI_LI);
            return;
         }
         *sptr = '\0';
         rqptr->RemoteUserLength = sptr - rqptr->RemoteUser;

         if (VMSok (status = AuthVmsGetUai (rqptr, rqptr->RemoteUser)))
         {
            if (VMSok (status = AuthVmsVerifyUser (rqptr)))
            {
               /* authenticated ... user can do anything (the path allows!) */
               rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
               rqptr->rqAuth.SysUafAuthenticated = true;
            }
         }
         rqptr->rqAuth.FinalStatus = status;

         return;
      }
      AuthAgentCalloutResponseError (rqptr);
      return;
   }

   if (!strncmp (OutputPtr, "200 ", 4))
   {
      /* authenticated */
      rqptr->rqAuth.FinalStatus = SS$_NORMAL;
      if (strsame (OutputPtr+4, "FULL", 4))
      {
         rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
         if (rqptr->rqAuth.FinalStatus != STS$K_ERROR)
            rqptr->rqAuth.FinalStatus = SS$_NORMAL;
         return;
      }
      if (strsame (OutputPtr+4, "READ+WRITE", 10))
      {
         rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
         if (rqptr->rqAuth.FinalStatus != STS$K_ERROR)
            rqptr->rqAuth.FinalStatus = SS$_NORMAL;
         return;
      }
      if (strsame (OutputPtr+4, "READ", 4))
      {
         rqptr->rqAuth.UserCan = AUTH_READONLY_ACCESS;
         if (rqptr->rqAuth.FinalStatus != STS$K_ERROR)
            rqptr->rqAuth.FinalStatus = SS$_NORMAL;
         return;
      }
      if (strsame (OutputPtr+4, "WRITE", 5))
      {
         rqptr->rqAuth.UserCan = AUTH_WRITEONLY_ACCESS;
         if (rqptr->rqAuth.FinalStatus != STS$K_ERROR)
            rqptr->rqAuth.FinalStatus = SS$_NORMAL;
         return;
      }
      AuthAgentCalloutResponseError (rqptr);
      return;
   }

   if (!strncmp (OutputPtr, "302 ", 4))
   {
      /* redirection */
      rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_REDIRECT;
      cptr = OutputPtr + 4;
      while (*cptr && ISLWS(*cptr)) cptr++;
      for (sptr = cptr; *sptr && NOTEOL(*sptr) && !ISLWS(*sptr); sptr++);
      ResponseLocation (rqptr, cptr, sptr - cptr);
      return;
   }

   if (!strncmp (OutputPtr, "401 ", 4))
   {
      /* not authenticated */
      rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN;
      cptr = OutputPtr + 4;
      while (*cptr && ISLWS(*cptr)) cptr++;
      if (*cptr == '\"')
      {
         /* realm description */
         for (sptr = cptr+1; *sptr && *sptr != '\"'; sptr++);
         if (*sptr)
            zptr = sptr + 1;
         else
            zptr = NULL;
         *sptr = '\0';
         rqptr->rqAuth.RealmDescrPtr = sptr = VmGetHeap (rqptr, sptr-cptr);
         cptr++;
         /* trim leading white-space */
         while (*cptr && ISLWS(*cptr)) cptr++;
         while (*cptr) *sptr++ = *cptr++;
         *sptr = '\0';
         /* trim trailing white-space */
         if (sptr > rqptr->rqAuth.RealmDescrPtr) sptr--;
         while (sptr > rqptr->rqAuth.RealmDescrPtr && ISLWS(*sptr)) sptr--;
      }
      return;
   }

   if (!strncmp (OutputPtr, "403 ", 4))
   {
      /* not authorized */
      rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_OTHER;
      return;
   }

   if (!strncmp (OutputPtr, "500 ", 4))
   {
      /* report this error via the server */
      rqptr->rqAuth.FinalStatus = STS$K_ERROR;
      if (OutputPtr[OutputCount-1] == '\n') OutputPtr[--OutputCount] = '\0'; 
      ErrorGeneral (rqptr, OutputPtr+4, FI_LI);
      return;
   }

   AuthAgentCalloutResponseError (rqptr);
}

/*****************************************************************************/
/*
Simple way to generate this particular error message from various points.
*/ 

void AuthAgentCalloutResponseError (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthAgentCalloutResponseError()\n");

   ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_AGENT_RESPONSE), FI_LI);

   rqptr->rqAuth.FinalStatus = STS$K_ERROR;
}

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