/*****************************************************************************/ /* FCGIplus.c Copyright (C) 2003-2005 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 2. FastCGI Copyright (C) 1995-1996 Open Market, Inc. CGI/CGIplus/RTE interface for connectivity to a FastCGI application server. The CGI activated capability has some limitations due to the way CGI variables are handled using DCL symbols but this is not a significant limitation as the obvious efficiencies are to be derived using CGIplus and RTE activations. Currently supports the RESPONDER role. This interface provides single-threaded (only one request will be using each instance of FCGIPLUS at a time) communication with a FastCGI application. It drives application server output reads using ASTs to conform with FastCGI's asynchronous specification for this requirement (interleaved STDIN reading with STDOUT writing). The local IP port is used as the request identifier (in this single-threaded but multi-instance interface it's a low-cost, unique identifier). As VMS is big-endian, so network-order values in the FastCGI data structures are created using htons(), htonl(), etc, rather than manipulating the bytes individually. MAPPING RULES ------------- These will vary according to usage requirements. There are a number of ways to configure script activations. If the script component of the path needs to vary with request then the RTE variant will be required. If constant then CGIplus can be used just as well. All FastCGI mappings require a specification for the application server host and port. These are examples. exec+ /fcgi/* ($CGI-BIN:[000000]FCGIPLUS)/* script=nofind \ script=params=fcgi_connect=localhost:45678 script+ /an_fcgi_script* /cgi-bin/fcgiplus* \ script=params=fcgi_connect=localhost:45678 OTHER CONFIGURATION ------------------- Nothing in HTTPD$CONFIG specific to FCGIPLUS. The FCGIPLUS interface itself uses some specific environment variables to tailor it's behaviour. These are generally provided using mapping rules but could be defined with logical names or assigned in DCL wrapper procedures. FCGI_CONNECT Provides the application server host and port as a string. script=params=fcgi_connect=localhost:45678 FCGI_BUFFER The default application read buffer is 16384 bytes in size. It must be large enough to accomodate any FastCGI PDU sent by the application. If not an error is reported. This environment variable can be used to set this buffer size. The FastCGI Specification limits this at 65,535 bytes maximum. script=params=(fcgi_connect=localhost:45678,fcgi_buffer=32768) FCGI_STDERR Include the application server's STDERR stream in request output. Normally this goes into the bit-bucket but may be useful when 'debugging'. script=params=(fcgi_connect=localhost:45678,fcgi_stderr=1) FCGI_WATCH Provide 'debugging' information as plain-text output. Significant events and a full hexadecimal dump of network traffic provide a useful snapshot of any one transaction. script=params=(fcgi_connect=localhost:45678,fcgi_watch=1) WARNING! -------- Don't forget that when using persistant environments such as CGIplus/RTE, especially during development, once changes have been made to source code and the environment rebuilt any currently executing instances of the previous build must be purged from the server environment (wish I had a dollar for every time I'd been caught like this myself!) $ HTTPD/DO=DCL=PURGE LOGICAL NAMES ------------- FCGIPLUS$DBUG turns on all "if (dbug)" statements BUILD DETAILS ------------- $ @BUILD_FCGIPLUS BUILD !compile+link $ @BUILD_FCGIPLUS LINK !link-only VERSION HISTORY (update SOFTWAREID as well!) --------------- 20-AUG-2005 MGD v1.0.1, minor conditional mods to support IA64 26-JUN-2003 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "1.0.1" #define SOFTWARENM "FCGIPLUS" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif /************/ /* includes */ /************/ /* standard C header files */ #include #include #include #include #include #include /* VMS-specific header files */ #include #include #include #include #include #include #include /* Internet-related header files */ #include #include #include #include /* define required values from TCPIP$INETDEF.H (Multinet does not supply one) */ #define INET_PROTYP$C_STREAM 1 #define INETACP$C_TRANS 2 #define INETACP_FUNC$C_GETHOSTBYNAME 1 #define INETACP_FUNC$C_GETHOSTBYADDR 2 #define TCPIP$C_AF_INET 2 #define TCPIP$C_DSC_ALL 2 #define TCPIP$C_FULL_DUPLEX_CLOSE 8192 #define TCPIP$C_REUSEADDR 4 #define TCPIP$C_SOCK_NAME 4 #define TCPIP$C_SOCKOPT 1 #define TCPIP$C_TCP 6 #define TCPIP$C_TCPOPT 6 #define TCPIP$C_TCP_NODELACK 9 /* FCGI specific includes */ #include "fastcgi.h" /**********/ /* macros */ /**********/ #define boolean int #define true 1 #define false 0 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define FI_LI __LINE__ #define READ_BUFFER_MAX 16384 #define WRITE_BUFFER_MAX 8192 /******************/ /* global storage */ /******************/ /* change to 0 to remove all (dbug) code from executable */ #define DBUG 1 #if DBUG int dbug, CgiVarDebug; #else #define DBUG 0 #define CgiVarDebug 0 #endif int AppStatus, IncludeStdErr, IsCgiPlus, NetReadCount, NetWriteCount, OutputCount, ProtocolStatus, ReadCount, ReadStatus, ServerVersion, UsageCount, WatchIt; unsigned short AppChannel, LocalIpPort, FcgiRequestId; char *CgiPlusEofPtr, *CgiPlusEotPtr, *CgiPlusEscPtr; int ReadBufferSize, ReadBufferMax = READ_BUFFER_MAX; char *ReadBufferPtr; char WriteBuffer [WRITE_BUFFER_MAX]; char Utility [] = "FCGIPLUS", ErrorAccessing [] = "accessing application server.", ErrorBufferSizeRead [] = "application read buffer.", ErrorCommunicating [] = "communicating with application server.", ErrorConfigBuffer [] = "Application read buffer configuration error.", ErrorConfigConnect [] = "Application server not configured.", ErrorRequestId [] = "Application server request ID error."; struct AnIOsb { unsigned short Status; unsigned short Count; unsigned long Unused; }; struct AnIOsb ReadIOsb; struct AnIOsb WriteIOsb; $DESCRIPTOR (InetDeviceDsc, "TCPIP$DEVICE"); unsigned short RemoteChannel; int OptionEnabled = 1; struct { unsigned short Length; unsigned short Parameter; void *Address; } ReuseAddress = { sizeof(OptionEnabled), TCPIP$C_REUSEADDR, &OptionEnabled }, ReuseAddressSocketOption = { sizeof(ReuseAddress), TCPIP$C_SOCKOPT, &ReuseAddress }, NoDelAck = { sizeof(OptionEnabled), TCPIP$C_TCP_NODELACK, &OptionEnabled }, NoDelAckTcpOption = { sizeof(NoDelAck), TCPIP$C_TCPOPT, &NoDelAck }; unsigned short TcpSocket [3] = { TCPIP$C_TCP, INET_PROTYP$C_STREAM, TCPIP$C_AF_INET }; /**************/ /* prototypes */ /**************/ int AppConnect (char*); int AppRead (int); int AppWrite (void*, int); void AppClose (); char* CgiVar (char*); char* CgiVarDclSymbolName (char*); void DumpBytes (char*, int); void ProcessRequest (); void ReportError (char*, int, int); /*****************************************************************************/ /* */ main (int argc, char *argv[]) { char *cptr; /*********/ /* begin */ /*********/ #if DBUG if (dbug = ((getenv ("FCGIPLUS$DBUG")) != NULL)) { /** CgiVarDebug = dbug; **/ fprintf (stdout, "Content-Type: text/plain\n\n"); } #endif /* if it doesn't look like CGI environment then forget it */ if (getenv ("HTTP$INPUT")) if (!(stdin = freopen ("HTTP$INPUT:", "r", stdin, "ctx=bin"))) exit (vaxc$errno); if (!dbug) { /* binary mode to eliminate carriage-control */ if (!(stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin"))) exit (vaxc$errno); if (!(stderr = freopen ("SYS$OUTPUT:", "w", stderr, "ctx=bin"))) exit (vaxc$errno); } IsCgiPlus = ((CgiPlusEofPtr = getenv("CGIPLUSEOF")) != NULL); CgiPlusEotPtr = getenv("CGIPLUSEOT"); CgiPlusEscPtr = getenv("CGIPLUSESC"); if (IsCgiPlus) { for (;;) { if (dbug) fprintf (stdout, "Content-Type: text/plain\n\n"); /* block waiting for the first/next request */ CgiVar (""); ProcessRequest (); fflush (stdout); fputs (CgiPlusEofPtr, stdout); fflush (stdout); } } else { if (dbug) fprintf (stdout, "Content-Type: text/plain\n\n"); ProcessRequest (); } } /*****************************************************************************/ /* Process a single CGI/CGIplus/RTE request. All request processing occurs within this function. */ void ProcessRequest () { int status, len, nlen, plen, rlen, vlen, ContentLength; char *bptr, *cptr, *sptr, *tptr, *zptr; FCGI_Header *fhptr; FCGI_BeginRequestBody *fbrbptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "ProcessRequest()\n"); UsageCount++; NetReadCount = NetWriteCount = OutputCount = 0; if (!ServerVersion) { cptr = CgiVar ("SERVER_SIGNATURE"); if (cptr) { /* should be something like "HTTPd-WASD/8.2.1 OpenVMS/AXP SSL" */ while (*cptr && !isdigit(*cptr)) cptr++; ServerVersion = atoi(cptr) * 10000; while (*cptr && isdigit(*cptr)) cptr++; if (*cptr) cptr++; ServerVersion += atoi(cptr) * 100; while (*cptr && isdigit(*cptr)) cptr++; if (*cptr) cptr++; ServerVersion += atoi(cptr); /* resulting in a number like 80,201 */ if (ServerVersion < 80000) ServerVersion = 0; } } /* specified using a 'set * script=params=fcgi_buffer=..' */ cptr = CgiVar ("FCGI_BUFFER"); if (!cptr) cptr = getenv("FCGI_BUFFER"); if (cptr) ReadBufferMax = atoi(cptr); if (ReadBufferMax <= 0) { ReportError (ErrorConfigBuffer, 0, FI_LI); return; } /* specified using a 'set * script=params=fcgi_stderr=1' */ cptr = CgiVar ("FCGI_STDERR"); if (!cptr) cptr = getenv("FCGI_STDERR"); if (cptr && *cptr == '1') IncludeStdErr = true; else IncludeStdErr = false; if (WatchIt && !dbug) { /* proactively back to binary mode */ if (!(stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=bin"))) exit (vaxc$errno); } /* specified using a 'set * script=params=fcgi_watch=1' */ cptr = CgiVar ("FCGI_WATCH"); if (!cptr) cptr = getenv("FCGI_WATCH"); if (cptr && *cptr == '1') WatchIt = true; else WatchIt = false; if (WatchIt) { /* back to record mode avoiding extraneous carriage control */ if (!(stdout = freopen ("SYS$OUTPUT:", "w", stdout, "ctx=rec"))) exit (vaxc$errno); fprintf (stdout, "Content-Type: text/plain\r\n\r\n|++ %s USAGE %d\n", SOFTWAREID, UsageCount); } /* specified using a 'set * script=params=fcgi_connect=..' */ cptr = CgiVar ("FCGI_CONNECT"); if (!cptr) cptr = getenv("FCGI_CONNECT"); if (!cptr) { ReportError (ErrorConfigConnect, 0, FI_LI); return; } status = AppConnect (cptr); if (WatchIt) fprintf (stdout, "|++ CONNECT %s %%X%08.08X\n", cptr, status); if (VMSnok (status)) { ReportError (ErrorAccessing, status, FI_LI); return; } FcgiRequestId = LocalIpPort; /*****************/ /* begin request */ /*****************/ fhptr = (FCGI_Header*)(bptr = WriteBuffer); fhptr->version = FCGI_VERSION_1; fhptr->type = FCGI_BEGIN_REQUEST; *(short*)&fhptr->requestIdB1 = htons(FcgiRequestId); *(short*)&fhptr->contentLengthB1 = htons(sizeof(FCGI_BeginRequestBody)); fhptr->paddingLength = 0; fhptr->reserved = 0; fbrbptr = (FCGI_BeginRequestBody*)(bptr + sizeof(FCGI_Header)); *(short*)&fbrbptr->roleB1 = htons(FCGI_RESPONDER); fbrbptr->flags = 0; memset (&fbrbptr->reserved, 0, sizeof(fbrbptr->reserved)); if (WatchIt) fprintf (stdout, "|++ FCGI_BEGIN_REQUEST id:%d FCGI_RESPONDER\n", FcgiRequestId); len = sizeof(FCGI_Header) + sizeof(FCGI_BeginRequestBody); status = AppWrite (WriteBuffer, len); if (VMSnok (status)) { ReportError (ErrorCommunicating, status, FI_LI); AppClose (); return; } /******************/ /* provide params */ /******************/ fhptr = (FCGI_Header*)(bptr = WriteBuffer); /* populate data structure body with CGI variables */ sptr = bptr + sizeof(FCGI_Header); zptr = bptr + sizeof(WriteBuffer); while (cptr = CgiVar("*")) { /* these request CGI data are not required to be propagated */ if (!memcmp (cptr, "fcgi_", 5) || !memcmp (cptr, "FCGI_", 5) || !memcmp (cptr, "KEY_", 4) || !memcmp (cptr, "FORM_", 5) || !memcmp (cptr, "GATEWAY_EOF", 11) || !memcmp (cptr, "GATEWAY_EOT", 11) || !memcmp (cptr, "GATEWAY_ESC", 11)) continue; for (tptr = cptr; *tptr && *tptr != '='; tptr++); nlen = tptr - cptr; if (*tptr) tptr++; while (*tptr) tptr++; vlen = tptr - cptr - nlen - 1; if (dbug) fprintf (stdout, "|%s| %d %d\n", cptr, nlen, vlen); if (WatchIt) fprintf (stdout, "|%*.*s|%*.*s|\n", nlen, nlen, cptr, vlen, vlen, cptr+nlen+1); if (nlen <= 127) { *(unsigned char*)sptr = nlen; sptr++; } else { *(unsigned long*)sptr = htonl(nlen) | 0x80; sptr += 4; } if (vlen <= 127) { *(unsigned char*)sptr = vlen; sptr++; } else { *(unsigned long*)sptr = htonl(vlen) | 0x80; sptr += 4; } while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } /* provide any required padding */ plen = len = sptr - bptr - sizeof(FCGI_Header); while (plen % 8 && sptr < zptr) { *sptr++ = 0; plen++; } plen -= len; if (sptr > zptr) exit (SS$_RESULTOVF); fhptr->version = FCGI_VERSION_1; fhptr->type = FCGI_PARAMS; *(short*)&fhptr->requestIdB1 = htons(FcgiRequestId); *(short*)&fhptr->contentLengthB1 = htons(len); fhptr->paddingLength = plen; fhptr->reserved = 0; if (WatchIt) fprintf (stdout, "|++ FCGI_PARAMS %d+%d\n", len, plen); len = len + plen + sizeof(FCGI_Header); status = AppWrite (WriteBuffer, len); if (VMSnok (status)) { ReportError (ErrorCommunicating, status, FI_LI); AppClose (); return; } /* end of params */ *(short*)&fhptr->contentLengthB1 = htons(0); fhptr->paddingLength = 0; if (WatchIt) fprintf (stdout, "|++ FCGI_PARAMS 0+0\n"); len = sizeof(FCGI_Header); status = AppWrite (WriteBuffer, len); if (VMSnok (status)) { ReportError (ErrorCommunicating, status, FI_LI); AppClose (); return; } /***************************/ /* begin asynchronous read */ /***************************/ status = AppRead (0); if (VMSnok (status)) { ReportError (ErrorCommunicating, status, FI_LI); AppClose (); return; } /****************/ /* request body */ /****************/ fhptr = (FCGI_Header*)(bptr = WriteBuffer); fhptr->version = FCGI_VERSION_1; fhptr->type = FCGI_STDIN; *(short*)&fhptr->requestIdB1 = htons(FcgiRequestId); fhptr->paddingLength = 0; fhptr->reserved = 0; cptr = CgiVar("CONTENT_LENGTH"); if (cptr) ContentLength = atoi(cptr); else ContentLength = 0; if (dbug) fprintf (stdout, "%d\n", ContentLength); while (ContentLength > 0) { sptr = bptr + sizeof(FCGI_Header); zptr = bptr + sizeof(WriteBuffer); len = sizeof(WriteBuffer) - sizeof(FCGI_Header); if (len > ContentLength) len = ContentLength; rlen = read (fileno(stdin), sptr, len); if (dbug) fprintf (stdout, "read() %d\n", len); if (WatchIt) { fprintf (stdout, "|<> %d bytes\n", rlen); if (rlen > 0) DumpBytes (sptr, rlen); } if (rlen <= 0) break; /* provide any required padding */ sptr += rlen; plen = rlen; while (plen % 8 && sptr < zptr) { *sptr++ = 0; plen++; } plen -= rlen; /* 'sptr + rlen' will result in it being exactly on the end-of-buffer */ if (sptr > zptr) exit (SS$_RESULTOVF); *(short*)&fhptr->contentLengthB1 = htons(rlen); fhptr->paddingLength = plen; if (WatchIt) fprintf (stdout, "|++ FCGI_STDIN %d+%d\n", rlen, plen); len = rlen + plen + sizeof(FCGI_Header); status = AppWrite (WriteBuffer, len); if (VMSnok (status)) { ReportError (ErrorCommunicating, status, FI_LI); break; } ContentLength -= rlen; } /* end of stdin */ *(short*)&fhptr->contentLengthB1 = htons(0); fhptr->paddingLength = 0; if (WatchIt) fprintf (stdout, "|++ FCGI_STDIN 0+0\n"); len = sizeof(FCGI_Header); status = AppWrite (WriteBuffer, len); if (VMSnok (status)) ReportError (ErrorCommunicating, status, FI_LI); /**********************************/ /* hibernate while reading output */ /**********************************/ sys$hiber (); /******************/ /* end of request */ /******************/ AppClose (); } /****************************************************************************/ /* Open a "socket" to the FastCGI application at the host name and port. */ AppConnect (char *HostPortPtr) { int status, IpAddress, LocalSocketLength; unsigned short Channel, PortNumber; char *cptr, *sptr, *zptr; char HostName [128]; struct AnIOsb IOsb; struct sockaddr_in SocketName, LocalSocketName; struct hostent *HostEntryPtr; struct { unsigned long Length; void *Address; int *LengthPtr; } SocketNameItem = { sizeof(SocketName), &SocketName, 0 }, LocalSocketNameItem = { sizeof(LocalSocketName), &LocalSocketName, &LocalSocketLength }; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "AppConnect() %s\n", HostPortPtr); zptr = (sptr = HostName) + sizeof(HostName)-1; for (cptr = HostPortPtr; *cptr && *cptr != ':' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (*cptr) cptr++; PortNumber = (unsigned short)atoi(cptr); if (dbug) fprintf (stdout, "%s %d\n", HostName, PortNumber); for (cptr = HostName; isdigit(*cptr) || *cptr == '.'; cptr++); if (*cptr) { /* "the.host.name" */ if (!(HostEntryPtr = gethostbyname (HostName))) { if (vaxc$errno == RMS$_RNF) return (SS$_NOSUCHNODE); if (vaxc$errno == SS$_ENDOFFILE) return (SS$_NOSUCHNODE); return (vaxc$errno); } memcpy (&IpAddress, HostEntryPtr->h_addr, 4); } else { /* "131.185.2.4" */ if ((IpAddress = inet_addr (HostName)) == -1) return (vaxc$errno); } /* assign a channel to the internet template device */ if (VMSnok (status = sys$assign (&InetDeviceDsc, &AppChannel, 0, 0))) return (status); /* make the channel a TCP, connection-oriented socket */ status = sys$qiow (0, AppChannel, IO$_SETMODE, &IOsb, 0, 0, &TcpSocket, 0, 0, 0, &NoDelAckTcpOption, 0); if (dbug) fprintf (stdout, "sys$qiow() %%X%08.08X IOsb.Status %%X%08.08X\n", status, IOsb.Status); if (VMSok (status) && VMSnok (IOsb.Status)) status = IOsb.Status; if (VMSnok (status)) return (status); SocketName.sin_family = AF_INET; SocketName.sin_port = htons (PortNumber); memcpy (&SocketName.sin_addr.s_addr, &IpAddress, 4); status = sys$qiow (0, AppChannel, IO$_ACCESS, &IOsb, 0, 0, 0, 0, &SocketNameItem, 0, 0, 0); if (dbug) fprintf (stdout, "sys$qiow() %%X%08.08X IOsb.Status %%X%08.08X\n", status, IOsb.Status); if (VMSok (status) && VMSnok (IOsb.Status)) status = IOsb.Status; if (VMSnok (status)) return (status); status = sys$qiow (0, AppChannel, IO$_SENSEMODE, &IOsb, 0, 0, 0, 0, &LocalSocketNameItem, 0, 0, 0); if (dbug) fprintf (stdout, "sys$qiow() %%X%08.08X IOsb.Status %%X%08.08X\n", status, IOsb.Status); if (VMSok (status) && VMSnok (IOsb.Status)) status = IOsb.Status; if (VMSok (status)) { LocalIpPort = LocalSocketName.sin_port; if (dbug) fprintf (stdout, "LocalIpPort: %d\n", LocalIpPort); } else { sys$dassgn (AppChannel); AppChannel = 0; } return (status); } /****************************************************************************/ /* ASYNCHRONOUS read from application server into a dynamically allocated buffer. Process the data in the buffer as a FastCGI application output stream. */ int AppRead (int AstParam) { int id, status, len, nlen, plen, BufferRemaining; char *bptr, *cptr, *sptr; FCGI_Header *fhptr; FCGI_EndRequestBody *ferbptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "AppRead() %d\n", AstParam); if (AstParam) { /* this is data from a completed read rather than the initial read */ if (dbug) fprintf (stdout, "sys$qiow() ReadIOsb.Status %%X%08.08X %d bytes\n", ReadIOsb.Status, ReadIOsb.Count); ReadStatus = ReadIOsb.Status; ReadCount = ReadIOsb.Count; if (WatchIt) { fprintf (stdout, "|<- %%X%08.08X\n|<- %d bytes\n", ReadStatus, ReadCount); DumpBytes (ReadBufferPtr, ReadCount); } if (VMSnok (ReadStatus)) { ReportError (ErrorCommunicating, ReadStatus, FI_LI); AppClose (); sys$wake (0, 0); return (SS$_NORMAL); } NetReadCount += ReadCount; BufferRemaining = ReadCount; fhptr = (FCGI_Header*)(bptr = ReadBufferPtr); for (;;) { len = ntohs(*(short*)&fhptr->contentLengthB1); plen = fhptr->paddingLength; BufferRemaining -= sizeof(FCGI_Header); if (len > BufferRemaining) { /* out read buffer is not big enough for the application */ ReportError (ErrorBufferSizeRead, SS$_RESULTOVF, FI_LI); AppClose (); sys$wake (0, 0); return (SS$_NORMAL); } BufferRemaining -= len + plen; id = htons(*(short*)&fhptr->requestIdB1); if (WatchIt) { switch (fhptr->type) { case FCGI_STDOUT: fprintf (stdout, "|++ FCGI_STDOUT id:%d %d+%d\n", id, len, plen); break; case FCGI_STDERR: fprintf (stdout, "|++ FCGI_STDERR id:%d %d+%d\n", id, len, plen); break; case FCGI_END_REQUEST: fprintf (stdout, "|++ FCGI_END_REQUEST id:%d %d+%d\n", id, len, plen); break; default : fprintf (stdout, "|++ id:%d ?%d? %d+%d\n", id, fhptr->type, len, plen); } } if (id != FcgiRequestId) { /* hmmm, something's got confused somewhere */ ReportError (ErrorRequestId, 0, FI_LI); AppClose (); sys$wake (0, 0); return (SS$_NORMAL); } cptr = bptr + sizeof(FCGI_Header); if (dbug) fprintf (stdout, "type: %d len: %d\n", fhptr->type, len); switch (fhptr->type) { case FCGI_STDOUT: if (len) { fwrite (cptr, len, 1, stdout); OutputCount += len; } break; case FCGI_STDERR: if (len && IncludeStdErr) { fwrite (cptr, len, 1, stdout); OutputCount += len; } break; case FCGI_END_REQUEST: ferbptr = (FCGI_EndRequestBody*)cptr; AppStatus = ntohl(*(long*)&ferbptr->appStatusB1); ProtocolStatus = ferbptr->protocolStatus; if (WatchIt) fprintf (stdout, "|++ STATUS app:%d pro:%d\n", AppStatus, ProtocolStatus); sys$wake (0, 0); return (SS$_NORMAL); default : /* unknown and/or uncared about */ break; } /* next structure in the stream */ bptr += sizeof(FCGI_Header) + len + plen; fhptr = (FCGI_Header*)bptr; /* if end of buffer, break to queue another read */ if ((char*)fhptr >= ReadBufferPtr + ReadCount) break; } } if (ReadBufferSize < ReadBufferMax) { ReadBufferSize = ReadBufferMax; if (ReadBufferPtr) free (ReadBufferPtr); ReadBufferPtr = calloc (1, ReadBufferSize); if (!ReadBufferPtr) exit (vaxc$errno); } /* asynchronous read */ status = sys$qio (0, AppChannel, IO$_READVBLK, &ReadIOsb, &AppRead, 1, ReadBufferPtr, ReadBufferSize, 0, 0, 0, 0); if (dbug) fprintf (stdout, "sys$qiow() %%X%08.08X\n", status); if (VMSnok (status)) exit (status); return (status); } /****************************************************************************/ /* Write the supplied data to the application. */ int AppWrite ( void *DataPtr, int DataLength ) { int status; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "AppWrite() %d\n", DataLength); if (WatchIt) { fprintf (stdout, "|-> %d bytes\n", DataLength); DumpBytes (DataPtr, DataLength); } status = sys$qiow (0, AppChannel, IO$_WRITEVBLK, &WriteIOsb, 0, 0, DataPtr, DataLength, 0, 0, 0, 0); if (dbug) fprintf (stdout, "sys$qiow() %%X%08.08X IOsb.Status %%X%08.08X\n", status, WriteIOsb.Status); if (VMSok (status) && VMSnok (WriteIOsb.Status)) status = WriteIOsb.Status; if (WatchIt) fprintf (stdout, "|-> %%X%08.08X\n", status); if (VMSok (status)) NetWriteCount += WriteIOsb.Count; return (status); } /****************************************************************************/ /* Close the network connection to the application server. */ void AppClose () { /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "AppClose() %d\n", AppChannel); if (!AppChannel) return; if (WatchIt) fprintf (stdout, "|++ CLOSE tx:%d rx:%d stdout:%d\n", NetWriteCount, NetReadCount, OutputCount); sys$dassgn (AppChannel); AppChannel = 0; } /*****************************************************************************/ /* Generate a standard WASD-like error message. WASD 8.2 or later, use "Script-Control:". */ void ReportError ( char *ErrorString, int StatusValue, int SourceLineNumber ) { static int PrevUsageCount; int status; unsigned short Length; char *cptr, *sptr; char StatusMsg [256]; $DESCRIPTOR (StatusMsgDsc, StatusMsg); /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "ReportError()\n"); /* only report the first error in any one request */ if (UsageCount == PrevUsageCount) return; PrevUsageCount = UsageCount; if (StatusValue) { status = sys$getmsg (StatusValue, &Length, &StatusMsgDsc, 1, 0); if (VMSnok(status)) exit (status); StatusMsg[Length] = '\0'; StatusMsg[0] = toupper(StatusMsg[0]); } else StatusMsg[0] = '\0'; if (OutputCount) { /*************************************/ /* already into the stream of output */ /*************************************/ fprintf (stdout, "\n\n+++++ %s ERROR: %s%s%s +++++\n\n", Utility, StatusValue ? StatusMsg : "", StatusValue ? " ... " : "", ErrorString); return; } if (ServerVersion >= 80200) { /*********************/ /* WASD 8.2 or later */ /*********************/ fprintf (stdout, "Status: 502\r\n\ Script-Control: X-error-module=\"%s\"\r\n\ Script-Control: X-error-line=%d\r\n\ Script-Control: X-error-text=\"%s\"\r\n", Utility, SourceLineNumber, ErrorString); if (StatusValue) fprintf (stdout, "Script-Control: X-error-vms-status=%%X%08.08X\r\n", StatusValue); fputs ("\r\n", stdout); return; } /*************************/ /* WASD earlier than 8.2 */ /*************************/ if (!(cptr = CgiVar("SERVER_SOFTWARE"))) cptr = "?"; sptr = CgiVar ("SERVER_SIGNATURE"); fprintf (stdout, "Status: 502\r\n\ Content-Type: text/html\r\n\ \r\n\ \n\ \n\ \n\ \n\ \n\ \n\ ERROR 502\n\ \n\ \n\ ERROR 502  -  Bad Gateway\n\

