/*****************************************************************************/
/*
                                 DECnet.c

This module implements a full multi-threaded, AST-driven, script execution via
DECnet.  Primarily this is to provide a substantially OSU-compatible scripting
enviroment, but does support WASD-style CGI scripting transparently over
DECnet. This code is functional but does not pretend to be highly optimized.

Two modes are supported.


WASD CGI MODE
-------------
These scripts behave in exactly the same manner as script process-based,
standard WASD CGI scripts. A stream of DCL commands create the CGI variable
environment and execute the script. The output stream can be CGI-compliant or
return a full HTTP response stream.

This is the default mode. If a mapped script contains a DECnet node component
but without a "TASK=" string the CGI mode is used.  It is also the mode for
any "TASK=" string containing "CGIWASD".


OSU MODE
--------
This mode emulates the OSU DECnet scripting environment.  It is based in large
part on reverse engineering the OSU 'script_execute.c' module, and lots of
trial-and-error!

This is the mode used when the mapped script contains a DECnet component and a
"TASK=" string that does not contain "WASD" as the final four characters.  In
general the string will be "TASK=WWWEXEC", the basic OSU script executive.
Any other task name will also be assumed to be OSU compliant.


MAPPING
-------
DECnet scripting is enabled for individual scripts by including a DECnet
component in the result of a "script" or "exec" mapping rule.

Examples ...

  exec /FRODO/* /FRODO::/cgi-bin/*
  exec /frodo/* /frodo::"task=cgiwasd"/cgi-bin/*
  exec /frodo/* /frodo::"0=cgiwasd"/cgi-bin/*

executes any WASD CGI script specified on node FRODO
(both forms result in the same script execution)

  exec /bilbo/osu/* /BILBO::"TASK=WWWEXEC"/*
  exec /bilbo/osu/* /BILBO::"0=WWWEXEC"/*

executes any OSU script specified on node BILBO


SCRIPTING AS A NON-HTTPD ACCOUNT
--------------------------------
The mapping rules

  set /frodo/ script=as=$
  set /frodo/ script=as=~
  set /frodo/ script=as=ACCOUNT-NAME

are all supported (with the necessary DECnet proxy access).

These functional equivalents are also available

  exec /frodo/* /frodo"$"::"0=cgiwasd"/cgi-bin/*
  exec /frodo/* /frodo"~"::"0=cgiwasd"/cgi-bin/*
  exec /frodo/* /frodo"ACCOUNT-NAME"::"0=cgiwasd"/cgi-bin/*


CONNECTION REUSE
----------------
As of v3.3 OSU provided the capability to reuse existing connections to the
WWWEXEC.COM task to provide multiple, consecutive requests for a single link.
An efficiency advantage gained through avoiding the overhead of connection
establishment with each script activation.  WASD provides similar functionality
for both OSU and CGI tasked scripts.

A list of connection structures is maintained. Some of these may have channels
assigned to established network tasks, others may not, just depending on
previous activity. The channel non-zero is used when scanning the list for an
appropriate connected task to reuse. Each established connection has a finite
lifetime after which the channel is deassigned effectively disconnecting the
link. Could have done the process control with an intermediate mailbox but
this will work as well most of the time almost as efficiently.

Related configuration parameters:

  [DECnetReuseLifeTime] .... minutes the connection to the task is maintained,
                             non-zero enables connection reuse by the server
  [DECnetConnectListMax] ... number of concurrent reuse connections the server
                             will maintain before reporting an error


VERSION HISTORY
---------------
12-OCT-2003  MGD  bugfix; allow for outstanding network writes during rundown
26-AUG-2003  MGD  bugfix; allow for outstanding body reads during task rundown
05-AUG-2003  MGD  bugfix; check for NULL pointer 'cnptr->ReuseConnection'
27-JUL-2003  MGD  revise reporting format
                  revise script activation code (include .CLD)
                  bugfix; DECnetCgiDialog() not strict wait for EOF sentinal
22-APR-2003  MGD  bugfix; (and refine) DECnetSupervisor()
15-MAR-2003  MGD  script=as=$? to indicate optional use of SYSUAF username
                  implement authorization "scriptas" keyword directive
30-JAN-2003  MGD  build up 'records' from single byte output streams
                  (see description of purpose and functionality in DCL.C)
                  bugfix; DECnetFindCgiScript() foreign verb creation
08-OCT-2002  MGD  implement 'HttpdScriptAsUserName' for DECnet also
14-SEP-2002  MGD  support 'script=as=' functionality, plus DECnet variants
                  NODE"$":: substitutes SYSUAF authenticated username into
                  access string (for proxy access to account) and
                  NODE"~":: substitutes '/~username/' username in same way
02-FEB-2002  MGD  rework due to request body processing changes
28-OCT-2001  MGD  "TASK=CGI..", "0=CGI.." now recognised as CGI dialog
29-SEP-2001  MGD  instance support
04-AUG-2001  MGD  support module WATCHing
26-APR-2001  MGD  use HttpdTick() to drive DECnetSupervisor()
26-JAN-2001  MGD  bugfix; force CgiOutput() record/stream mode
22-JUN-2000  MGD  bugfix; HEAD requests specifying content-length
09-MAY-2000  MGD  bugfix; sys$assign() error had no DECnetEnd()
08-APR-2000  MGD  if(!Config.cfScript.Enabled)
04-MAR-2000  MGD  use NetWriteFaol(), et.al.
13-JAN-2000  MGD  add OPCOM messages
28-NOV-1999  MGD  relocate CgiGenerateVariables()
19-JUN-1999  MGD  bugfix; remove SysDclAst() from DECnetBegin(),
                  general refinement
29-JAN-1999  MGD  add <DNETRECMODE2> from OSU 3.3b
06-DEC-1999  MGD  bugfix; initial OSU dialog send mapped path not original path
07-NOV-1998  MGD  WATCH facility,
                  client port number now available for OSU
15-AUG-1998  MGD  reuse network task-connections,
                  report status 500/501 if script returns no output,
                  report unknown OSU dialog tags as an error,
                  return translated path in Unix-style syntax
16-DEC-1997  MGD  initial development for v5.0
*/
/*****************************************************************************/

#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 <stdio.h>
#include <string.h>

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

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

#define WASD_MODULE "DECNET"

/**********/
/* macros */
/**********/

#define DECNET_CONTENT_MAX 1024

#define DECNET_SUPERVISOR_TICK_MIN 10
#define DECNET_SUPERVISOR_TICK_MAX 30

/* space for adding file types (e.g. ".COM") onto script specifications */
#define FIND_SCRIPT_OVERHEAD 48

#define DECNET_TASK_CGI "CGIWASD"
#define DECNET_TASK_OSU "WWWEXEC"

#define SCRIPT_CGI      1
#define SCRIPT_OSU      2

#define CGI_BEGIN       1
#define CGI_REUSE       2
#define CGI_DCL         3
#define CGI_OUTPUT      4

#define OSU_BEGIN       1
#define OSU_REUSE       2
#define OSU_DIALOG      3
#define OSU_DNET_HDR    4
#define OSU_DNET_INPUT  5
#define OSU_DNET_XLATE  6
#define OSU_OUTPUT_RAW  7
#define OSU_OUTPUT_REC  8
#define OSU_DNETTEXT    9

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

char  ErrorDECnetReuseListExhausted [] = "DECnet reuse list exhausted.",
      ErrorOsuImplementation [] = "Possible OSU implementation problem!",
      ErrorOsuNoInvCache [] =
         "OSU &quot;invalidate cache&quot; dialog not implemented.",
      ErrorOsuNoManage [] = "OSU &quot;manage&quot; dialog not implemented.",
      ErrorOsuUnknown [] = "Unknown OSU dialog ... &quot;<TT>!&;AZ</TT>&quot;";

LIST_HEAD  DECnetConnectList;

int  DECnetConnectListCount,
     DECnetPurgeAllConnectCount;

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

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

extern int  EfnNoWait,
            HttpdTickSecond,
            NetReadBufferSize,
            OpcomMessages;

