/*****************************************************************************/ /* 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 --------------- 07-FEB-2020 MGD RequestAbort() accomodates HttpdSupervisor() refinement and REQUEST_STATE_ABORT used throughout server 13-JAN-2014 MGD DECnetCountConnection() 12-JUL-2009 MGD DECnetCgiDialog() and DECnetOsuDialog() WASD_FILE_DEV and WASD_FILE_DEV_n procedure 09-JUN-2007 MGD use STR_DSC 05-JAN-2006 MGD bugfix; DECnetWriteRequestBody() suppress empty record on end-of-body for OSU (call DECnetWriteRequestBodyAst()) to prevent it interfering with functionality 06-OCT-2005 MGD copy sentinals into request storage 15-JUL-2005 MGD bugfix; DECnetOsuDialog() allow CgiOutput() error responses 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 FaolToNet(), 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 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_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include /* VMS related header files */ #include #include #include #include #include /* 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_SEARCH 3 #define CGI_DCL 4 #define CGI_OUTPUT 5 #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 "invalidate cache" dialog not implemented.", ErrorOsuNoManage [] = "OSU "manage" dialog not implemented.", ErrorOsuUnknown [] = "Unknown OSU dialog ... "!&;AZ""; char DECnetWasdFileDev [] = "IF F$TRNLNM(\"WASD_FILE_DEV\").NES.\"\" THEN @WASD_FILE_DEV"; $DESCRIPTOR (DECnetWasdFileDevFaoDsc, "IF F$TRNLNM(\"WASD_FILE_DEV_!UL\").NES.\"\" THEN @WASD_FILE_DEV_!UL !UL"); LIST_HEAD DECnetConnectList; int DECnetConnectListCount, DECnetPurgeAllConnectCount; /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern int EfnNoWait, HttpdTickSecond, InstanceEnvNumber, InstanceNumber, NetReadBufferSize, OpcomMessages, OutputBufferSize; extern int ToLowerCase[], ToUpperCase[]; extern char CliScriptAs[], DclCgiVariablePrefix[], ErrorSanityCheck[], HttpdScriptAsUserName[], SoftwareID[]; 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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 (!SAME2(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 (SAME2(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 (!SAME2(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 (!SAME2(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; 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_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_DECNET, "!AZ !AZ", tkptr->ConnectString, tkptr->ScriptPtr); if (WATCHING (rqptr, WATCH_RESPONSE)) { if (ScriptAsPtr) { /* allow for possible URI '/~username/blah' */ for (uptr = ScriptAsPtr; *uptr && *uptr != '/'; uptr++); switch (tkptr->ScriptType) { case SCRIPT_CGI : WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "SCRIPT as !#AZ CGI !AZ", uptr - ScriptAsPtr, ScriptAsPtr, MappedScript); break; case SCRIPT_OSU : WatchThis (WATCHITM(rqptr), 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 (WATCHITM(rqptr), WATCH_RESPONSE, "SCRIPT CGI !AZ", MappedScript); break; case SCRIPT_OSU : WatchThis (WATCHITM(rqptr), 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. */ BOOL DECnetEnd (REQUEST_STRUCT *rqptr) { int status; DECNET_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "DECnetEnd() !&F !UL !UL !UL", &DECnetEnd, rqptr->DECnetTaskPtr->QueuedBodyRead, rqptr->DECnetTaskPtr->QueuedNetWrite, rqptr->DECnetTaskPtr->QueuedDECnetIO); tkptr = rqptr->DECnetTaskPtr; if (!tkptr->NextTaskFunction) return (true); if (tkptr->QueuedDECnetIO) { sys$cancel (tkptr->DECnetChannel); return (false); } if (rqptr->RequestState >= REQUEST_STATE_ABORT) 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); tkptr->NextTaskFunction = NULL; return (false); } /*****************************************************************************/ /* 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 (WATCHMOD (tkptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(tkptr), 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 (WATCHMOD (tkptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(tkptr), 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, LIST_ENTRY_TYPE_DECNET); } /*******************/ /* 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++; cnptr->LastUsedTime64 = tkptr->RequestPtr->rqTime.BeginTime64; /* for the first call to DECnetRead() ensure status check is OK */ tkptr->RequestPtr->NetIoPtr->WriteStatus = 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 */ /**************************/ DECnetCountConnection (); if (WATCHMOD (tkptr, 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (tkptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(tkptr), 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 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 (WATCHMOD (tkptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(tkptr), WATCH_MOD_DECNET, "DECnetConnectProbeOsu()"); tkptr->QueuedDECnetIO++; status = sys$qio (EfnNoWait, tkptr->DECnetChannel, IO$_WRITEVBLK, &tkptr->DECnetWriteIOsb, &DECnetOsuDialog, tkptr->RequestPtr, "", 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 (WATCHMOD (tkptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(tkptr), WATCH_MOD_DECNET, "DECnetConnectEnd()"); if (!(cnptr = tkptr->cnptr)) return; if (!cnptr->ReuseConnection || cnptr->IsMarkedForDisconnect) { status = sys$dassgn (cnptr->DECnetChannel); cnptr->DECnetChannel = cnptr->LifeTimeSecond = 0; DECnetCountConnection (); } /* 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "DECnetWrite() 0x!8XL !UL !UL", DataPtr, DataLength, rqptr->DECnetTaskPtr->QueuedDECnetIO); if (rqptr->RequestState >= REQUEST_STATE_ABORT) { DECnetEnd (rqptr); return; } tkptr = rqptr->DECnetTaskPtr; if (DataLength < 0) DataLength = strlen(DataPtr); if (WATCHING (rqptr, WATCH_DECNET)) { WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "DECnetWriteAst() !&F !UL !&X", &DECnetWriteAst, rqptr->DECnetTaskPtr->QueuedDECnetIO, rqptr->DECnetTaskPtr->DECnetWriteIOsb.Status); tkptr = rqptr->DECnetTaskPtr; if (tkptr->QueuedDECnetIO) tkptr->QueuedDECnetIO--; if (rqptr->RequestState >= REQUEST_STATE_ABORT) { DECnetEnd (rqptr); return; } /* reusing the IOsb, expect it to occasionally have been initialized! */ if (!tkptr->DECnetWriteIOsb.Status) tkptr->DECnetWriteIOsb.Status = SS$_NORMAL; if (WATCHING (rqptr, WATCH_DECNET)) WatchThis (WATCHITM(rqptr), 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; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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) */ if (!(cptr = rqptr->rqHeader.RequestHeaderPtr)) { denptr = RequestDictHeader (rqptr); cptr = DICT_GET_VALUE(denptr); } 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_DECNET)) { WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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) { if (tkptr->ScriptType == SCRIPT_OSU) { SysDclAst (&DECnetWriteRequestAst, rqptr); return; } 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_DECNET)) { WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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_DECNET)) WatchThis (WATCHITM(rqptr), 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 into dialog */ tkptr->OsuDialogState = OSU_DIALOG; DECnetRead (rqptr); return; } } else { /* must be OSU_DNET_INPUT */ if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) { /* back from 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "DECnetRead() !&F !&X", &DECnetRead, rqptr->NetIoPtr->WriteStatus); tkptr = rqptr->DECnetTaskPtr; if (tkptr->QueuedNetWrite) tkptr->QueuedNetWrite--; if (rqptr->RequestState >= REQUEST_STATE_ABORT) { if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false; DECnetEnd (rqptr); return; } /* abort task if NETWORK ERROR when writing TO CLIENT */ if (VMSnok (rqptr->NetIoPtr->WriteStatus)) { /* best way to clean out network buffers, etc., is to break link */ if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = false; DECnetEnd (rqptr); return; } StrDscIfNotBegin (rqptr, &rqptr->NetWriteBufferDsc, OutputBufferSize); tkptr->QueuedDECnetIO++; status = sys$qio (EfnNoWait, tkptr->DECnetChannel, IO$_READVBLK | IO$M_MULTIPLE, &tkptr->DECnetReadIOsb, &DECnetReadAst, rqptr, STR_DSC_PTR(&rqptr->NetWriteBufferDsc), STR_DSC_SIZE(&rqptr->NetWriteBufferDsc), 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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--; if (rqptr->RequestState >= REQUEST_STATE_ABORT) { DECnetEnd (rqptr); return; } /* reusing the IOsb, expect it to occasionally have been initialized! */ if (!tkptr->DECnetReadIOsb.Status) tkptr->DECnetReadIOsb.Status = SS$_NORMAL; if (WATCHING (rqptr, WATCH_DECNET)) { WatchThis (WATCHITM(rqptr), WATCH_DECNET, "READ !&S !UL byte!%s", tkptr->DECnetReadIOsb.Status, tkptr->DECnetReadIOsb.Count); if (tkptr->DECnetReadIOsb.Count) { if (tkptr->BuildRecords) WatchDataDump (STR_DSC_PTR(&rqptr->NetWriteBufferDsc) + tkptr->BuildCount, tkptr->DECnetReadIOsb.Count); else WatchDataDump (STR_DSC_PTR(&rqptr->NetWriteBufferDsc), 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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) { /*****************/ /* WASD_FILE_DEV */ /*****************/ if (InstanceEnvNumber == 1) DECnetWrite (rqptr, DECnetWasdFileDev, sizeof(DECnetWasdFileDev)-1); else { DclCommandDsc.dsc$a_pointer = tkptr->DclCommand; DclCommandDsc.dsc$w_length = sizeof(tkptr->DclCommand)-1; sys$fao (&DECnetWasdFileDevFaoDsc, &Length, &DclCommandDsc, InstanceEnvNumber, InstanceEnvNumber, InstanceEnvNumber); tkptr->DclCommand[Length] = '\0'; DECnetWrite (rqptr, tkptr->DclCommand, Length); } tkptr->CgiDialogState = CGI_SEARCH; } if (tkptr->CgiDialogState == CGI_SEARCH) { /***********************/ /* 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); strcpy (rqptr->rqCgi.EofStr, 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 = *(USHORTPTR)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_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_DECNET, "BUILD records"); } } if (tkptr->BuildRecords) { /*****************************************/ /* building 'records' from single bytes! */ /*****************************************/ cptr = STR_DSC_PTR(&rqptr->NetWriteBufferDsc) + tkptr->BuildCount; if (tkptr->DECnetReadIOsb.Count) { tkptr->BuildCount += tkptr->DECnetReadIOsb.Count; cnt = STR_DSC_SIZE(&rqptr->NetWriteBufferDsc) - 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_DECNET)) { WatchThis (WATCHITM(rqptr), WATCH_DECNET, "BUILT record !UL byte!%s", tkptr->DECnetReadIOsb.Count); WatchDataDump (STR_DSC_PTR(&rqptr->NetWriteBufferDsc), tkptr->DECnetReadIOsb.Count); } } /******************/ /* process record */ /******************/ cptr = STR_DSC_PTR(&rqptr->NetWriteBufferDsc); 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 && MATCH0 (cptr, rqptr->rqCgi.EofStr, 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "!SL", cnt); if (cnt == CGI_OUTPUT_END) { /* terminate processing */ DECnetEnd (rqptr); return; } if (cnt < 0) { /* CgiOutput() error responses have codes less than zero */ 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "DECnetFindCgiScript() !&F", &DECnetFindCgiScript); tkptr = rqptr->DECnetTaskPtr; if (tkptr->FindScriptCount && tkptr->DECnetReadIOsb.Count) { STR_DSC_PTR(&rqptr->NetWriteBufferDsc) [tkptr->DECnetReadIOsb.Count] = '\0'; if (strsame (STR_DSC_PTR(&rqptr->NetWriteBufferDsc), 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "!&Z", tkptr->RunTimePtr); cptr = tkptr->RunTimePtr; if (SAME2(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 (SAME2(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 (SAME2(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].StringPtr; while (*cptr && *cptr != ';' && *sptr) { if (TOUP(*cptr) != TOUP(*sptr)) break; cptr++; sptr++; } if (*cptr == ';' && !*sptr) break; } if (idx >= Config.cfScript.RunTimeCount) { /* nope, not found */ rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, "Execution of  !AZ  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].StringPtr; *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; unsigned short Length; char *cptr, *sptr, *zptr; char Buffer [2048]; $DESCRIPTOR (BufferDsc, Buffer); DECNET_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = rqptr->DECnetTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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) { InstanceGblSecIncrLong (&AccountingPtr->DoDECnetOsuCount); tkptr->OsuDnetRecMode = tkptr->OsuDnetCgi = false; /*****************/ /* WASD_FILE_DEV */ /*****************/ /* if the v10 OSU DECnet object (logical name) is detected */ if (v10orPrev10("?WASD_DECNET_OSU_OBJECT\0",NULL)) { /* requires a modified WWWEXEC.COM to accomodate this datum */ if (InstanceEnvNumber == 1) DECnetWrite (rqptr, DECnetWasdFileDev, sizeof(DECnetWasdFileDev)-1); else { sys$fao (&DECnetWasdFileDevFaoDsc, &Length, &BufferDsc, InstanceEnvNumber, InstanceEnvNumber, InstanceEnvNumber); Buffer[Length] = '\0'; DECnetWrite (rqptr, Buffer, Length); } } /******************/ /* 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). */ DECnetWrite (rqptr, "HTBIN", 5); DECnetWrite (rqptr, rqptr->rqHeader.MethodName, -1); if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) cptr = "HTTP/1.1"; else cptr = "HTTP/1.0"; DECnetWrite (rqptr, cptr, -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. "" 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 persistent 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 = STR_DSC_PTR(&rqptr->NetWriteBufferDsc); 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 (MATCH0 (cptr, "", 13) || MATCH0 (cptr, "", 14)) { /* forces text mode */ tkptr->OsuDnetRecMode = true; DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 9)) { /* similar to raw mode except response header is checked */ tkptr->OsuDnetCgi = true; DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 9)) { /* script handles all response formatting, return byte-for-byte */ DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 10)) { /* script sends status line then plain text */ tkptr->OsuDialogState = OSU_DNETTEXT; DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 10) || MATCH0 (cptr, "", 10) || MATCH0 (cptr, "", 11)) { /* end of script output */ DECnetEnd (rqptr); return; } /**********************/ /* information dialog */ /**********************/ if (MATCH0 (cptr, "", 9)) { /* full search argument (query string) */ cptr = VmGetHeap (rqptr, rqptr->rqHeader.QueryStringLength+2); /* OSU supplies a leading '?' query separator */ *cptr = '?'; if (rqptr->rqHeader.QueryStringLength) memcpy (cptr+1, rqptr->rqHeader.QueryStringPtr, rqptr->rqHeader.QueryStringLength); DECnetWrite (rqptr, cptr, rqptr->rqHeader.QueryStringLength+1); DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 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); DECnetWrite (rqptr, cptr, 254); } else { if (rqptr->rqHeader.QueryStringLength) memcpy (cptr+1, rqptr->rqHeader.QueryStringPtr, rqptr->rqHeader.QueryStringLength); DECnetWrite (rqptr, cptr, rqptr->rqHeader.QueryStringLength+1); } DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 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 (MATCH0 (cptr, "", 9)) { /* send the full request header */ tkptr->OsuDialogState = OSU_DNET_HDR; DECnetWriteRequest (rqptr); return; } if (MATCH0 (cptr, "", 11)) { /* begin to write the request body */ tkptr->OsuDialogState = OSU_DNET_INPUT; DECnetWriteRequest (rqptr); return; } if (MATCH0 (cptr, "", 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 (MATCH0 (cptr, "", 11)) { /* full request URL prior to any mapping */ DECnetWrite (rqptr, rqptr->rqHeader.RequestUriPtr, -1); DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 14)) { /* invalidate cache */ rqptr->rqResponse.HttpStatus = 501; ErrorGeneral (rqptr, ErrorOsuNoInvCache, FI_LI); DECnetEnd (rqptr); return; } if (MATCH0 (cptr, "", 10)) { /* server host name */ DECnetWrite (rqptr, rqptr->ServicePtr->ServerHostName, -1); DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 8)) { /* composite line of request data */ DECnetOsuDnetId (rqptr, tkptr, false); return; } if (MATCH0 (cptr, "", 9)) { /* composite line of request data (plus a bit!) */ DECnetOsuDnetId (rqptr, tkptr, true); return; } if (MATCH0 (cptr, "", 12)) { /* server management */ rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorOsuNoManage, FI_LI); DECnetEnd (rqptr); return; } if (MATCH0 (cptr, "", 11)) { if (Config.cfScript.DECnetReuseLifeTime) { /* reuse enabled, do not drop DECnet link */ if (tkptr->cnptr) tkptr->cnptr->ReuseConnection = true; } DECnetRead (rqptr); return; } if (MATCH0 (cptr, "", 11) || MATCH0 (cptr, "", 12)) { /* into translate state */ tkptr->OsuDialogState = OSU_DNET_XLATE; /* read the path to be translated */ DECnetRead (rqptr); return; } /* hmmm, unknown dialog tag */ { status = FaoToBuffer (Buffer, sizeof(Buffer), NULL, ErrorOsuUnknown, cptr); if (VMSnok (status) || status == SS$_BUFFEROVF) ErrorNoticed (rqptr, status, NULL, 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 (MATCH0 (cptr, "", 10) || MATCH0 (cptr, "", 10) || MATCH0 (cptr, "", 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 < 0) { /* CgiOutput() error responses have codes less than zero */ rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(tkptr->RequestPtr,MSG_SCRIPT_RESPONSE_ERROR), FI_LI); DECnetEnd (rqptr); return; } } 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 (MATCH0 (cptr, "", 10) || MATCH0 (cptr, "", 10) || MATCH0 (cptr, "", 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); if (cnt < 0) { /* CgiOutput() error responses have codes less than zero */ rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(tkptr->RequestPtr,MSG_SCRIPT_RESPONSE_ERROR), FI_LI); DECnetEnd (rqptr); return; } } 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "DECnetOsuDnetId()"); cptr = STR_DSC_PTR(&rqptr->NetWriteBufferDsc); 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->ClientPtr->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->ClientPtr->IpAddress)) sys$fao (&SignedLongFaoDsc, 0, &StringDsc, IPADDRESS_ADR4(&rqptr->ClientPtr->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->ClientPtr->Lookup.HostName; *cptr && sptr < zptr; *sptr++ = *cptr++); } *sptr = '\0'; /* it's an asynchronous write so we need some persistent 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 (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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, NULL); TransPathLength = MapOdsVmsToUrl (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, DassgnCount, MinSeconds; LIST_ENTRY *leptr; DECNET_CONNECT *cnptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_DECNET) && PeriodSeconds != -1) WatchThis (WATCHALL, 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) { DECnetCountConnection (); return (true); } DassgnCount = 0; 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->RequestPtr) { if (cnptr->RequestPtr->RequestState >= REQUEST_STATE_ABORT) { ContinueTicking = true; DECnetEnd (cnptr->RequestPtr); 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; DassgnCount++; } if (WATCH_MODULE(WATCH_MOD_DECNET)) WatchThis (WATCHALL, WATCH_MOD_DECNET, "SUPERVISOR !&B !UL", ContinueTicking, MinSeconds); if (DassgnCount) DECnetCountConnection (); 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); } /*****************************************************************************/ /* Update the accounting accumulator. */ DECnetCountConnection () { int CgiCount, ConnectCount, OsuCount; DECNET_CONNECT *cnptr; LIST_ENTRY *leptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_DECNET)) WatchThis (WATCHALL, WATCH_MOD_DECNET, "DECnetCountConnection()"); ConnectCount = CgiCount = OsuCount = 0; for (leptr = DECnetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr) { cnptr = (DECNET_CONNECT*)leptr; if (!cnptr->DECnetChannel) continue; ConnectCount++; if (!cnptr->RequestPtr) continue; if (!cnptr->RequestPtr->DECnetTaskPtr) continue; if (cnptr->RequestPtr->DECnetTaskPtr->ScriptType == SCRIPT_CGI) CgiCount++; else if (cnptr->RequestPtr->DECnetTaskPtr->ScriptType == SCRIPT_OSU) OsuCount++; } InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->CurrentDECnetTasks[InstanceNumber] = ConnectCount; AccountingPtr->CurrentDECnetCGI[InstanceNumber] = CgiCount; AccountingPtr->CurrentDECnetOSU[InstanceNumber] = OsuCount; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /*****************************************************************************/ /* Return a report on DECnet scripting, in particular displaying each item in the connection list. */ DECnetScriptingReport (REQUEST_STRUCT *rqptr) { static char BeginPageFao [] = "