%s%s%s\n\ %s%s%s\ \n\ \n", SOFTWAREID, cptr, Utility, SourceLineNumber, StatusValue ? StatusMsg : "", StatusValue ? "  ...  " : "", ErrorString, sptr ? "


\n" : "", sptr ? sptr : "", sptr ? "\n" : ""); } /*****************************************************************************/ /* Print in hexadecimal and readable the bytes provided. */ void DumpBytes ( char *cptr, int len ) { int cnt; char *sptr; /*********/ /* begin */ /*********/ if (dbug) fprintf (stdout, "DumpBytes() %d %d\n", cptr, len); sptr = cptr; for (cnt = 0; cnt < len; cnt++) { if (cnt && !(cnt % 32)) { fputc (' ', stdout); while (sptr < cptr) { if (isprint (*sptr)) fputc (*sptr, stdout); else fputc ('.', stdout); sptr++; } sptr = cptr; fputc ('\n', stdout); } else if (cnt && !(cnt % 8)) fputc (' ', stdout); fprintf (stdout, "%02.02x", *(unsigned char*)cptr++); } if (sptr < cptr) fputc (' ', stdout); while (sptr < cptr) { if (isprint (*sptr)) fputc (*sptr, stdout); else fputc ('.', stdout); sptr++; } fputc ('\n', stdout); } /*****************************************************************************/ /* Return the value of a CGI variable regardless of whether it is used in a standard CGI environment or a WASD CGIplus (RTE) environment. Also automatically switches WASD V7.2 and later servers into 'struct' mode for significantly improved performance. WASD by default supplies CGI variables prefixed by "WWW_" to differentiate them from any other DCL symbols (or "env"ironment logicals). This function strips that "WWW_" if present. */ char* CgiVar (char *VarName) { # ifndef CGIVAR_STRUCT_SIZE # define CGIVAR_STRUCT_SIZE 8192 # endif # define SOUS sizeof(unsigned short) static int CalloutDone, StructLength; static char *NextVarNamePtr; static char StructBuffer [CGIVAR_STRUCT_SIZE]; static FILE *CgiPlusIn; int status, Length, VarNameWildcard; char *bptr, *cptr, *sptr; /*********/ /* begin */ /*********/ if (CgiVarDebug) fprintf (stdout, "CgiVar() |%s|\n", !VarName ? "NULL" : VarName); if (!VarName || !VarName[0]) { /* initialize */ StructLength = 0; NextVarNamePtr = StructBuffer; if (!VarName) return (NULL); } if (VarName[0]) { /***************************/ /* return a variable value */ /***************************/ if (!IsCgiPlus) { /* standard CGI environment */ static int CheckWWW, PrefixWWW; static char NameValue [256+1024]; static $DESCRIPTOR (NameDsc, ""); static $DESCRIPTOR (ValueDsc, NameValue); static $DESCRIPTOR (WwwGatewayInterfaceDsc, "WWW_GATEWAY_INTERFACE"); unsigned short ShortLength; if (!CheckWWW) { CheckWWW = 1; status = lib$get_symbol (&WwwGatewayInterfaceDsc, &ValueDsc, &ShortLength, NULL); if (status & 1) PrefixWWW = 1; if (CgiVarDebug) fprintf (stdout, "PrefixWWW: %d\n", PrefixWWW); } if (VarName[0] == '*') VarNameWildcard = 1; else VarNameWildcard = 0; for (;;) { if (VarNameWildcard) if (!(VarName = CgiVarDclSymbolName ("*"))) return (NULL); /* by default WASD CGI variable names are prefixed by "WWW_" */ if (PrefixWWW) { memcpy (NameValue, "WWW_", 4); strncpy (NameValue+4, VarName, sizeof(NameValue)-5); } else strncpy (NameValue, VarName, sizeof(NameValue)-1); NameDsc.dsc$a_pointer = NameValue; NameDsc.dsc$w_length = strlen(NameValue); NameValue[NameDsc.dsc$w_length] = '='; ValueDsc.dsc$a_pointer = NameValue + NameDsc.dsc$w_length + 1; ValueDsc.dsc$w_length = sizeof(NameValue) - NameDsc.dsc$w_length - 1; status = lib$get_symbol (&NameDsc, &ValueDsc, &ShortLength, NULL); if (CgiVarDebug) fprintf (stdout, "lib$get_symbol() %%X%08.08X\n", status); if (status & 1) ValueDsc.dsc$a_pointer[ShortLength] = '\0'; else { if (VarNameWildcard) continue; return (NULL); } if (CgiVarDebug) fprintf (stdout, "CGI |%s|\n", NameValue); if (!VarNameWildcard) return (ValueDsc.dsc$a_pointer); if (PrefixWWW) return (NameValue + 4); return (NameValue); } } /* hmmm, CGIplus not initialized */ if (IsCgiPlus && !StructLength) return (NULL); if (VarName[0] == '*') { /* return each CGIplus variable in successive calls */ if (!(Length = *(unsigned short*)NextVarNamePtr)) { NextVarNamePtr = StructBuffer; if (CgiVarDebug) fprintf (stdout, "CGIplus |NULL|\n"); return (NULL); } sptr = (NextVarNamePtr += SOUS); NextVarNamePtr += Length; if (CgiVarDebug) fprintf (stdout, "CGIplus |%s|\n", sptr); /* by default WASD CGI variable name are prefixed by "WWW_", ignore */ return (sptr + 4); } /* return a pointer to this CGIplus variable's value */ for (bptr = StructBuffer; Length = *(unsigned short*)bptr; bptr += Length) { /* by default WASD CGI variable name are prefixed by "WWW_", ignore */ sptr = (bptr += SOUS) + 4; for (cptr = VarName; *cptr && *sptr && *sptr != '='; cptr++, sptr++) if (toupper(*cptr) != toupper(*sptr)) break; /* if found return a pointer to the value */ if (!*cptr && *sptr == '=') { if (CgiVarDebug) fprintf (stdout, "CGIplus |%s|\n", sptr+1); cptr = malloc (strlen(sptr)); strcpy (cptr, sptr+1); return (cptr); } } /* not found */ if (CgiVarDebug) fprintf (stdout, "CGIplus |NULL|\n"); return (NULL); } /*****************************/ /* get the CGIplus variables */ /*****************************/ /* cannot "sync" in a non-CGIplus environment */ if (!VarName[0] && !IsCgiPlus) return (NULL); /* the CGIPLUSIN stream can be left open */ if (!CgiPlusIn) if (!(CgiPlusIn = fopen (getenv("CGIPLUSIN"), "r"))) exit (vaxc$errno); /* get the starting record (the essentially discardable one) */ for (;;) { cptr = fgets (StructBuffer, sizeof(StructBuffer), CgiPlusIn); if (!cptr) exit (vaxc$errno); /* if the starting sentinal is detected then break */ if (*(unsigned short*)cptr == '!\0' || *(unsigned short*)cptr == '!\n' || (*(unsigned short*)cptr == '!!' && isdigit(*(cptr+2)))) break; } /* MUST be done after reading the synchronizing starting record */ if (dbug) fprintf (stdout, "Content-Type: text/plain\n\n"); /* detect the CGIplus "force" record-mode environment variable (once) */ if (*(unsigned short*)cptr == '!!') { /********************/ /* CGIplus 'struct' */ /********************/ /* get the size of the binary structure */ StructLength = atoi(cptr+2); if (StructLength <= 0 || StructLength > sizeof(StructBuffer)) exit (SS$_BUGCHECK); if (!fread (StructBuffer, 1, StructLength, CgiPlusIn)) exit (vaxc$errno); } else { /*********************/ /* CGIplus 'records' */ /*********************/ /* reconstructs the original 'struct'ure from the records */ sptr = (bptr = StructBuffer) + sizeof(StructBuffer); while (fgets (bptr+SOUS, sptr-(bptr+SOUS), CgiPlusIn)) { /* first empty record (line) terminates variables */ if (bptr[SOUS] == '\n') break; /* note the location of the length word */ cptr = bptr; for (bptr += SOUS; *bptr && *bptr != '\n'; bptr++); if (*bptr != '\n') exit (SS$_BUGCHECK); *bptr++ = '\0'; if (bptr >= sptr) exit (SS$_BUGCHECK); /* update the length word */ *(unsigned short*)cptr = bptr - (cptr + SOUS); } if (bptr >= sptr) exit (SS$_BUGCHECK); /* terminate with a zero-length entry */ *(unsigned short*)bptr = 0; StructLength = (bptr + SOUS) - StructBuffer; } if (CgiVarDebug) { fprintf (stdout, "%d\n", StructLength); for (bptr = StructBuffer; Length = *(unsigned short*)bptr; bptr += Length) fprintf (stdout, "|%s|\n", bptr += SOUS); } if (!CalloutDone) { /* provide the CGI callout to set CGIplus into 'struct' mode */ fflush (stdout); fputs (CgiPlusEscPtr, stdout); fflush (stdout); /* the leading '!' indicates we're not going to read the response */ fputs ("!CGIPLUS: struct", stdout); fflush (stdout); fputs (CgiPlusEotPtr, stdout); fflush (stdout); /* don't need to do this again (the '!!' tells us what mode) */ CalloutDone = 1; } return (NULL); # undef SOUS } /*****************************************************************************/ /* Standard CGI environment. Clunky, but what else can we do with DCL symbols? */ char* CgiVarDclSymbolName (char *VarName) { static char *CgiVarSymbolNames [] = { /* standard CGI variable names */ "AUTH_ACCESS", "AUTH_AGENT", "AUTH_GROUP", "AUTH_PASSWORD", "AUTH_REALM", "AUTH_REALM_DESCRIPTION", "AUTH_REMOTE_USER", "AUTH_TYPE", "AUTH_USER", "CONTENT_LENGTH", "CONTENT_TYPE", "DOCUMENT_ROOT", /** "GATEWAY_BG", "GATEWAY_EOF", "GATEWAY_EOT", "GATEWAY_ESC", **/ "GATEWAY_INTERFACE", "GATEWAY_MRS", "HTML_BODYTAG", "HTML_FOOTER", "HTML_FOOTERTAG", "HTML_HEADER", "HTML_HEADERTAG", "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHORIZATION", "HTTP_CONNECTION", "HTTP_CACHE_CONTROL", "HTTP_CONTENT_LENGTH", "HTTP_CONTENT_ENCODING", "HTTP_CONTENT_TYPE", "HTTP_COOKIE", "HTTP_FORWARDED", "HTTP_HOST", "HTTP_IF_MATCH", "HTTP_IF_NOT_MODIFIED", "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE", "HTTP_IF_UNMODIFIED_SINCE", "HTTP_IF_RANGE", "HTTP_KEEP_ALIVE", "HTTP_RANGE", "HTTP_TRAILER", "HTTP_TRANSFER_ENCODING", "HTTP_PRAGMA", "HTTP_REFERER", "HTTP_USER_AGENT", "HTTP_X_FORWARDED_FOR", "PATH_INFO", "PATH_ODS", "PATH_TRANSLATED", "QUERY_STRING", "REMOTE_ADDR", "REMOTE_HOST", "REMOTE_PORT", "REMOTE_USER", "REQUEST_CHARSET", "REQUEST_CONTENT_TYPE", "REQUEST_METHOD", "REQUEST_SCHEME", "REQUEST_TIME_GMT", "REQUEST_TIME_LOCAL", "REQUEST_URI", "SCRIPT_DEFAULT", "SCRIPT_FILENAME", "SCRIPT_NAME", "SCRIPT_RTE", "SERVER_ADDR", "SERVER_ADMIN", "SERVER_CHARSET", "SERVER_GMT", "SERVER_NAME", "SERVER_PROTOCOL", "SERVER_PORT", "SERVER_SOFTWARE", "SERVER_SIGNATURE", "UNQIUE_ID", /* mod_ssl names */ "#mod_ssl", "HTTPS", "SSL_PROTOCOL", "SSL_SESSION_ID", "SSL_CIPHER", "SSL_CIPHER_EXPORT", "SSL_CIPHER_USEKEYSIZE", "SSL_CIPHER_ALGKEYSIZE", "SSL_CLIENT_M_VERSION", "SSL_CLIENT_M_SERIAL", "SSL_CLIENT_S_DN", "SSL_CLIENT_S_DN_x509", "SSL_CLIENT_I_DN", "SSL_CLIENT_I_DN_x509", "SSL_CLIENT_V_START", "SSL_CLIENT_V_END", "SSL_CLIENT_A_SIG", "SSL_CLIENT_A_KEY", "SSL_CLIENT_CERT", "SSL_SERVER_M_VERSION", "SSL_SERVER_M_SERIAL", "SSL_SERVER_S_DN", "SSL_SERVER_S_DN_x509", "SSL_SERVER_I_DN", "SSL_SERVER_I_DN_x509", "SSL_SERVER_V_START", "SSL_SERVER_V_END", "SSL_SERVER_A_SIG", "SSL_SERVER_A_KEY", "SSL_SERVER_CERT", "SSL_VERSION_INTERFACE", "SSL_VERSION_LIBRARY", /* Purveyor SSL names */ "#purveyor", "SECURITY_STATUS", "SSL_CIPHER", "SSL_CIPHER_KEYSIZE", "SSL_CLIENT_CA", "SSL_CLIENT_DN", "SSL_SERVER_CA", "SSL_SERVER_DN", "SSL_VERSION", /* X509 names */ "#X509", "AUTH_X509_CIPHER", "AUTH_X509_FINGERPRINT", "AUTH_X509_ISSUER", "AUTH_X509_KEYSIZE", "AUTH_X509_SUBJECT", /* end of list */ NULL }; static int idx; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (CgiVarDebug) fprintf (stdout, "CgiVarDclSymbolName() %d |%s|\n", idx, !VarName ? "NULL" : VarName); if (!VarName) { idx = 0; return (NULL); } for (;;) { cptr = CgiVarSymbolNames[idx++]; if (CgiVarDebug) fprintf (stdout, "|%s|\n", !cptr ? "NULL" : cptr); if (!cptr) break; if (*cptr != '#') return (cptr); for (;;) { if (*(unsigned long*)cptr == '#mod') { /* Apache mod_ssl-like SSL CGI variables */ idx++; if (CgiVar ("SSL_VERSION_INTERFACE")) break; } if (*(unsigned long*)cptr == '#pur') { /* Purveyor-like SSL CGI variables */ idx++; if (CgiVar ("SECURITY_STATUS")) break; } if (*(unsigned long*)cptr == '#X50') { /* X.509 client certificate authentication CGI variables */ idx++; if (CgiVar ("AUTH_X509_CIPHER")) break; } while (cptr = CgiVarSymbolNames[idx]) { if (*cptr == '#') break; idx++; } if (!cptr) break; if (CgiVarDebug) fprintf (stdout, "|%s|\n", cptr); } if (!cptr) break; } idx = 0; return (NULL); } /*****************************************************************************/