extern char  CliScriptAs[],
             DclCgiVariablePrefix[],
             ErrorSanityCheck[],
             HttpProtocol[],
             HttpdScriptAsUserName[],
             ServerHostPort[],
             SoftwareID[],
             Utility[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_PROCESS  HttpdProcess;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initiate a connection to a DECnet node using the connection details specified
in 'ConnectString'.
*/ 
 
DECnetBegin
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *MappedScript,
char *ScriptRunTime
)
{
   int  status;
   char  *cptr, *sptr, *uptr, *zptr,
         *ScriptAsPtr;
   REQUEST_AST AstFunction;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetBegin() !&A !&Z !&Z",
                 NextTaskFunction, MappedScript, ScriptRunTime);

   if (!Config.cfScript.Enabled)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   if (!rqptr->AccountingDone++)
      InstanceGblSecIncrLong (&AccountingPtr->DoDECnetCount);

   /* set up the task structure */
   if (!rqptr->DECnetTaskPtr)
   {
      rqptr->DECnetTaskPtr = tkptr = (DECNET_TASK*)
         VmGetHeap (rqptr, sizeof(DECNET_TASK));
   }
   else
   {
      tkptr = rqptr->DECnetTaskPtr;
      memset (tkptr, 0, sizeof(DECNET_TASK));
   }
   tkptr->RequestPtr = rqptr;
   tkptr->NextTaskFunction = NextTaskFunction;
   tkptr->WatchItem = rqptr->WatchItem;

   cptr = MappedScript;
   zptr = (sptr = tkptr->MappedScript) + sizeof(tkptr->MappedScript);
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      DECnetEnd (rqptr);
      return;
   }
   *sptr = '\0';

   if (ScriptRunTime && ScriptRunTime[0])
   {
      cptr = ScriptRunTime;
      zptr = (sptr = tkptr->ScriptRunTime) + sizeof(tkptr->ScriptRunTime);
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr)
      {
         ErrorGeneralOverflow (rqptr, FI_LI);
         DECnetEnd (rqptr);
         return;
      }
      *sptr = '\0';
   }
   else
      tkptr->ScriptRunTime[0] = '\0';

   /* reset CGI output processing (both OSU and CGI use this) */
   CgiOutput (rqptr, NULL, 0);

   /*******************************/
   /* generate the connect string */
   /*******************************/

   ScriptAsPtr = NULL;

   zptr = (sptr = tkptr->ConnectString) + sizeof(tkptr->ConnectString);
   for (cptr = tkptr->MappedScript;
        *cptr && *cptr != ':' && *cptr != '\"' && sptr < zptr;
        *sptr++ = *cptr++);
   if (*cptr == '\"')
   {
      /* account detail string */
      if (sptr < zptr) *sptr++ = *cptr++;
      if (*cptr == '$')
      {
         /* use the SYSUAF authenticated username for proxy access */
         if (rqptr->RemoteUser[0] &&
             rqptr->rqAuth.SysUafAuthenticated)
         {
            ScriptAsPtr = uptr = rqptr->RemoteUser;
            while (*uptr && sptr < zptr) *sptr++ = *uptr++;
         }
         else
         {
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_REQUIRED), FI_LI);
            DECnetEnd (rqptr);
            return;
         }
         while (*cptr && *cptr != '\"') cptr++;
      }
      else
      if (*cptr == '~')
      {
         /* use the URI /~username/ for proxy access */
         uptr = rqptr->rqHeader.RequestUriPtr;
         if (*(unsigned short*)uptr != '/~')
         {
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_MAPPING_DENIED_RULE), FI_LI);
            DECnetEnd (rqptr);
            return;
         }
         uptr += 2;
         ScriptAsPtr = uptr;
         while (*uptr && *uptr != '/' && sptr < zptr) *sptr++ = *uptr++;
         while (*cptr && *cptr != '\"') cptr++;
      }
      while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++;
   }
   else
   if (rqptr->rqAuth.VmsUserScriptAs)
   {
      /* authorization rule has precendence over mapping rule */
      if (rqptr->RemoteUser[0] &&
          rqptr->rqAuth.SysUafAuthenticated)
      {
         ScriptAsPtr = uptr = rqptr->RemoteUser;
         if (sptr < zptr) *sptr++ = '\"';
         while (*uptr && sptr < zptr) *sptr++ = *uptr++;
         if (sptr < zptr) *sptr++ = '\"';
      }
      else
      {
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_REQUIRED), FI_LI);
         DECnetEnd (rqptr);
         return;
      }
   }
   else
   if (rqptr->rqPathSet.ScriptAsPtr)
   {
      /* no account detail string, but the path is set script=as= */
      if (((unsigned short*)rqptr->rqPathSet.ScriptAsPtr)[0] == '$?')
      {
         /* optionally use a SYSUAF authenticated username for proxy access */
         if (rqptr->RemoteUser[0] &&
             rqptr->rqAuth.SysUafAuthenticated)
         {
            ScriptAsPtr = uptr = rqptr->RemoteUser;
            if (sptr < zptr) *sptr++ = '\"';
            while (*uptr && sptr < zptr) *sptr++ = *uptr++;
            if (sptr < zptr) *sptr++ = '\"';
         }
      }
      else
      if (rqptr->rqPathSet.ScriptAsPtr[0] == '$')
      {
         /* must use the SYSUAF authenticated username for proxy access */
         if (rqptr->RemoteUser[0] &&
             rqptr->rqAuth.SysUafAuthenticated)
         {
            ScriptAsPtr = uptr = rqptr->RemoteUser;
            if (sptr < zptr) *sptr++ = '\"';
            while (*uptr && sptr < zptr) *sptr++ = *uptr++;
            if (sptr < zptr) *sptr++ = '\"';
         }
         else
         {
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_REQUIRED), FI_LI);
            DECnetEnd (rqptr);
            return;
         }
      }
      else
      if (rqptr->rqPathSet.ScriptAsPtr[0] == '~')
      {
         /* use the URI /~username/ for proxy access */
         uptr = rqptr->rqHeader.RequestUriPtr;
         if (*(unsigned short*)uptr != '/~')
         {
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_MAPPING_DENIED_RULE), FI_LI);
            DECnetEnd (rqptr);
            return;
         }
         uptr += 2;
         ScriptAsPtr = uptr;
         if (sptr < zptr) *sptr++ = '\"';
         while (*uptr && *uptr != '/' && sptr < zptr) *sptr++ = *uptr++;
         if (sptr < zptr) *sptr++ = '\"';
      }
      else
      {
         /* an explicitly specified username */
         ScriptAsPtr = uptr = rqptr->rqPathSet.ScriptAsPtr;
         if (sptr < zptr) *sptr++ = '\"';
         while (*uptr && sptr < zptr) *sptr++ = *uptr++;
         if (sptr < zptr) *sptr++ = '\"';
      }
   }
   else
   if (HttpdScriptAsUserName[0])
   {
      ScriptAsPtr = uptr = HttpdScriptAsUserName;
      if (sptr < zptr) *sptr++ = '\"';
      while (*uptr && sptr < zptr) *sptr++ = *uptr++;
      if (sptr < zptr) *sptr++ = '\"';
   }

   if (*(unsigned short*)cptr != '::')
   {
      /* shouldn't be in this function if no DECnet in specification! */
      ErrorGeneral (rqptr, ErrorSanityCheck, FI_LI);
      DECnetEnd (rqptr);
      return;
   }

   /* isolate the DECnet component from the script file specification */
   if (sptr < zptr) *sptr++ = *cptr++;
   if (sptr < zptr) *sptr++ = *cptr++;
   if (*cptr == '\"')
   {
      /* mapping rule is supplying the DECnet task information */
      if (sptr < zptr) *sptr++ = *cptr++;
      while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      tkptr->ScriptPtr = cptr + 1;
      DECnetSetDialog (tkptr);
   }
   else
   {
      /* supply the default WASD CGI DECnet task */
      tkptr->ScriptPtr = cptr;
      cptr = "\"TASK=";
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      cptr = DECNET_TASK_CGI;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = '\"';
      tkptr->ScriptType = SCRIPT_CGI;
   }

   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      DECnetEnd (rqptr);
      return;
   }
   *sptr = '\0';

   tkptr->ConnectStringLength = sptr - tkptr->ConnectString;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_DECNET,
                 "!AZ !AZ", tkptr->ConnectString, tkptr->ScriptPtr);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
   {
      if (ScriptAsPtr)
      {
         /* allow for possible URI '/~username/blah' */
         for (uptr = ScriptAsPtr; *uptr && *uptr != '/'; uptr++);
         switch (tkptr->ScriptType)
         {
            case SCRIPT_CGI :
               WatchThis (rqptr, FI_LI, WATCH_RESPONSE,
                          "SCRIPT as !#AZ CGI !AZ",
                          uptr - ScriptAsPtr, ScriptAsPtr, MappedScript);
               break;
            case SCRIPT_OSU :
               WatchThis (rqptr, FI_LI, WATCH_RESPONSE,
                          "SCRIPT as !#AZ OSU !AZ",
                          uptr - ScriptAsPtr, ScriptAsPtr, MappedScript);
               break;
            default :
               ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         }
      }
      else
      {
         switch (tkptr->ScriptType)
         {
            case SCRIPT_CGI :
               WatchThis (rqptr, FI_LI, WATCH_RESPONSE,
                          "SCRIPT CGI !AZ", MappedScript);
               break;
            case SCRIPT_OSU :
               WatchThis (rqptr, FI_LI, WATCH_RESPONSE,
                          "SCRIPT OSU !AZ", MappedScript);
               break;
            default :
               ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         }
      }
   }

   /**********************/
   /* access DECnet task */
   /**********************/

   DECnetConnectBegin(tkptr);
}

/*****************************************************************************/
/*
End of request's DECnet task.  If outstanding I/O cancel and wait for that to
complete.  Disassociate (disconnect) the request and the connection structure.
Queue an AST for the next task.
*/ 
 
DECnetEnd (REQUEST_STRUCT *rqptr)

{
   int  status;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetEnd() !&F !UL !UL !UL", &DECnetEnd,
                 rqptr->DECnetTaskPtr->QueuedBodyRead,
                 rqptr->DECnetTaskPtr->QueuedNetWrite,
                 rqptr->DECnetTaskPtr->QueuedDECnetIO);

   tkptr = rqptr->DECnetTaskPtr;

   if (tkptr->QueuedDECnetIO)
   {
      sys$cancel (tkptr->DECnetChannel);
      return;
   }

   if (tkptr->QueuedBodyRead)
   {
      if (rqptr->rqTmr.TerminatedCount) NetCloseSocket (rqptr);
      return;
   }

   if (tkptr->QueuedNetWrite)
   {
      if (rqptr->rqTmr.TerminatedCount) NetCloseSocket (rqptr);
      return;
   }

   if (rqptr->rqTmr.TerminatedCount)
      if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false;

   DECnetConnectEnd (tkptr);

   if (!tkptr->ScriptResponded)
   {
      /* hmmm, script has not provided any output! */
      if (tkptr->RequestPtr->rqHeader.Method == HTTP_METHOD_GET)
      {
         /* blame script for general GET method failures */
         tkptr->RequestPtr->rqResponse.HttpStatus = 502;
         ErrorGeneral (tkptr->RequestPtr,
            MsgFor(tkptr->RequestPtr,MSG_SCRIPT_RESPONSE_ERROR), FI_LI);
      }
      else
      {
         /* other methods are probably not implemented by the script */
         tkptr->RequestPtr->rqResponse.HttpStatus = 501;
         ErrorGeneral (tkptr->RequestPtr,
            MsgFor(tkptr->RequestPtr,MSG_REQUEST_METHOD), FI_LI);
      }
   }

   tkptr->WatchItem = 0;

   /* declare the next task */
   SysDclAst (tkptr->NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Examine the connect string to determine the scripting dialog required (CGI or
OSU). Set a flag in the task structure to indicate the type of dialog.
*/ 
 
DECnetSetDialog (DECNET_TASK *tkptr)

{
   char  *cptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_DECNET,
                 "DECnetSetDialog() !&Z", tkptr->ConnectString);

   cptr = tkptr->ConnectString;
   while (*cptr)
   {
      if (strsame (cptr, "\"TASK=CGI", 9) || strsame (cptr, "\"0=CGI", 6))
      {
         tkptr->ScriptType = SCRIPT_CGI;
         return;
      }
      cptr++;
   }

   /* any task name not otherwise recognized becomes an OSU-based script */
   tkptr->ScriptType = SCRIPT_OSU;
}

/*****************************************************************************/
/*
Set up a connection between the request and the DECnet task involved. Scan
through the list of already created connection items, looking for an item,
idle but network-connected task-string of the required flavour. If none is
found scan again, this time looking for any unused item (idle, not connected).
If none is found create a new one and place it in the list. If already
network-connected to the task just increment a counter and queue a "fake" AST
to the connection-established function. If not already network-connected
assign a channel and queue an I/O to connect to the desired network task, a
real AST being deliver this time to the connection-established function.
*/ 
 
DECnetConnectBegin (DECNET_TASK *tkptr)

{
   static $DESCRIPTOR (NetDeviceDsc, "_NET:");
   static $DESCRIPTOR (NcbDsc, "");

   int  status;
   DECNET_CONNECT  *cnptr;
   LIST_ENTRY  *leptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_DECNET,
                 "DECnetConnectBegin()");

   cnptr = NULL;

   /*****************************************/
   /* look for available AND connected item */
   /*****************************************/

   for (leptr = DECnetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      cnptr = (DECNET_CONNECT*)leptr;

      if (!cnptr->DECnetChannel ||
          !cnptr->ReuseConnection ||
          cnptr->RequestPtr ||
          !strsame (tkptr->ConnectString, cnptr->ConnectString, -1))
      {
         cnptr = NULL;
         continue;
      }

      break;
   }

   if (!cnptr)
   {
      /****************************************/
      /* look for available disconnected item */
      /****************************************/

      for (leptr = DECnetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr)
      {
         cnptr = (DECNET_CONNECT*)leptr;

         if (cnptr->DECnetChannel)
         {
            cnptr = NULL;
            continue;
         }

         break;
      }
   }

   if (!cnptr)
   {
      /********************************/
      /* create a new connection item */
      /********************************/

      if (Config.cfScript.DECnetConnectListMax &&
          DECnetConnectListCount >= Config.cfScript.DECnetConnectListMax)
      {
         tkptr->RequestPtr->rqResponse.HttpStatus = 500;
         ErrorGeneral (tkptr->RequestPtr, ErrorDECnetReuseListExhausted, FI_LI);
         DECnetEnd (tkptr->RequestPtr);
         return;
      }
      DECnetConnectListCount++;

      cnptr = VmGet (sizeof(DECNET_CONNECT));
      ListAddTail (&DECnetConnectList, cnptr);
   }

   /*******************/
   /* initialize item */
   /*******************/

   if (cnptr->DECnetChannel)
   {
      /* (probably) still connected */
      cnptr->ReUsageCount++;
   }
   else
   {
      /* assign channel for new connection */
      status = sys$assign (&NetDeviceDsc, &cnptr->DECnetChannel, 0, 0);
      if (VMSnok (status))
      {
         cnptr->DECnetChannel = 0;
         tkptr->RequestPtr->rqResponse.HttpStatus = 502;
         tkptr->RequestPtr->rqResponse.ErrorTextPtr =
            tkptr->RequestPtr->ScriptName;
         ErrorVmsStatus (tkptr->RequestPtr, status, FI_LI);
         DECnetEnd (tkptr->RequestPtr);
         return;
      }

      cnptr->ReUsageCount = 0;
      cnptr->ReuseConnection = false;
      strcpy (cnptr->ConnectString, tkptr->ConnectString);
   }

   /* associate the request, task and connect instance */
   tkptr->cnptr = cnptr;
   cnptr->RequestPtr = tkptr->RequestPtr;
   tkptr->DECnetChannel = cnptr->DECnetChannel;
   tkptr->BuildRecords = tkptr->ScriptResponded = false;
   tkptr->BuildCount = 0;
   cnptr->UsageCount++;
   memcpy (&cnptr->LastUsedBinaryTime, &tkptr->RequestPtr->rqTime.Vms64bit, 8);

   /* for the first call to DECnetRead() ensure status check is OK */
   tkptr->RequestPtr->rqNet.WriteIOsb.Status = SS$_NORMAL;

   if (cnptr->ReuseConnection)
   {
      /******************************/
      /* (probably) still connected */
      /******************************/

      /* restart the connected task lifetime */
      tkptr->cnptr->LifeTimeSecond =
         HttpdTickSecond + Config.cfScript.DECnetReuseLifeTime;

      switch (tkptr->ScriptType)
      {
         case SCRIPT_CGI :

            /* reusing, check the connection, ASTs to DECnetCgiDialog() */
            tkptr->CgiDialogState = CGI_REUSE;
            DECnetConnectProbeCgi (tkptr);
            return;

         case SCRIPT_OSU :

            /* reusing, check the connection, ASTs to DECnetOsuDialog() */
            tkptr->OsuDialogState = OSU_REUSE;
            DECnetConnectProbeOsu (tkptr);
            return;

         default :

            ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      }
   }

   /**************************/
   /* connect to DECnet task */
   /**************************/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchDataFormatted ("{!UL}!-!#AZ\n", tkptr->ConnectStringLength,
                                           tkptr->ConnectString);

   /* apply that to the network Connect Block descriptor */
   NcbDsc.dsc$w_length = tkptr->ConnectStringLength;
   NcbDsc.dsc$a_pointer = tkptr->ConnectString;

   tkptr->QueuedDECnetIO++;
   status = sys$qio (EfnNoWait, tkptr->DECnetChannel, IO$_ACCESS,
                     &tkptr->DECnetConnectIOsb,
                     &DECnetConnectAst, tkptr->RequestPtr,
                     0, &NcbDsc, 0, 0, 0, 0);
   if (VMSok (status)) return;

   /* report error via AST */
   tkptr->DECnetConnectIOsb.Status = status;
   tkptr->DECnetConnectIOsb.Count = 0;
   SysDclAst (&DECnetConnectAst, tkptr->RequestPtr);
}

