/*****************************************************************************/ /* AuthAgent.c THE GNU GENERAL PUBLIC LICENSE APPLIES DOUBLY TO ANYTHING TO DO WITH AUTHENTICATION AND AUTHORIZATION! This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License, or any later version. > This package is distributed in the hope that it will be useful, > but WITHOUT ANY WARRANTY; without even the implied warranty of > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 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:' ........ '100 REDACT-SIZE:integer' .... VERSION HISTORY --------------- 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 #include #include #include /* VMS related header files */ #include #include #include /* 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; strcpy (Scratch, AUTH_AGENT_PATH); strcat (Scratch, AgentName); /* indicate it's an agent script, not a CGI-compliant script */ rqptr->rqCgi.AgentScript = true; 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; } /* indicate it's an agent script, not a CGI-compliant script */ rqptr->rqCgi.AgentScript = true; DclBegin (rqptr, AgentCalloutFunction, NULL, AgentScriptName, NULL, AgentFileName, NULL, &AuthAgentCallout, NULL); } /*****************************************************************************/ /* 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; /*********/ /* 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:", Length=8) || strsame (OutputPtr, "!CGIPLUS:", Length=9)) { /************/ /* cgiplus: */ /************/ for (cptr = OutputPtr+Length; *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; } /******************/ /* 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); strcpy (rqptr->rqAuth.ReasonPtr, cptr); 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) */ strcpy (rqptr->rqAuth.RemoteUser, rqptr->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 = 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); strcpy (rqptr->rqResponse.CookiePtr[idx], cptr); 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); strcpy (rqptr->rqAuth.UserDetailsPtr, cptr); return; } if (strsame (OutputPtr+4, "VMS-USER ", 9)) { /* buffer the original remote user (the browser-supplied user id) */ strcpy (rqptr->rqAuth.RemoteUser, rqptr->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; } /****************************************************************************/