\n\ \n\
\n\ \n\ \n\ \n\ \n\ \n\ \n\
CountReuse
Total:!&L!&L
CGI-script:!&L!&L
OSU-script:!&L!&L
As:!AZ
\n\
\n\ \

\n\ \ \ \ \ \ \ \ \n"; static char ItemFao [] = "\ \ \ \ \ \ \n\ !&@"; static char EmptyConnectListFao [] = "\ \n"; static char ButtonsFao [] = "
Connect / ClientLifetime / RequestReusedTotalLast
!3ZL!&@!AZ!&L!&L!20%D
000empty
\n\

\n\ \n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\ !AZ\ \n\ \n\ \n"; int status, Count; unsigned long FaoVector [32]; unsigned long *vecptr; char *cptr; DECNET_CONNECT *cnptr; LIST_ENTRY *leptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_DECNET, "DECnetScriptingReport()"); AdminPageTitle (rqptr, "DECnet Scripting Report"); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); vecptr = FaoVector; *vecptr++ = AccountingPtr->DoDECnetCount; *vecptr++ = AccountingPtr->DoDECnetReuseCount; *vecptr++ = AccountingPtr->DoDECnetCgiCount; *vecptr++ = AccountingPtr->DoDECnetCgiReuseCount; *vecptr++ = AccountingPtr->DoDECnetOsuCount; *vecptr++ = AccountingPtr->DoDECnetOsuReuseCount; if (HttpdScriptAsUserName[0]) *vecptr++ = HttpdScriptAsUserName; else *vecptr++ = HttpdProcess.UserName; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); status = FaolToNet (rqptr, BeginPageFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); Count = 0; for (leptr = DECnetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr) { cnptr = (DECNET_CONNECT*)leptr; if (WATCHMOD (rqptr, WATCH_MOD_DECNET)) WatchThis (WATCHITM(rqptr), 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++ = "!AZ"; *vecptr++ = cnptr->ConnectString; } *vecptr++ = MetaConShowSeconds (rqptr, cnptr->LifeTimeSecond - HttpdTickSecond); *vecptr++ = cnptr->ReUsageCount; *vecptr++ = cnptr->UsageCount; *vecptr++ = &cnptr->LastUsedTime64; if (cnptr->RequestPtr) { *vecptr++ = "!&@!AZ\ !&;AZ\n"; if (cnptr->RequestPtr->RemoteUser[0]) { *vecptr++ = "!&;AZ@"; *vecptr++ = cnptr->RequestPtr->RemoteUser; } else *vecptr++ = ""; *vecptr++ = cnptr->RequestPtr->ClientPtr->Lookup.HostName; *vecptr++ = cnptr->RequestPtr->rqHeader.RequestUriPtr; } else *vecptr++ = "\ idle\n"; status = FaolToNet (rqptr, ItemFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } if (!DECnetConnectList.HeadPtr) { status = FaolToNet (rqptr, EmptyConnectListFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } vecptr = FaoVector; *vecptr++ = ADMIN_CONTROL_DECNET_PURGE; *vecptr++ = ADMIN_CONTROL_DECNET_DISCONNECT; *vecptr++ = AdminRefresh(); status = FaolToNet (rqptr, ButtonsFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc); AdminEnd (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, DassgnCount, DisconnectedCount, MarkedCount; unsigned short Length; LIST_ENTRY *leptr; DECNET_CONNECT *cnptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_DECNET)) WatchThis (WATCHALL, WATCH_MOD_DECNET, "DECnetControlPurgeAllConnect() !UL", WithExtremePrejudice); DECnetPurgeAllConnectCount++; DassgnCount = 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++; DassgnCount++; continue; } else { /* connection is currently active, disconnect when finished */ cnptr->IsMarkedForDisconnect = true; MarkedCount++; } } if (DassgnCount) DECnetCountConnection (); sys$fao (&ResponseFaoDsc, &Length, &ResponseDsc, DisconnectedCount, MarkedCount); Response[Length] = '\0'; return (Response); } /*****************************************************************************/