/*****************************************************************************/
/*
The connection request to the specified DECnet task has completed (real or
"faked").  Check the status and report any error.  If no error then initialize
the required script dialog and call the appropriate function to begin it. 
Connection that may be reused begin with the ..._REUSE dialog, which probes the
connection ensuring it's still valid.  Non-reused connections begin with the
appropriate ..._BEGIN dialog.
*/ 
 
DECnetConnectAst (REQUEST_STRUCT *rqptr)

{
   int  status;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET, "DECnetConnectAst() !&X",
                 rqptr->DECnetTaskPtr->DECnetConnectIOsb.Status);

   tkptr = rqptr->DECnetTaskPtr;

   if (tkptr->QueuedDECnetIO) tkptr->QueuedDECnetIO--;

   if (VMSnok (tkptr->DECnetConnectIOsb.Status))
   {
      if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false;
      tkptr->RequestPtr->rqResponse.HttpStatus = 502;
      rqptr->rqResponse.ErrorTextPtr = rqptr->ScriptName;
      ErrorVmsStatus (rqptr, tkptr->DECnetConnectIOsb.Status, FI_LI);
      DECnetEnd (rqptr);
      return;
   }

   if (Config.cfScript.DECnetReuseLifeTime)
   {
      /* reuse enabled, plus one allows at least that period */
      tkptr->cnptr->LifeTimeSecond =
         HttpdTickSecond + Config.cfScript.DECnetReuseLifeTime+1;
      /* start the supervisor ticking for connected task lifetimes */
      DECnetSupervisor (0);
   }

   switch (tkptr->ScriptType)
   {
      case SCRIPT_CGI :

         /* just begin the CGI dialog phase */
         tkptr->CgiDialogState = CGI_BEGIN;
         DECnetCgiDialog (rqptr);
         return;

      case SCRIPT_OSU :

         /* just begin the OSU dialog phase */
         tkptr->OsuDialogState = OSU_BEGIN;
         DECnetOsuDialog (rqptr);
         return;

      default :

         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }
}

/*****************************************************************************/
/*
Write just a DCL comment character to the script network process (will be
ignored by DCL) to check that the connection still exists.  Delivers an AST to
DECnetCGIDialog() which checks the success of the probe.
*/ 
 
DECnetConnectProbeCgi (DECNET_TASK *tkptr)
{
   int  status;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_DECNET,
                 "DECnetConnectProbeCgi()");

   tkptr->QueuedDECnetIO++;
   status = sys$qio (EfnNoWait, tkptr->DECnetChannel, IO$_WRITEVBLK,
                     &tkptr->DECnetWriteIOsb, &DECnetCgiDialog,
                     tkptr->RequestPtr, "!", 1, 0, 0, 0, 0);
   if (VMSok (status)) return;

   /* report error via AST */
   tkptr->DECnetWriteIOsb.Status = status;
   tkptr->DECnetWriteIOsb.Count = 0;
   SysDclAst (&DECnetCgiDialog, tkptr->RequestPtr);
}

/*****************************************************************************/
/*
Write a <DNETREUSE> dialog tag to the script (will be ignored by the task
procedure WWWEXEC.COM) to check that the connection still exists.  Delivers an
AST to DECnetOsuDialog() which checks the success of the probe.
*/ 
 
DECnetConnectProbeOsu (DECNET_TASK *tkptr)
{
   int  status;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_DECNET,
                 "DECnetConnectProbeOsu()");

   tkptr->QueuedDECnetIO++;
   status = sys$qio (EfnNoWait, tkptr->DECnetChannel, IO$_WRITEVBLK,
                     &tkptr->DECnetWriteIOsb,
                     &DECnetOsuDialog, tkptr->RequestPtr,
                     "<DNETREUSE>", 11, 0, 0, 0, 0);
   if (VMSok (status)) return;

   /* report error via AST */
   tkptr->DECnetWriteIOsb.Status = status;
   tkptr->DECnetWriteIOsb.Count = 0;
   SysDclAst (&DECnetOsuDialog, tkptr->RequestPtr);
}

/*****************************************************************************/
/*
Conclude a request's use of a DECnet connect item.  If the connection can
reused then leave the channel assigned, otherwise deassign it disconnecting
from the network task.
*/ 
 
DECnetConnectEnd (DECNET_TASK *tkptr)

{
   int  status;
   DECNET_CONNECT  *cnptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_DECNET,
                 "DECnetConnectEnd()");

   if (!(cnptr = tkptr->cnptr)) return;

   if (!cnptr->ReuseConnection ||
       cnptr->IsMarkedForDisconnect)
   {
      status = sys$dassgn (cnptr->DECnetChannel);
      cnptr->DECnetChannel = cnptr->LifeTimeSecond = 0;
   }

   /* disassociate the request, task and connect instance */
   tkptr->cnptr = cnptr->RequestPtr = NULL;
}

/*****************************************************************************/
/*
Write a record to the remote DECnet task.  AST function will process any
errors at all.
*/ 
 
DECnetWrite
(
REQUEST_STRUCT *rqptr,
char *DataPtr,
int DataLength
)
{
   int  status;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetWrite() 0x!8XL !UL !UL",
                 DataPtr, DataLength, rqptr->DECnetTaskPtr->QueuedDECnetIO);

   tkptr = rqptr->DECnetTaskPtr;

   if (DataLength < 0) DataLength = strlen(DataPtr);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_DECNET))
   {
      WatchThis (rqptr, FI_LI, WATCH_DECNET,
                 "WRITE !UL bytes", DataLength);
      WatchDataDump (DataPtr, DataLength);
   }

   tkptr->QueuedDECnetIO++;
   status = sys$qio (EfnNoWait, tkptr->DECnetChannel, IO$_WRITEVBLK,
                     &tkptr->DECnetWriteIOsb, &DECnetWriteAst, rqptr,
                     DataPtr, DataLength, 0, 0, 0, 0);
   if (VMSok (status)) return;

   /* report error via AST */
   tkptr->DECnetWriteIOsb.Status = status;
   tkptr->DECnetWriteIOsb.Count = 0;
   SysDclAst (&DECnetWriteAst, rqptr);
}

/*****************************************************************************/
/*
*/ 
 
DECnetWriteAst (REQUEST_STRUCT *rqptr)

{
   int  status;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetWriteAst() !&F !UL !&X",
                 &DECnetWriteAst, rqptr->DECnetTaskPtr->QueuedDECnetIO,
                 rqptr->DECnetTaskPtr->DECnetWriteIOsb.Status);

   tkptr = rqptr->DECnetTaskPtr;

   if (tkptr->QueuedDECnetIO) tkptr->QueuedDECnetIO--;

   /* reusing the IOsb, expect it to occasionally have been initialized! */
   if (!tkptr->DECnetWriteIOsb.Status)
      tkptr->DECnetWriteIOsb.Status = SS$_NORMAL;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_DECNET))
   {
      if (VMSnok (tkptr->DECnetWriteIOsb.Status))
         WatchThis (rqptr, FI_LI, WATCH_DECNET,
                    "WRITE !&S %!&M",
                    tkptr->DECnetWriteIOsb.Status,
                    tkptr->DECnetWriteIOsb.Status);
      else
         WatchThis (rqptr, FI_LI, WATCH_DECNET,
                    "WRITE !&S",
                    tkptr->DECnetWriteIOsb.Status);
   }

   if (VMSnok (tkptr->DECnetWriteIOsb.Status))
   {
      if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false;
      rqptr->rqResponse.ErrorTextPtr = rqptr->ScriptName;
      ErrorVmsStatus (rqptr, tkptr->DECnetWriteIOsb.Status, FI_LI);
      DECnetEnd (rqptr);
      return;
   }
}

/*****************************************************************************/
/*
Write a chunk of the request header or body to the script. When the content
data is exhausted write an empty record as and end-of-data marker. This must
be checked for and detected by the script as there is no end-of-file
associated with this stream!
*/ 
 
DECnetWriteRequest (REQUEST_STRUCT *rqptr)

{
   int  status,
        DataLength;
   char  *cptr,
         *DataPtr;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetWriteRequest() !&F 0x!8XL !UL !UL",
                 &DECnetWriteRequest, rqptr->DECnetTaskPtr->ContentPtr,
                 rqptr->DECnetTaskPtr->ContentCount,
                 rqptr->DECnetTaskPtr->QueuedDECnetIO);

   tkptr = rqptr->DECnetTaskPtr;

   if (!tkptr->ContentPtr)
   {
      switch (tkptr->ScriptType)
      {
         case SCRIPT_CGI :

            if (rqptr->rqBody.DataStatus)
               BodyRead (rqptr);
            else
               BodyReadBegin (rqptr, DECnetWriteRequestBody, NULL);
            tkptr->QueuedBodyRead++;

            return;  /* NOTE: RETURN! */

         case SCRIPT_OSU :

            switch (tkptr->OsuDialogState)
            {
               case OSU_DNET_HDR :

                  /* request header (excluding first line) */
                  cptr = rqptr->rqHeader.RequestHeaderPtr;
                  while (NOTEOL(*cptr)) cptr++;
                  if (*cptr == '\r') cptr++;
                  if (*cptr == '\n') cptr++;
                  tkptr->ContentPtr = cptr;
                  tkptr->ContentCount = -1;

                  break;  /* FALL THRU TO HEADER PROCESSING */

               case OSU_DNET_INPUT :

                  if (rqptr->rqBody.DataStatus)
                     BodyRead (rqptr);
                  else
                     BodyReadBegin (rqptr, DECnetWriteRequestBody, NULL);
                  tkptr->QueuedBodyRead++;

                  return;  /* NOTE: RETURN! */

               default :
                  ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
            }
            break;

         default :
            ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      }
   }

   /***************************/
   /* send header data (only) */
   /***************************/

   /* OSU gets the header line-by-line (excluding carriage-control) */
   for (cptr = DataPtr = tkptr->ContentPtr; NOTEOL(*cptr); cptr++);
   DataLength = cptr - DataPtr;
   if (*cptr == '\r') cptr++;
   if (*cptr == '\n') cptr++;
   /* if end-of-header blank line, indicate this */
   if (*DataPtr == '\r' || *DataPtr == '\n')
      tkptr->ContentPtr = NULL;
   else
      tkptr->ContentPtr = cptr;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_DECNET))
   {
      WatchThis (rqptr, FI_LI, WATCH_DECNET,
                 "WRITE !UL bytes", DataLength);
      WatchDataDump (DataPtr, DataLength);
   }

   tkptr->QueuedDECnetIO++;
   status = sys$qio (EfnNoWait, tkptr->DECnetChannel, IO$_WRITEVBLK,
                     &tkptr->DECnetWriteIOsb, &DECnetWriteRequestAst, rqptr,
                     DataPtr, DataLength, 0, 0, 0, 0);
   if (VMSok (status)) return;

   /* report error via AST */
   tkptr->DECnetWriteIOsb.Status = status;
   tkptr->DECnetWriteIOsb.Count = 0;
   SysDclAst (&DECnetWriteRequestAst, rqptr);
}

/*****************************************************************************/
/*
Write a chunk of the request header or body to the script. When the content
data is exhausted write an empty record as an end-of-data marker. This must
be checked for and detected by the script as there is no end-of-file
associated with this stream!
*/ 
 
DECnetWriteRequestBody (REQUEST_STRUCT *rqptr)

{
   int  status,
        DataLength;
   char  *cptr,
         *DataPtr;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetWriteRequestBody() !&F !&S 0x!8XL !UL !UL !UL",
                 &DECnetWriteRequestBody, rqptr->rqBody.DataStatus,
                 rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount,
                 rqptr->DECnetTaskPtr->QueuedBodyRead,
                 rqptr->DECnetTaskPtr->QueuedDECnetIO);

   tkptr = rqptr->DECnetTaskPtr;

   if (tkptr->QueuedBodyRead) tkptr->QueuedBodyRead--;

   if (VMSok (rqptr->rqBody.DataStatus))
   {
      DataPtr = rqptr->rqBody.DataPtr;
      DataLength = rqptr->rqBody.DataCount;
   }
   else
   if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
   {
      DataPtr = "";
      DataLength = 0;
   }
   else
   {
      /* report error via AST */
      tkptr->DECnetWriteIOsb.Status = rqptr->rqBody.DataStatus;
      tkptr->DECnetWriteIOsb.Count = 0;
      tkptr->QueuedDECnetIO++;
      SysDclAst (&DECnetWriteRequestAst, rqptr);
      return;
   }

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_DECNET))
   {
      WatchThis (rqptr, FI_LI, WATCH_DECNET, "WRITE !UL bytes", DataLength);
      WatchDataDump (DataPtr, DataLength);
   }

   tkptr->QueuedDECnetIO++;
   status = sys$qio (EfnNoWait, tkptr->DECnetChannel, IO$_WRITEVBLK,
                     &tkptr->DECnetWriteIOsb, &DECnetWriteRequestAst, rqptr,
                     DataPtr, DataLength, 0, 0, 0, 0);
   if (VMSok (status)) return;

   /* report error via AST */
   tkptr->DECnetWriteIOsb.Status = status;
   tkptr->DECnetWriteIOsb.Count = 0;
   SysDclAst (&DECnetWriteRequestAst, rqptr);
}

/*****************************************************************************/
/*
A content write has completed.  Check the IO status block for errors.  If no
error then check for more content to be written, if so the write it!
*/ 
 
DECnetWriteRequestAst (REQUEST_STRUCT *rqptr)

{
   int  status;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetWriteRequestAst() !&F !UL !&X !UL",
                 &DECnetWriteRequestAst, rqptr->DECnetTaskPtr->QueuedDECnetIO,
                 rqptr->DECnetTaskPtr->DECnetWriteIOsb.Status,
                 rqptr->DECnetTaskPtr->DECnetWriteIOsb.Count);

   tkptr = rqptr->DECnetTaskPtr;

   if (tkptr->QueuedDECnetIO) tkptr->QueuedDECnetIO--;

   /* reusing the IOsb, expect it to occasionally have been initialized! */
   if (!tkptr->DECnetWriteIOsb.Status)
      tkptr->DECnetWriteIOsb.Status = SS$_NORMAL;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_DECNET))
   {
      if (VMSnok (tkptr->DECnetWriteIOsb.Status))
         WatchThis (rqptr, FI_LI, WATCH_DECNET,
                    "WRITE !&S %!&M",
                    tkptr->DECnetWriteIOsb.Status,
                    tkptr->DECnetWriteIOsb.Status);
      else
         WatchThis (rqptr, FI_LI, WATCH_DECNET,
                    "WRITE !&S",
                    tkptr->DECnetWriteIOsb.Status);
   }

   if (VMSnok (tkptr->DECnetWriteIOsb.Status))
   {
      if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false;
      rqptr->rqResponse.ErrorTextPtr = rqptr->ScriptName;
      ErrorVmsStatus (rqptr, tkptr->DECnetWriteIOsb.Status, FI_LI);
      DECnetEnd (rqptr);
      return;
   }

   if (tkptr->ScriptType == SCRIPT_OSU)
   {
      if (tkptr->OsuDialogState == OSU_DNET_HDR)
      {
         if (!tkptr->ContentPtr)
         {
            /* back from <DNETHDR> into dialog */
            tkptr->OsuDialogState = OSU_DIALOG;
            DECnetRead (rqptr);
            return;
         }
      }
      else
      {
         /* must be OSU_DNET_INPUT */
         if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
         {
            /* back from <DNETINPUT> into dialog */
            tkptr->OsuDialogState = OSU_DIALOG;
            DECnetRead (rqptr);
            return;
         }
      }

      /* write more data */
      DECnetWriteRequest (rqptr);
      return;
   }

   /* CGI script */
   if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) return;

   /* write more data */
   DECnetWriteRequest (rqptr);
}

/*****************************************************************************/
/*
Queue a read from the remote DECnet task. AST function will process any errors
at all.
*/ 
 
DECnetRead (REQUEST_STRUCT *rqptr)

{
   int  status;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET, "DECnetRead() !&F !&X",
                 &DECnetRead, rqptr->rqNet.WriteIOsb.Status);

   tkptr = rqptr->DECnetTaskPtr;

   if (tkptr->QueuedNetWrite) tkptr->QueuedNetWrite--;

   /* abort task if NETWORK ERROR when writing TO CLIENT */
   if (VMSnok (rqptr->rqNet.WriteIOsb.Status))
   {
      /* best way to clean out network buffers, etc., is to break link */
      if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false;
      DECnetEnd (rqptr);
      return;
   }

   if (!rqptr->rqOutput.BufferPtr) NetWriteInit (rqptr);

   tkptr->QueuedDECnetIO++;
   status = sys$qio (EfnNoWait, tkptr->DECnetChannel,
                     IO$_READVBLK | IO$M_MULTIPLE,
                     &tkptr->DECnetReadIOsb, &DECnetReadAst, rqptr,
                     rqptr->rqOutput.BufferPtr, rqptr->rqOutput.BufferSize,
                     0, 0, 0, 0);
   if (VMSok (status)) return;

   /* report error via AST */
   tkptr->DECnetReadIOsb.Status = status;
   tkptr->DECnetReadIOsb.Count = 0;
   SysDclAst (&DECnetReadAst, rqptr);
}

/*****************************************************************************/
/*
The queued read from the remote DECnet task has completed.  Check for any
errors reported in the IO status block.  If no errors call the function
appropriate for processing the dialog being used by this script.
*/ 
 
DECnetReadAst (REQUEST_STRUCT *rqptr)

{
   int  status;
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetReadAst() !&F !UL !&X !UL",
                 &DECnetReadAst, rqptr->DECnetTaskPtr->QueuedDECnetIO,
                 rqptr->DECnetTaskPtr->DECnetReadIOsb.Status,
                 rqptr->DECnetTaskPtr->DECnetReadIOsb.Count);

   tkptr = rqptr->DECnetTaskPtr;

   if (tkptr->QueuedDECnetIO) tkptr->QueuedDECnetIO--;

   /* reusing the IOsb, expect it to occasionally have been initialized! */
   if (!tkptr->DECnetReadIOsb.Status)
      tkptr->DECnetReadIOsb.Status = SS$_NORMAL;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_DECNET))
   {
      if (VMSnok (tkptr->DECnetReadIOsb.Status))
         WatchThis (rqptr, FI_LI, WATCH_DECNET,
                    "READ !&S %!&M",
                    tkptr->DECnetReadIOsb.Status,
                    tkptr->DECnetReadIOsb.Status);
      else
      {
         WatchThis (rqptr, FI_LI, WATCH_DECNET,
                    "READ !&S !UL byte!%s",
                    tkptr->DECnetReadIOsb.Status,
                    tkptr->DECnetReadIOsb.Count);
         if (tkptr->BuildRecords)
            WatchDataDump (rqptr->rqOutput.BufferPtr+tkptr->BuildCount,
                           tkptr->DECnetReadIOsb.Count);
         else
            WatchDataDump (rqptr->rqOutput.BufferPtr,
                           tkptr->DECnetReadIOsb.Count);
      }
   }

   if (VMSnok (tkptr->DECnetReadIOsb.Status))
   {
      if (tkptr->DECnetReadIOsb.Status != SS$_LINKDISCON)
      {
         rqptr->rqResponse.ErrorTextPtr = rqptr->ScriptName;
         ErrorVmsStatus (rqptr, tkptr->DECnetReadIOsb.Status, FI_LI);
      }
      DECnetEnd (rqptr);
      return;
   }

   switch (tkptr->ScriptType)
   {
      case SCRIPT_CGI :

         DECnetCgiDialog (rqptr);
         return;

      case SCRIPT_OSU :

         DECnetOsuDialog (rqptr);
         return;

      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }
}

/*****************************************************************************/
/*
Dialog for a WASD standard CGI DECnet-based script. These scripts behave
identically to script process-based standard CGI scripts. The dialog comprises
two phases. The first sends a stream of records to the CGI DECnet task. These
records comprise DCL commands, used to set up the CGI environment and to
execute the script. The second phase uses the generic CGI modules functions to
process CGI script output, writing it to the client.
*/ 
 
DECnetCgiDialog (REQUEST_STRUCT *rqptr)

{
   static $DESCRIPTOR (DefineEofFaoDsc, "DEFINE/PROCESS CGIEOF \"!AZ\"");
   static $DESCRIPTOR (DclCommandDsc, "");

   int  cnt, status;
   unsigned short  Length;
   char  *cptr;
   DECNET_TASK  *tkptr;

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

   tkptr = rqptr->DECnetTaskPtr;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetCgiDialog() !UL", tkptr->CgiDialogState);

   if (tkptr->CgiDialogState == CGI_REUSE)
   {
      /**********************/
      /* probing connection */
      /**********************/

      if (tkptr->QueuedDECnetIO) tkptr->QueuedDECnetIO--;

      /* reuse needs to be reset with each use of the DECnet task */
      if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false;

      if (VMSnok (tkptr->DECnetWriteIOsb.Status))
      {
         /* problem writing to established connection, restart */
         DECnetConnectEnd (tkptr);
         DECnetConnectBegin (tkptr);
         return;
      }

      InstanceGblSecIncrLong (&AccountingPtr->DoDECnetReuseCount);
      InstanceGblSecIncrLong (&AccountingPtr->DoDECnetCgiReuseCount);
      tkptr->CgiDialogState = CGI_BEGIN;

      /* drop through to continue with first phase of dialog */
   }

   if (tkptr->CgiDialogState == CGI_BEGIN)
   {
      /***********************/
      /* look for the script */
      /***********************/

      status = DECnetFindCgiScript (rqptr);

      /* retry indicates the search is still underway */
      if (status == SS$_RETRY) return;

      InstanceGblSecIncrLong (&AccountingPtr->DoDECnetCgiCount);

      if (VMSnok (status))
      {
         /* fudge the status so the write AST reports the problem */
         tkptr->DECnetWriteIOsb.Status = status;
         tkptr->QueuedDECnetIO++;
         SysDclAst (&DECnetWriteAst, rqptr);
         return;
      }

      /* must have found the script, set up the DCL/CGI environment */
      tkptr->CgiDialogState = CGI_DCL;
   }

   if (tkptr->CgiDialogState == CGI_DCL)
   {
      /*****************/
      /* define CGIEOF */
      /*****************/

      /* always generate a new EOF string for standard CGI scripts */
      CgiSequenceEof (tkptr->CgiEof, &tkptr->CgiEofLength);
      rqptr->rqCgi.EofPtr = tkptr->CgiEof;
      rqptr->rqCgi.EofLength = tkptr->CgiEofLength;

      DclCommandDsc.dsc$a_pointer = tkptr->DclCommand;
      DclCommandDsc.dsc$w_length = sizeof(tkptr->DclCommand)-1;
      sys$fao (&DefineEofFaoDsc, &Length, &DclCommandDsc, tkptr->CgiEof);
      tkptr->DclCommand[Length] = '\0';
      DECnetWrite (rqptr, tkptr->DclCommand, Length);

      /*******************/
      /* define CGIREUSE */
      /*******************/

      if (Config.cfScript.DECnetReuseLifeTime)
      {
         /* reuse enabled, do not drop DECnet link, reuse it */
         if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = true;
         /* indicate this to the task procedure */
         DECnetWrite (rqptr, "DEFINE/PROCESS CGIREUSE 1", 25);
      }

      /*****************/
      /* CGI variables */
      /*****************/

      if (VMSnok (status = CgiGenerateVariables (rqptr, CGI_VARIABLE_DCL)))
      {
         DECnetEnd (rqptr);
         return;
      }

      cptr = rqptr->rqCgi.BufferPtr;
      for (;;)
      {
         if (!(Length = *(short*)cptr)) break;
         DECnetWrite (rqptr, cptr+sizeof(short), Length-1);
         cptr += Length + sizeof(short);
      }

      /*******************************/
      /* DCL command/procedure/image */
      /*******************************/

      if (tkptr->CgiScriptForeignVerb[0])
         DECnetWrite (rqptr, tkptr->CgiScriptForeignVerb, -1);

      DECnetWrite (rqptr, "GOTO DOIT", 9);

      DECnetWrite (rqptr, tkptr->CgiScriptDcl, -1);

      if (rqptr->rqHeader.Method == HTTP_METHOD_POST ||
          rqptr->rqHeader.Method == HTTP_METHOD_PUT)
      {
         /* begin to write the request content (body) */
         DECnetWriteRequest (rqptr);
      }

      /*******************************/
      /* begin to read script output */
      /*******************************/

      /* now into dialog state */
      tkptr->CgiDialogState = CGI_OUTPUT;

      DECnetRead (rqptr);
      return;
   }

   /*****************/
   /* script output */
   /*****************/

   if (!tkptr->ScriptResponded)
   {
      /*********************/
      /* finally it's true */
      /*********************/

      tkptr->ScriptResponded = true;
      if (tkptr->DECnetReadIOsb.Count == 1)
      {
         /* hmmm, one byte only - start 'building records'! */
         tkptr->BuildRecords = true;
         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_DECNET))
            WatchThis (rqptr, FI_LI, WATCH_DECNET, "BUILD records");
      }
   }

   if (tkptr->BuildRecords)
   {
      /*****************************************/
      /* building 'records' from single bytes! */
      /*****************************************/

      cptr = rqptr->rqOutput.BufferPtr + tkptr->BuildCount;

      if (tkptr->DECnetReadIOsb.Count)
      {
         tkptr->BuildCount += tkptr->DECnetReadIOsb.Count;
         cnt = rqptr->rqOutput.BufferSize - tkptr->BuildCount;

         if (tkptr->DECnetReadIOsb.Count == 1 &&
             *cptr != '\n' && cnt > 0)
         {
            /* not a newline and still space in the buffer */
            tkptr->QueuedDECnetIO++;
            status = sys$qio (EfnNoWait, tkptr->DECnetChannel,
                              IO$_READVBLK | IO$M_MULTIPLE,
                              &tkptr->DECnetReadIOsb, &DECnetReadAst, rqptr,
                              cptr+1, cnt, 0, 0, 0, 0);
            if (VMSok (status)) return;

            /* report error via AST */
            tkptr->DECnetReadIOsb.Status = status;
            tkptr->DECnetReadIOsb.Count = 0;
            SysDclAst (&DECnetReadAst, rqptr);
            return;
         }
      }
      else
      {
         *cptr = '\n';
         tkptr->BuildCount++;
      }

      /* newline, zero bytes, multiple bytes, or out of buffer space */
      tkptr->DECnetReadIOsb.Count = tkptr->BuildCount;
      tkptr->BuildCount = 0;

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_DECNET))
      {
         WatchThis (rqptr, FI_LI, WATCH_DECNET, "BUILT record !UL byte!%s",
                    tkptr->DECnetReadIOsb.Count);
         WatchDataDump (rqptr->rqOutput.BufferPtr, tkptr->DECnetReadIOsb.Count);
      }
   }

   /******************/
   /* process record */
   /******************/

   cptr = rqptr->rqOutput.BufferPtr;
   cnt = tkptr->DECnetReadIOsb.Count;
   cptr[cnt] = '\0';

   /* explcitly look for EOF sentinal because it's hardwired in CGIWASD.COM */
   if (cnt >= rqptr->rqCgi.EofLength &&
       cnt <= rqptr->rqCgi.EofLength+2 &&
       !memcmp (cptr, rqptr->rqCgi.EofPtr, rqptr->rqCgi.EofLength))
   {
      DECnetEnd (rqptr);
      return;
   }
   if (tkptr->RequestPtr->rqResponse.HttpStatus / 100 == 5)
   {
      /* absorbing all output up until the EOF sentinal */
      DECnetRead (rqptr);
      return;
   }

   cnt = CgiOutput (rqptr, cptr, cnt);
   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET, "!SL", cnt);

   if (cnt == CGI_OUTPUT_END)
   {
      /* terminate processing */
      DECnetEnd (rqptr);
      return;
   }

   if (cnt == CGI_OUTPUT_NOT_STRICT)
   {
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr,
         MsgFor(tkptr->RequestPtr,MSG_SCRIPT_RESPONSE_ERROR), FI_LI);
      /* absorb output, queue the next read from the script */
      DECnetRead (rqptr);
      return;
   }

   if (!cnt)
   {
      /* absorb output, queue the next read from the script */
      DECnetRead (rqptr);
      return;
   }

   tkptr->QueuedNetWrite++;
   NetWrite (rqptr, &DECnetRead, cptr, cnt);
}

/*****************************************************************************/
/*
Use the DECnet CGI task to search for the script file. A DCL command is
constructed with the F$SEARCH() lexical and written to the DECnet task which
then executes it, writing the result as an output record which is read by
DECnetRead(), passed to DECnetCgiDialog() and passed back to this same
function for result assessment. If not found and if no explicit file type,
step through each of ".COM", ".EXE" and then any configuration specified file
types and run-time environments, repeating this until found or until all file
types exhausted, at which time a "script not found" report is generated. If
found the DCL required to execute the script is generated in
'tkptr->CgiScriptForeignVerb' and 'tkptr->CgiScriptDcl'.
*/ 

int DECnetFindCgiScript (REQUEST_STRUCT *rqptr)

{
   static $DESCRIPTOR (SearchFaoDsc, "WRITE NET$LINK F$SEARCH(\"!AZ\")");
   static $DESCRIPTOR (DclCommandDsc, "");

   int  cnt, idx, status;
   unsigned short  Length;
   char  *cptr, *sptr, *zptr,
         *StringPtr;
   char  String [256];
   DECNET_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetFindCgiScript() !&F", &DECnetFindCgiScript);

   tkptr = rqptr->DECnetTaskPtr;

   if (tkptr->FindScriptCount && tkptr->DECnetReadIOsb.Count)
   {
      rqptr->rqOutput.BufferPtr[tkptr->DECnetReadIOsb.Count] = '\0';

      if (strsame (rqptr->rqOutput.BufferPtr,
                   tkptr->FindScript,
                   tkptr->FindScriptTotalLength))
      {
         /*************/
         /* found it! */
         /*************/

         tkptr->CgiScriptDcl[0] = tkptr->CgiScriptForeignVerb[0] = '\0';

         if (tkptr->ScriptRunTime[0] == '!')
         {
            /* mapped runtime environment */
            tkptr->RunTimePtr = tkptr->ScriptRunTime+1;
         }
   
         if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
            WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                       "!&Z", tkptr->RunTimePtr);

         cptr = tkptr->RunTimePtr;
         if (*(unsigned short*)cptr == '@\0')
         {
            /* DCL procedure */
            zptr = (sptr = tkptr->CgiScriptDcl) + sizeof(tkptr->CgiScriptDcl)-1;
            *sptr++ = '@';
            cptr = rqptr->rqCgi.ScriptFileNamePtr = tkptr->FindScript;
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            *sptr = '\0';
         }
         else
         if (*(unsigned short*)cptr == '=\0')
         {
            /* command definition */
            zptr = (sptr = tkptr->CgiScriptForeignVerb) +
               sizeof(tkptr->CgiScriptForeignVerb)-1;
            for (cptr = "SET COMMAND "; *cptr; *sptr++ = *cptr++);
            cptr = rqptr->rqCgi.ScriptFileNamePtr = tkptr->FindScript;
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            *sptr = '\0';

            /* now use the command defintion file name as the verb */
            while (sptr > tkptr->CgiScriptForeignVerb && *sptr != ']') sptr--;
            if (*sptr == ']') sptr++;
            cptr = sptr;
            zptr = (sptr = tkptr->CgiScriptDcl) + sizeof(tkptr->CgiScriptDcl)-1;
            while (*cptr && *cptr != '.' && sptr < zptr) *sptr++ = *cptr++;
            *sptr = '\0';
         }
         else
         if (*(unsigned short*)cptr == '$\0')
         {
            /* execute an image */
            zptr = (sptr = tkptr->CgiScriptForeignVerb) +
               sizeof(tkptr->CgiScriptForeignVerb)-1;
            memcpy (sptr, "WASDVERB:=$", 11);
            sptr += 11;
            cptr = rqptr->rqCgi.ScriptFileNamePtr = tkptr->FindScript;
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            *sptr = '\0';

            /* now use it as a verb, in case mapped params need to be added */
            zptr = (sptr = tkptr->CgiScriptDcl) + sizeof(tkptr->CgiScriptDcl)-1;
            memcpy (sptr, "WASDVERB", 9);
            sptr += 8;
         }
         else
         {
            if (*cptr == '@' || *cptr == '$')
            {
               /* foreign-verb DCL procedure or executable, create the verb */
               zptr = (sptr = tkptr->CgiScriptForeignVerb) +
                  sizeof(tkptr->CgiScriptForeignVerb)-1;
               memcpy (sptr, "WASDVERB:=", 10);
               sptr += 10;
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               *sptr = '\0';

               /* now place it as the verb before the script file */
               zptr = (sptr = tkptr->CgiScriptDcl) +
                  sizeof(tkptr->CgiScriptDcl)-1;
               memcpy (sptr, "WASDVERB ", 9);
               sptr += 9;
               cptr = rqptr->rqCgi.ScriptFileNamePtr = tkptr->FindScript;
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               *sptr = '\0';
            }
            else
            {
               /* verb already exists, place before the script file */
               zptr = (sptr = tkptr->CgiScriptDcl) +
                  sizeof(tkptr->CgiScriptDcl)-1;
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = ' ';
               cptr = rqptr->rqCgi.ScriptFileNamePtr = tkptr->FindScript;
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               *sptr = '\0';
            }
         }

         if (rqptr->rqPathSet.ScriptCommandPtr)
         {
            /* add script activation command elements from path SETing */
            StringPtr = rqptr->rqPathSet.ScriptCommandPtr;
            cnt = 0;
            for (;;)
            {
               status = StringParseValue (&StringPtr, String, sizeof(String));
               if (VMSnok (status)) break;
               cnt++;
               cptr = String;
               if (cnt == 1 && *cptr != '*')
               {
                  tkptr->CgiScriptForeignVerb[0] = '\0';
                  zptr = (sptr = tkptr->CgiScriptDcl) +
                     sizeof(tkptr->CgiScriptDcl)-1;
               }
               while (*cptr == '*') cptr++;
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               *sptr = '\0';
            }
            if (status != SS$_ENDOFFILE)
            {
               ErrorVmsStatus (rqptr, status, FI_LI);
               tkptr->CgiScriptDcl[0] = tkptr->CgiScriptForeignVerb[0] = '!';
            }
         }

         return (SS$_NORMAL);
      }

      /* not our dialog, force not found report */
      tkptr->FindScriptCount = 999999;
   }

   /*******************/
   /* not found (yet) */
   /*******************/

   if (tkptr->FindScriptCount == 1) 
   {
      /* explicitly supplied file type was not found */
      rqptr->rqResponse.HttpStatus = 404;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_SCRIPT_NOT_FOUND), FI_LI);
      return (STS$K_ERROR);
   }

   /* if started searching then search for the next one */
   if (tkptr->FindScriptCount > 1) tkptr->FindScriptCount++;

   if (!tkptr->FindScriptCount)
   {
      /****************/
      /* initial call */
      /****************/

      /* get the script file name (minus all DECnet-related stuff) */
      zptr = (sptr = tkptr->FindScript) +
             sizeof(tkptr->FindScript) - FIND_SCRIPT_OVERHEAD;
      for (cptr = tkptr->MappedScript; *cptr && *cptr != ':'; cptr++);
      if (*cptr == ':') cptr++;
      if (*cptr == ':') cptr++;
      if (*cptr == '\"')
      {
         /* task specification string */
         cptr++;
         while (*cptr != '\"') cptr++;
         if (*cptr == '\"') cptr++;
      }
      /* here we go with the file name */
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr)
      {
         ErrorGeneralOverflow (rqptr, FI_LI);
         return (STS$K_ERROR);
      }
      *sptr = '\0';
      tkptr->FindScriptLength = sptr - tkptr->FindScript;

      /* look backwards to see if a file type has been supplied */
      cptr = tkptr->FindScript;
      while (*sptr != '.' && *sptr != ']' && sptr > cptr) sptr--;
      if (*sptr != '.')
      {
         /* indicate no file type using 2 */
         tkptr->FindScriptCount = 2;
      }
      else
      {
         /**********************/
         /* file type supplied */
         /**********************/

         /* indicate file type supplied using 1 */
         tkptr->FindScriptCount = 1;
         tkptr->FindScriptTypePtr = sptr;
         tkptr->FindScriptTotalLength = tkptr->FindScriptLength;

         if (tkptr->ScriptRunTime[0] == '!')
            tkptr->RunTimePtr = tkptr->ScriptRunTime+1;
         else
         if (strsame (sptr, ".COM", -1))
            tkptr->RunTimePtr = "@";
         else
         if (strsame (sptr, ".CLD", -1))
            tkptr->RunTimePtr = "=";
         else
         if (strsame (sptr, ".EXE", -1))
            tkptr->RunTimePtr = "$";
         else
         {
            /* let's look for it in the run-time environment information */
            for (idx = 0; idx < Config.cfScript.RunTimeCount; idx++)
            {
               sptr = tkptr->FindScriptTypePtr;
               cptr = Config.cfScript.RunTime[idx].String;
               while (*cptr && *cptr != ';' && *sptr)
               {
                  if (toupper(*cptr) != toupper(*sptr)) break;
                  cptr++;
                  sptr++;
               }
               if (*cptr == ';' && !*sptr) break;
            }

            if (idx >= Config.cfScript.RunTimeCount)
            {
               /* nope, not found */
               rqptr->rqResponse.HttpStatus = 500;
               ErrorGeneral (rqptr,
"Execution of &nbsp;<TT>!AZ</TT>&nbsp; script types not configured.",
                             tkptr->FindScriptTypePtr, FI_LI);
               return (STS$K_ERROR);
            }

            /* found the file type, point to the run-time stuff */
            if (*cptr) cptr++;
            while (ISLWS(*cptr)) cptr++;
            tkptr->RunTimePtr = cptr;
         }
      }
   }

   if (tkptr->FindScriptCount == 2)
   {
      /*************************/
      /* first, DCL procedures */
      /*************************/

      memcpy (sptr = tkptr->FindScript+tkptr->FindScriptLength, ".COM", 5);
      tkptr->FindScriptTypePtr = sptr;
      tkptr->FindScriptTotalLength = tkptr->FindScriptLength + 4;
      tkptr->RunTimePtr = "@";
   }
   else
   if (tkptr->FindScriptCount == 3)
   {
      /*******************************/
      /* second, command definitions */
      /*******************************/

      memcpy (sptr = tkptr->FindScript+tkptr->FindScriptLength, ".CLD", 5);
      tkptr->FindScriptTypePtr = sptr;
      tkptr->FindScriptTotalLength = tkptr->FindScriptLength + 4;
      tkptr->RunTimePtr = "=";
   }
   else
   if (tkptr->FindScriptCount == 4)
   {
      /**********************/
      /* third, executables */
      /**********************/

      memcpy (sptr = tkptr->FindScript+tkptr->FindScriptLength, ".EXE", 5);
      tkptr->FindScriptTypePtr = sptr;
      tkptr->FindScriptTotalLength = tkptr->FindScriptLength + 4;
      tkptr->RunTimePtr = "$";
   }
   else
   if (tkptr->FindScriptCount != 1 &&
       tkptr->FindScriptCount-5 < Config.cfScript.RunTimeCount)
   {
      /*************************************/
      /* subsequent, run-time environments */
      /*************************************/

      tkptr->FindScriptTypePtr = sptr =
         tkptr->FindScript + tkptr->FindScriptLength;
      for (cptr = Config.cfScript.RunTime[tkptr->FindScriptCount-5].String;
           *cptr && *cptr != ';';
           *sptr++ = *cptr++);
      *sptr = '\0';
      tkptr->FindScriptTotalLength = sptr - tkptr->FindScript;
      if (*cptr) cptr++;
      while (*cptr && ISLWS(*cptr)) cptr++;
      tkptr->RunTimePtr = cptr;
   }
   else
   if (tkptr->FindScriptCount != 1)
   {
      /**************/
      /* not found! */
      /**************/

      rqptr->rqResponse.HttpStatus = 404;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_SCRIPT_NOT_FOUND), FI_LI);
      return (STS$K_ERROR);
   }
   /* else 'tkptr->FindScriptCount' can be -1, when the type is supplied! */

   /********************/
   /* write search DCL */
   /********************/

   DclCommandDsc.dsc$a_pointer = tkptr->DclCommand;
   DclCommandDsc.dsc$w_length = sizeof(tkptr->DclCommand)-1;
   sys$fao (&SearchFaoDsc, &Length, &DclCommandDsc, tkptr->FindScript);

   DECnetWrite (rqptr, tkptr->DclCommand, Length);
   DECnetRead (rqptr);   
   return (SS$_RETRY);
}

/*****************************************************************************/
/*
Implement (most) OSU server scripting dialogs.
Behaviour determined from the OSU 'script_execute.c' module.
*/ 
 
DECnetOsuDialog (REQUEST_STRUCT *rqptr)

{
   int  cnt,
        status;
   char  *cptr, *sptr, *zptr;
   DECNET_TASK  *tkptr;
   char  Buffer [2048];

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

   tkptr = rqptr->DECnetTaskPtr;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                "DECnetOsuDialog() !UL", tkptr->OsuDialogState);

   if (tkptr->OsuDialogState == OSU_REUSE)
   {
      /**********************/
      /* probing connection */
      /**********************/

      if (tkptr->QueuedDECnetIO) tkptr->QueuedDECnetIO--;

      /* reuse needs to be reset with each use of the DECnet task */
      if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false;

      if (VMSnok (tkptr->DECnetWriteIOsb.Status))
      {
         /* problem writing to established connection, restart */
         DECnetConnectEnd (tkptr);
         DECnetConnectBegin (tkptr);
         return;
      }

      InstanceGblSecIncrLong (&AccountingPtr->DoDECnetReuseCount);
      InstanceGblSecIncrLong (&AccountingPtr->DoDECnetOsuReuseCount);
      tkptr->OsuDialogState = OSU_BEGIN;

      /* drop through to continue with first phase of dialog */
   }

   if (tkptr->OsuDialogState == OSU_BEGIN)
   {
      /******************/
      /* initial dialog */
      /******************/

      /*
         OSU task executive expects four records as initial and unsolicited
         data from the server.  These are: script type, HTTP method name,
         HTTP protocol version, request path (excluding any query string).
      */

      InstanceGblSecIncrLong (&AccountingPtr->DoDECnetOsuCount);
      tkptr->OsuDnetRecMode = tkptr->OsuDnetCgi = false;

      DECnetWrite (rqptr, "HTBIN", 5);
      DECnetWrite (rqptr, rqptr->rqHeader.MethodName, -1);
      DECnetWrite (rqptr, HttpProtocol, -1);

      /* 
         URL (everything before any query string in original request)
         OSU requires the mapped script portion, which we have have to
         construct from the script name and the path information.
         "<DNETRQURL>" provides the unmapped (original) request path.
      */
      zptr = (sptr = Buffer) + sizeof(Buffer);
      cptr = rqptr->ScriptName;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      cptr = rqptr->MappedPathPtr;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr)
      {
         ErrorGeneralOverflow (rqptr, FI_LI);
         DECnetEnd (rqptr);
         return;
      }
      *sptr = '\0';
      /* it's an asynchronous write so we need some persistant storage */
      cptr = VmGetHeap (rqptr, sptr-Buffer+1);
      memcpy (cptr, Buffer, sptr-Buffer+1);
      DECnetWrite (rqptr, cptr, sptr-Buffer);

      /* now into dialog state */
      tkptr->OsuDialogState = OSU_DIALOG;

      DECnetRead (rqptr);
      return;
   }

   /**********************/
   /* dialog established */
   /**********************/

   cptr = rqptr->rqOutput.BufferPtr;
   cnt = tkptr->DECnetReadIOsb.Count;
   cptr[cnt] = '\0';

   if (tkptr->OsuDialogState == OSU_DIALOG && *cptr != '<')
   {
      /************************/
      /* start of data stream */
      /************************/

      /* now set the carriage-control appropriately */
      if (tkptr->OsuDnetRecMode)
         tkptr->OsuDialogState = OSU_OUTPUT_REC;
      else
         tkptr->OsuDialogState = OSU_OUTPUT_RAW;

      /* drop through so the line can be processed by "_REC" or "_RAW" */
   }

   switch (tkptr->OsuDialogState)
   {
      case OSU_DIALOG :

         /********************/
         /* stream massaging */
         /********************/

         /* treat the same until I can work out what 2 does differently */
         if (!memcmp (cptr, "<DNETRECMODE>", 13) ||
             !memcmp (cptr, "<DNETRECMODE2>", 14))
         {
            /* forces text mode */
            tkptr->OsuDnetRecMode = true;
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETCGI>", 9))
         {
            /* similar to raw mode except response header is checked */
            tkptr->OsuDnetCgi = true;
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETRAW>", 9))
         {
            /* script handles all response formatting, return byte-for-byte */
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETTEXT>", 10))
         {
            /* script sends status line then plain text */
            tkptr->OsuDialogState = OSU_DNETTEXT;
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "</DNETCGI>", 10) ||
             !memcmp (cptr, "</DNETRAW>", 10) ||
             !memcmp (cptr, "</DNETTEXT>", 11))
         {
            /* end of script output */
            if (tkptr->cnptr && tkptr->cnptr->ReuseConnection)
               DECnetWrite (rqptr, "<DNETREUSE>", 11);

            DECnetEnd (rqptr);
            return;
         }

         /**********************/
         /* information dialog */
         /**********************/

         if (!memcmp (cptr, "<DNETARG>", 9))
         {
            /* full search argument (query string) */
            cptr = VmGetHeap (rqptr, rqptr->rqHeader.QueryStringLength+2);
            /* OSU supplies a leading '?' query separator */
            *cptr = '?';
            memcpy (cptr+1, rqptr->rqHeader.QueryStringPtr, rqptr->rqHeader.QueryStringLength+1);
            DECnetWrite (rqptr, cptr, rqptr->rqHeader.QueryStringLength+1);
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETARG2>", 10))
         {
            /* truncated search argument (query string) */
            cptr = VmGetHeap (rqptr, 256);
            /* OSU supplies a leading '?' query separator */
            *cptr = '?';
            if (rqptr->rqHeader.QueryStringLength > 253)
            {
               memcpy (cptr+1, rqptr->rqHeader.QueryStringPtr, 253);
               cptr[255] = '\0';
               DECnetWrite (rqptr, cptr, 254);
            }
            else
            {
               memcpy (cptr+1, rqptr->rqHeader.QueryStringPtr, rqptr->rqHeader.QueryStringLength);
               DECnetWrite (rqptr, cptr, rqptr->rqHeader.QueryStringLength+1);
            }
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETPATH>", 10))
         {
            /* script path (but excluding script name itself) */
            char  *cptr;
            for (cptr = rqptr->ScriptName; *cptr; cptr++);
            while (cptr > rqptr->ScriptName && *cptr != '/') cptr--;
            DECnetWrite (rqptr, rqptr->ScriptName,
                         cptr-(char*)rqptr->ScriptName+1);
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETHDR>", 9))
         {
            /* send the full request header */
            tkptr->OsuDialogState = OSU_DNET_HDR;
            DECnetWriteRequest (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETINPUT>", 11))
         {
            /* begin to write the request body */
            tkptr->OsuDialogState = OSU_DNET_INPUT;
            DECnetWriteRequest (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETBINDIR>", 12))
         {
            /*
               OSU script and binaries VMS directory specification.
               Isolate the 'HT_ROOT:[dir]' from the example
               'node::"0=wwwexec"HT_ROOT:[dir]script.ext'
            */
            for (cptr = tkptr->MappedScript; *cptr && *cptr != ':'; cptr++);
            if (*cptr == ':') cptr++;
            if (*cptr == ':') cptr++;
            if (*cptr == '\"')
            {
               cptr++;
               while (*cptr && *cptr != '\"') cptr++;
               if (*cptr) cptr++;
            }
            sptr = cptr;
            while (*cptr) cptr++;
            while (cptr > sptr && *cptr != ']') cptr--;
            DECnetWrite (rqptr, sptr, cptr - sptr + 1);
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETRQURL>", 11))
         {
            /* full request URL prior to any mapping */
            DECnetWrite (rqptr, rqptr->rqHeader.RequestUriPtr, -1);
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETINVCACHE>", 14))
         {
            /* invalidate cache */
            rqptr->rqResponse.HttpStatus = 501;
            ErrorGeneral (rqptr, ErrorOsuNoInvCache, FI_LI);
            DECnetEnd (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETHOST>", 10))
         {
            /* server host name */
            DECnetWrite (rqptr, rqptr->ServicePtr->ServerHostName, -1);
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETID>", 8))
         {
            /* composite line of request data */
            DECnetOsuDnetId (rqptr, tkptr, false);
            return;
         }

         if (!memcmp (cptr, "<DNETID2>", 9))
         {
            /* composite line of request data (plus a bit!) */
            DECnetOsuDnetId (rqptr, tkptr, true);
            return;
         }

         if (!memcmp (cptr, "<DNETMANAGE>", 12))
         {
            /* server management */
            rqptr->rqResponse.HttpStatus = 500;
            ErrorGeneral (rqptr, ErrorOsuNoManage, FI_LI);
            DECnetEnd (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETREUSE>", 11))
         {
            if (Config.cfScript.DECnetReuseLifeTime)
            {
               /* reuse enabled, do not drop DECnet link */
               if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = true;
            }
            DECnetRead (rqptr);
            return;
         }

         if (!memcmp (cptr, "<DNETXLATE>", 11) ||
             !memcmp (cptr, "<DNETXLATEV>", 12))
         {
            /* into translate state */
            tkptr->OsuDialogState = OSU_DNET_XLATE;
            /* read the path to be translated */
            DECnetRead (rqptr);
            return;
         }

         /* hmmm, unknown dialog tag */
         {
            status = WriteFao (Buffer, sizeof(Buffer), NULL,
                               ErrorOsuUnknown, cptr);
            if (VMSnok (status) || status == SS$_BUFFEROVF)
               ErrorNoticed (status, "WriteFao()", FI_LI);

            rqptr->rqResponse.HttpStatus = 500;
            ErrorGeneral (rqptr, Buffer, FI_LI);
            DECnetEnd (rqptr);
            return;
         }


      /****************/
      /* other states */
      /****************/

      case OSU_DNETTEXT :

         /*
            This output record contains a status line something like "200 ok".
            Get the status code, ignore the rest, generate an HTTP header.
            Then move into a state to accept record-oriented script output.
         */
         while (*cptr && !isdigit(*cptr) && *cptr != '\n') cptr++;
         if (isdigit(*cptr)) rqptr->rqResponse.HttpStatus = atoi(cptr);

         ResponseHeader (rqptr, rqptr->rqResponse.HttpStatus,
                     "text/plain", -1, NULL, NULL);

         /* note that the script has generated some output */
         tkptr->ScriptResponded = true;

         /* now move into record output state */
         tkptr->OsuDialogState = OSU_OUTPUT_REC;
         DECnetRead (rqptr);

         return;


      case OSU_OUTPUT_RAW :

         /*
            This script output record is "binary" data.  Write it to
            the client via the network without any further processing.
         */
         if (!memcmp (cptr, "</DNETCGI>", 10) ||
             !memcmp (cptr, "</DNETRAW>", 10) ||
             !memcmp (cptr, "</DNETTEXT>", 11))
         {
            /* end of script output */
            DECnetEnd (rqptr);
            return;
         }

         /* note that the script has generated some output */
         tkptr->ScriptResponded = true;

         if (tkptr->OsuDnetCgi && !rqptr->rqCgi.ProcessingBody)
         {
            /* force output processor into stream mode */
            rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_STREAM;
            /* "CGI" script, check header */
            cnt = CgiOutput (rqptr, cptr, cnt);
         }

         if (!cnt)
         {
            /* absorb output, queue the next read from the script */
            DECnetRead (rqptr);
            return;
         }

         tkptr->QueuedNetWrite++;
         NetWrite (rqptr, &DECnetRead, cptr, cnt);
         return;


      case OSU_OUTPUT_REC :

         /*
            This record of script output is essentially a line of "text".
            Ensure it has correct HTTP carriage-control (trailing newline).
            Write this (possibly) massaged record to the client (network).
         */
         if (!memcmp (cptr, "</DNETCGI>", 10) ||
             !memcmp (cptr, "</DNETRAW>", 10) ||
             !memcmp (cptr, "</DNETTEXT>", 11))
         {
            /* end of script output */
            DECnetEnd (rqptr);
            return;
         }

         /* note that the script has generated some output */
         tkptr->ScriptResponded = true;

         if (tkptr->OsuDnetCgi && !rqptr->rqCgi.ProcessingBody)
         {
            /* force output processor into record mode */
            rqptr->rqCgi.OutputMode = CGI_OUTPUT_MODE_RECORD;
            /* "CGI" script, check header */
            cnt = CgiOutput (rqptr, cptr, cnt);
         }
         else
         {
            /* adjust carriage-control if necessary */
            if (cnt)
               { if (cptr[cnt-1] != '\n') cptr[cnt++] = '\n'; }
            else
               cptr[cnt++] = '\n';
            cptr[cnt] = '\0';
         }

         if (!cnt)
         {
            /* absorb output, queue the next read from the script */
            DECnetRead (rqptr);
            return;
         }

         tkptr->QueuedNetWrite++;
         NetWrite (rqptr, &DECnetRead, cptr, cnt);
         return;


      case OSU_DNET_XLATE :

         /* record contains a URL-style path to be translated into RMS */
         DECnetOsuDnetXlate (rqptr, tkptr, cptr);
         return;


      default :

         /* Hmmm, problem? */
         rqptr->rqResponse.HttpStatus = 500;
         ErrorGeneral (rqptr, ErrorOsuImplementation, FI_LI);
         DECnetEnd (rqptr);
         return;
   }
}

/*****************************************************************************/
/*
Write a single record comprising a series of white-space separated request
data to the OSU task.
*/ 
 
DECnetOsuDnetId
(
REQUEST_STRUCT *rqptr,
DECNET_TASK *tkptr,
BOOL Id2
)
{
   static $DESCRIPTOR (SignedLongFaoDsc, "!SL\0");
   static $DESCRIPTOR (UnsignedLongFaoDsc, "!UL\0");

   char Buffer [1024],
        String [256];
   char  *cptr, *sptr, *zptr;
   $DESCRIPTOR (StringDsc, String);

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET, "DECnetOsuDnetId()");

   cptr = rqptr->rqOutput.BufferPtr;
   zptr = (sptr = Buffer) + sizeof(Buffer);

   /* server identification (underscores substituted for spaces) */
   cptr = SoftwareID;
   while (*cptr && sptr < zptr)
   {
      if (*cptr == ' ')
      {
         *sptr++ = '_';
         cptr++;
      }
      else
         *sptr++ = *cptr++;
   }

   /* local host */
   if (sptr < zptr) *sptr++ = ' ';
   for (cptr = rqptr->ServicePtr->ServerHostName;
        *cptr && sptr < zptr; *sptr++ = *cptr++);

   /* local port */
   if (sptr < zptr) *sptr++ = ' ';
   for (cptr = rqptr->ServicePtr->ServerPortString;
        *cptr && sptr < zptr; *sptr++ = *cptr++);

   /* remote (client) port */
   if (sptr < zptr) *sptr++ = ' ';
   sys$fao (&UnsignedLongFaoDsc, 0, &StringDsc,
            rqptr->rqClient.IpPort);
   for (cptr = String; *cptr && sptr < zptr; *sptr++ = *cptr++);

   /* remote host address (OSU cannot support IPv6 using this mechanism) */
   if (sptr < zptr) *sptr++ = ' ';
   if (IPADDRESS_IS_V4(&rqptr->rqClient.IpAddress))
      sys$fao (&SignedLongFaoDsc, 0, &StringDsc,
               IPADDRESS_ADR4(&rqptr->rqClient.IpAddress));
   else
      strcpy (String, "0");
   for (cptr = String; *cptr && sptr < zptr; *sptr++ = *cptr++);

   /* remote user */
   if (rqptr->RemoteUser[0])
   {
      if (sptr < zptr) *sptr++ = ' ';
      for (cptr = rqptr->RemoteUser;
           *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   else
   if (Id2)
      if (sptr < zptr) *sptr++ = ' ';

   if (Id2 && Config.cfMisc.DnsLookupClient)
   {
      /* remote host name */
      if (sptr < zptr) *sptr++ = ' ';
      for (cptr = rqptr->rqClient.Lookup.HostName;
           *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   *sptr = '\0';

   /* it's an asynchronous write so we need some persistant storage */
   cptr = VmGetHeap (rqptr, sptr-Buffer+1);
   memcpy (cptr, Buffer, sptr-Buffer+1);

   DECnetWrite (rqptr, cptr, sptr-Buffer);
   DECnetRead (rqptr);
}

/*****************************************************************************/
/*
Map the supplied path (either absolute or relative to the request path) into a
VMS file system specification and write that to the OSU task.
*/ 
 
DECnetOsuDnetXlate
(
REQUEST_STRUCT *rqptr,
DECNET_TASK *tkptr,
char *Path
)
{
   int  TransPathLength;
   char  *cptr;
   char AbsPath [256],
        TransPath [256],
        VmsPath [256+7];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetOsuDnetXlate() !&Z", Path);

   /* WASD always ignores any method */
   if (*(cptr = Path) == '(')
   {
      while (*cptr && *cptr != ')') cptr++;
      if (*cptr) cptr++;
   }

   TransPath[0] = VmsPath[0] = '\0';
   TransPathLength = 0;
   if (*cptr == '.')
   {
      MapUrl_VirtualPath (rqptr->rqHeader.PathInfoPtr, cptr,
                          AbsPath, sizeof(AbsPath));
      cptr = AbsPath;
   }
   if (*cptr)
   {
      MapUrl_Map (cptr, 0, VmsPath, sizeof(VmsPath), NULL, 0, NULL, 0, NULL, 0,
                  NULL, rqptr);
      TransPathLength = MapUrl_VmsToUrl (TransPath, VmsPath,
                                         sizeof(TransPath), false, 0);
   }

   DECnetWrite (rqptr, TransPath, TransPathLength);
   DECnetRead (rqptr);

   /* back from translate into dialog state */
   tkptr->OsuDialogState = OSU_DIALOG;
}

/*****************************************************************************/
/*
Every minute scan the list of network connections looking for those whose
lifetimes have expired. Break the connection of those who have. As this is a
one minute tick set the lifetime counters to configuration plus one to ensure
at least that number of minutes before expiry.
*/

BOOL DECnetSupervisor (int PeriodSeconds)

{
   static int  TaskScanSeconds = 0;

   BOOL  ContinueTicking;
   int  status,
        MinSeconds;
   LIST_ENTRY  *leptr;
   DECNET_CONNECT  *cnptr;

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

   if (WATCH_MODULE(WATCH_MOD_DECNET) && PeriodSeconds != -1)
      WatchThis (NULL, FI_LI, WATCH_MOD_DECNET,
                 "DECnetSupervisor() !SL !UL", PeriodSeconds, HttpdTickSecond);

   if (PeriodSeconds >= 0)
   {
      /* initiate or reset the task supervisor ticking */
      if (PeriodSeconds)
      {
         if (HttpdTickSecond + PeriodSeconds < TaskScanSeconds &&
             PeriodSeconds >= DECNET_SUPERVISOR_TICK_MIN &&
             PeriodSeconds <= DECNET_SUPERVISOR_TICK_MAX)
            TaskScanSeconds = HttpdTickSecond + PeriodSeconds;
      }
      else
      if (!TaskScanSeconds)
         TaskScanSeconds = HttpdTickSecond + DECNET_SUPERVISOR_TICK_MAX;

      return (false);
   }

   /*******************/
   /* task supervisor */
   /*******************/

   /* no script process is currently executing */
   if (!TaskScanSeconds) return (false);

   /* script process executing, but no need to do a scan just yet */
   if (TaskScanSeconds > HttpdTickSecond) return (true);

   ContinueTicking = false;
   MinSeconds = DECNET_SUPERVISOR_TICK_MAX;

   for (leptr = DECnetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      cnptr = (DECNET_CONNECT*)leptr;

      /* only interested in those which are currently connected */
      if (!cnptr->DECnetChannel) continue;

      if (cnptr->LifeTimeSecond > HttpdTickSecond)
      {
         ContinueTicking = true;
         if (cnptr->LifeTimeSecond - HttpdTickSecond < MinSeconds)
            MinSeconds = cnptr->LifeTimeSecond - HttpdTickSecond;
         continue;
      }

      status = sys$dassgn (cnptr->DECnetChannel);
      cnptr->DECnetChannel = cnptr->LifeTimeSecond = 0;
   }

   if (WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (NULL, FI_LI, WATCH_MOD_DECNET,
                 "SUPERVISOR !&B !UL", ContinueTicking, MinSeconds);

   if (ContinueTicking)
   {
      /* at least one item in the connect list */
      if (MinSeconds < 0) MinSeconds = 0;
      TaskScanSeconds = HttpdTickSecond + MinSeconds;
      return (true);
   }

   /* reinitialize the supervisor timings */
   TaskScanSeconds = 0;

   return (false);
}

/*****************************************************************************/
/*
Return a report on DECnet scripting, in particular displaying each item in the
connection list.
*/ 

DECnetScriptingReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   static char  BeginPageFao [] =
"<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH>Statistics</TH></TR>\n\
<TR><TD>\n\
<TABLE CELLPADDING=0 CELLSPACING=5 BORDER=0>\n\
<TH ALIGN=right>Default:</TH><TD COLSPAN=2 ALIGN=left>!AZ</TD></TR>\n\
<TR><TH></TH><TH><U>Count</U></TH><TH><U>Reuse</U></TH></TR>\n\
<TR><TH ALIGN=right>Total:</TH><TD>!UL</TD><TD>!UL</TD></TR>\n\
<TR><TH ALIGN=right>CGI-script:</TH><TD>!UL</TD><TD>!UL</TD></TR>\n\
<TR><TH ALIGN=right>OSU-script:</TH><TD>!UL</TD><TD>!UL</TD></TR>\n\
</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
\
<P><TABLE CELLPADDING=1 CELLSPACING=0 BORDER=0>\n\
<TR>\
<TH></TH>\
<TH ALIGN=left><U>Connect&nbsp;/&nbsp;Client</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Lifetime&nbsp;/&nbsp;Request</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Reused</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=right><U>Total</U>&nbsp;&nbsp;</TH>\
<TH ALIGN=left><U>Last</U></TH>\
<TH>&nbsp;&nbsp;</TH>\
</TR>\n\
<TR HEIGHT=5></TR>\n";

   static char  ItemFao [] =
"<TR><TD ALIGN=right>!3ZL&nbsp;&nbsp;</TD>\
<TD ALIGN=left>!&@&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=right>!UL&nbsp;&nbsp;</TD>\
<TD ALIGN=left><NOBR>!20%D</NOBR></TD>\
<TR>\n\
!&@";

   static char EmptyConnectListFao [] =
"<TR><TH><B>000</B>&nbsp;&nbsp;</TH>\
<TD COLSPAN=5 BGCOLOR=\"#eeeeee\"><I>empty</I></TD><TR>\n";

   static char  ButtonsFao [] =
"</TABLE>\n\
<HR SIZE=1 NOSHADE WIDTH=60% ALIGN=left>\n\
<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=0>\n\
<TR><TD>\n\
<FORM METHOD=GET ACTION=\"!AZ\">\n\
<INPUT TYPE=submit VALUE=\" Purge \">\n\
</FORM>\n\
</TD><TD>&nbsp;</TD><TD>\n\
<FORM METHOD=GET ACTION=\"!AZ\">\n\
<INPUT TYPE=submit VALUE=\" Force Disconnect \">\n\
</FORM>\n\
</TD></TR>\n\
</TABLE>\n\
</BODY>\n\
</HTML>\n";

   int  status,
        Count;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   char  *cptr;
   DECNET_CONNECT  *cnptr;
   LIST_ENTRY  *leptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET,
                 "DECnetScriptingReport() !&A", NextTaskFunction);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "DECnet Scripting Report");

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   vecptr = FaoVector;
   if (HttpdScriptAsUserName[0])
      *vecptr++ = HttpdScriptAsUserName;
   else
      *vecptr++ = HttpdProcess.UserName;
   *vecptr++ = AccountingPtr->DoDECnetCount;
   *vecptr++ = AccountingPtr->DoDECnetReuseCount;
   *vecptr++ = AccountingPtr->DoDECnetCgiCount;
   *vecptr++ = AccountingPtr->DoDECnetCgiReuseCount;
   *vecptr++ = AccountingPtr->DoDECnetOsuCount;
   *vecptr++ = AccountingPtr->DoDECnetOsuReuseCount;

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   status = NetWriteFaol (rqptr, BeginPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   Count = 0;

   for (leptr = DECnetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      cnptr = (DECNET_CONNECT*)leptr;

      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_DECNET))
         WatchThis (rqptr, FI_LI, WATCH_MOD_DECNET, "!&X !UL !&X",
                    cnptr, cnptr->DECnetChannel, cnptr->RequestPtr);

      vecptr = FaoVector;

      *vecptr++ = ++Count;
      if (cnptr->DECnetChannel)
      {
         *vecptr++ = "!AZ";
         *vecptr++ = cnptr->ConnectString;
      }
      else
      {
         *vecptr++ = "<I>!AZ</I>";
         *vecptr++ = cnptr->ConnectString;
      }
      if (cnptr->LifeTimeSecond > HttpdTickSecond)
         *vecptr++ = cnptr->LifeTimeSecond - HttpdTickSecond;
      else
         *vecptr++ = 0;
      *vecptr++ = cnptr->ReUsageCount;
      *vecptr++ = cnptr->UsageCount;
      *vecptr++ = &cnptr->LastUsedBinaryTime;

      if (cnptr->RequestPtr)
      {
         *vecptr++ =
"<TR><TD></TD>\
<TD BGCOLOR=\"#eeeeee\">!&@!AZ</TD>\
<TD COLSPAN=4 BGCOLOR=\"#eeeeee\">!&;AZ</TD></TR>\n";

         if (cnptr->RequestPtr->RemoteUser[0])
         {
            *vecptr++ = "!&;AZ@";
            *vecptr++ = cnptr->RequestPtr->RemoteUser;
         }
         else
            *vecptr++ = "";
         *vecptr++ = cnptr->RequestPtr->rqClient.Lookup.HostName;
         *vecptr++ = cnptr->RequestPtr->rqHeader.RequestUriPtr;
      }
      else
         *vecptr++ =
"<TR><TD></TD><TD COLSPAN=5 BGCOLOR=\"#eeeeee\"><I>idle</I></TD></TR>\n";

      status = NetWriteFaol (rqptr, ItemFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }
   if (!DECnetConnectList.HeadPtr)
   {
      status = NetWriteFaol (rqptr, EmptyConnectListFao, NULL);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   vecptr = FaoVector;
   *vecptr++ = ADMIN_CONTROL_DECNET_PURGE;
   *vecptr++ = ADMIN_CONTROL_DECNET_DISCONNECT;

   status = NetWriteFaol (rqptr, ButtonsFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Same as for DECnetPurgeAllConnect() except called from control module from
command line!
*/

char* DECnetControlDisconnect (BOOL WithExtremePrejudice)

{
   static char  Response [64];
   static $DESCRIPTOR (ResponseFaoDsc,
          "!UL disconnected, !UL marked for disconnection");
   static $DESCRIPTOR (ResponseDsc, Response);

   int  status,
        DisconnectedCount,
        MarkedCount;
   unsigned short  Length;
   LIST_ENTRY  *leptr;
   DECNET_CONNECT  *cnptr;

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

   if (WATCH_MODULE(WATCH_MOD_DECNET))
      WatchThis (NULL, FI_LI, WATCH_MOD_DECNET,
                 "DECnetControlPurgeAllConnect() !UL", WithExtremePrejudice);

   DECnetPurgeAllConnectCount++;
   DisconnectedCount = MarkedCount = 0;

   for (leptr = DECnetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      cnptr = (DECNET_CONNECT*)leptr;

      /* only interested in those which are currently connected */
      if (!cnptr->DECnetChannel) continue;

      if (WithExtremePrejudice ||
          !cnptr->RequestPtr)
      {
         /* connection not active, or we don't care, disconnect */
         status = sys$dassgn (cnptr->DECnetChannel);
         cnptr->DECnetChannel = cnptr->LifeTimeSecond = 0;
         DisconnectedCount++;
         continue;
      }
      else
      {
         /* connection is currently active, disconnect when finished */
         cnptr->IsMarkedForDisconnect = true;
         MarkedCount++;
      }
   }

   sys$fao (&ResponseFaoDsc, &Length, &ResponseDsc,
            DisconnectedCount, MarkedCount);
   Response[Length] = '\0';

   return (Response);
}

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

