#ifndef lint
static const char rcsid[] = "$Id: cgi-fcgi.c,v 1.15 2001/09/01 01:14:28 robs Exp $";
#endif
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fcgi_config.h"
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef _WIN32
#include <stdlib.h>
#include <io.h>
#else
extern char **environ;
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "fcgimisc.h"
#include "fcgiapp.h"
#include "fastcgi.h"
#include "fcgios.h"
static int wsReadPending = 0;
static int fcgiReadPending = 0;
static int fcgiWritePending = 0;
static void ScheduleIo(void);
#define BUFFLEN 8192
typedef struct {
char *next;
char *stop;
char buff[BUFFLEN];
} Buffer;
static int GetPtr(char **ptr, int n, Buffer *pBuf)
{
int result;
*ptr = pBuf->next;
result = min(n, pBuf->stop - pBuf->next);
pBuf->next += result;
return result;
}
static FCGI_Header MakeHeader(
int type,
int requestId,
int contentLength,
int paddingLength)
{
FCGI_Header header;
ASSERT(contentLength >= 0 && contentLength <= FCGI_MAX_LENGTH);
ASSERT(paddingLength >= 0 && paddingLength <= 0xff);
header.version = FCGI_VERSION_1;
header.type = (unsigned char) type;
header.requestIdB1 = (unsigned char) ((requestId >> 8) & 0xff);
header.requestIdB0 = (unsigned char) ((requestId ) & 0xff);
header.contentLengthB1 = (unsigned char) ((contentLength >> 8) & 0xff);
header.contentLengthB0 = (unsigned char) ((contentLength ) & 0xff);
header.paddingLength = (unsigned char) paddingLength;
header.reserved = 0;
return header;
}
static FCGI_BeginRequestBody MakeBeginRequestBody(
int role,
int keepConnection)
{
FCGI_BeginRequestBody body;
ASSERT((role >> 16) == 0);
body.roleB1 = (unsigned char) ((role >> 8) & 0xff);
body.roleB0 = (unsigned char) (role & 0xff);
body.flags = (unsigned char) ((keepConnection) ? FCGI_KEEP_CONN : 0);
memset(body.reserved, 0, sizeof(body.reserved));
return body;
}
static int bytesToRead;
static int appServerSock = -1;
static Buffer fromAS;
static FCGI_Header header;
static int headerLen = 0;
static int contentLen;
static int paddingLen;
static int requestId;
static FCGI_EndRequestBody erBody;
static int readingEndRequestBody = FALSE;
static int exitStatus = 0;
static int exitStatusSet = FALSE;
static int stdinFds[3];
static void FCGIexit(int exitCode)
{
if(appServerSock != -1) {
OS_Close(appServerSock);
appServerSock = -1;
}
OS_LibShutdown();
exit(exitCode);
}
#undef exit
#define exit FCGIexit
static void AppServerReadHandler(ClientData dc, int bytesRead)
{
int count, outFD;
char *ptr;
dc = NULL;
assert(fcgiReadPending == TRUE);
fcgiReadPending = FALSE;
count = bytesRead;
if(count <= 0) {
if(count < 0) {
exit(OS_Errno);
}
if(headerLen > 0 || paddingLen > 0) {
exit(FCGX_PROTOCOL_ERROR);
}
if(appServerSock != -1) {
OS_Close(appServerSock);
appServerSock = -1;
}
exit((exitStatusSet) ? exitStatus : FCGX_PROTOCOL_ERROR);
}
fromAS.stop = fromAS.next + count;
while(fromAS.next != fromAS.stop) {
if(headerLen < sizeof(header)) {
count = GetPtr(&ptr, sizeof(header) - headerLen, &fromAS);
assert(count > 0);
memcpy(&header + headerLen, ptr, count);
headerLen += count;
if(headerLen < sizeof(header)) {
break;
}
if(header.version != FCGI_VERSION_1) {
exit(FCGX_UNSUPPORTED_VERSION);
}
if((header.requestIdB1 << 8) + header.requestIdB0 != requestId) {
exit(FCGX_PROTOCOL_ERROR);
}
contentLen = (header.contentLengthB1 << 8)
+ header.contentLengthB0;
paddingLen = header.paddingLength;
} else {
switch(header.type) {
case FCGI_STDOUT:
case FCGI_STDERR:
count = GetPtr(&ptr, contentLen, &fromAS);
contentLen -= count;
if(count > 0) {
outFD = (header.type == FCGI_STDOUT) ?
STDOUT_FILENO : STDERR_FILENO;
if(OS_Write(outFD, ptr, count) < 0) {
exit(OS_Errno);
}
}
break;
case FCGI_END_REQUEST:
if(!readingEndRequestBody) {
if(contentLen != sizeof(erBody)) {
exit(FCGX_PROTOCOL_ERROR);
}
readingEndRequestBody = TRUE;
}
count = GetPtr(&ptr, contentLen, &fromAS);
if(count > 0) {
memcpy(&erBody + sizeof(erBody) - contentLen,
ptr, count);
contentLen -= count;
}
if(contentLen == 0) {
if(erBody.protocolStatus != FCGI_REQUEST_COMPLETE) {
exit(FCGX_PROTOCOL_ERROR);
}
exitStatus = (erBody.appStatusB3 << 24)
+ (erBody.appStatusB2 << 16)
+ (erBody.appStatusB1 << 8)
+ (erBody.appStatusB0 );
exitStatusSet = TRUE;
readingEndRequestBody = FALSE;
}
break;
case FCGI_GET_VALUES_RESULT:
case FCGI_UNKNOWN_TYPE:
default:
exit(FCGX_PROTOCOL_ERROR);
}
if(contentLen == 0) {
if(paddingLen > 0) {
paddingLen -= GetPtr(&ptr, paddingLen, &fromAS);
}
if(paddingLen == 0) {
headerLen = 0;
}
}
}
}
ScheduleIo();
}
static Buffer fromWS;
static int webServerReadHandlerEOF;
static void WriteStdinEof(void)
{
static int stdin_eof_sent = 0;
if (stdin_eof_sent)
return;
*((FCGI_Header *)fromWS.stop) = MakeHeader(FCGI_STDIN, requestId, 0, 0);
fromWS.stop += sizeof(FCGI_Header);
stdin_eof_sent = 1;
}
static void WebServerReadHandler(ClientData dc, int bytesRead)
{
dc = NULL;
assert(fromWS.next == fromWS.stop);
assert(fromWS.next == &fromWS.buff[0]);
assert(wsReadPending == TRUE);
wsReadPending = FALSE;
if(bytesRead < 0) {
exit(OS_Errno);
}
*((FCGI_Header *) &fromWS.buff[0])
= MakeHeader(FCGI_STDIN, requestId, bytesRead, 0);
bytesToRead -= bytesRead;
fromWS.stop = &fromWS.buff[sizeof(FCGI_Header) + bytesRead];
webServerReadHandlerEOF = (bytesRead == 0);
if (bytesToRead <= 0)
WriteStdinEof();
ScheduleIo();
}
static void AppServerWriteHandler(ClientData dc, int bytesWritten)
{
int length = fromWS.stop - fromWS.next;
dc = NULL;
assert(length > 0);
assert(fcgiWritePending == TRUE);
fcgiWritePending = FALSE;
if(bytesWritten < 0) {
exit(OS_Errno);
}
if((int)bytesWritten < length) {
fromWS.next += bytesWritten;
} else {
fromWS.stop = fromWS.next = &fromWS.buff[0];
}
ScheduleIo();
}
static void ScheduleIo(void)
{
int length;
if(!fcgiWritePending && appServerSock != -1 &&
((length = fromWS.stop - fromWS.next) != 0)) {
if(OS_AsyncWrite(appServerSock, 0, fromWS.next, length,
AppServerWriteHandler,
(ClientData)appServerSock) == -1) {
FCGIexit(OS_Errno);
} else {
fcgiWritePending = TRUE;
}
}
if(!fcgiReadPending && appServerSock != -1) {
fromAS.next = &fromAS.buff[0];
if(OS_AsyncRead(appServerSock, 0, fromAS.next, BUFFLEN,
AppServerReadHandler,
(ClientData)appServerSock) == -1) {
FCGIexit(OS_Errno);
} else {
fcgiReadPending = TRUE;
}
}
if((bytesToRead > 0) && !webServerReadHandlerEOF && !wsReadPending &&
!fcgiWritePending &&
fromWS.next == &fromWS.buff[0]) {
if(OS_AsyncReadStdin(fromWS.next + sizeof(FCGI_Header),
BUFFLEN - sizeof(FCGI_Header),
WebServerReadHandler, STDIN_FILENO)== -1) {
FCGIexit(OS_Errno);
} else {
wsReadPending = TRUE;
}
}
}
static void FCGI_Start(char *bindPath, char *appPath, int nServers)
{
int listenFd, i;
if((listenFd = OS_CreateLocalIpcFd(bindPath, 5)) == -1) {
exit(OS_Errno);
}
if(access(appPath, X_OK) == -1) {
fprintf(stderr, "%s is not executable\n", appPath);
exit(1);
}
for(i = 0; i < nServers; i++) {
if(OS_SpawnChild(appPath, listenFd) == -1) {
exit(OS_Errno);
}
}
OS_Close(listenFd);
}
static void FCGIUtil_BuildNameValueHeader(
int nameLen,
int valueLen,
unsigned char *headerBuffPtr,
int *headerLenPtr) {
unsigned char *startHeaderBuffPtr = headerBuffPtr;
ASSERT(nameLen >= 0);
if (nameLen < 0x80) {
*headerBuffPtr++ = (unsigned char) nameLen;
} else {
*headerBuffPtr++ = (unsigned char) ((nameLen >> 24) | 0x80);
*headerBuffPtr++ = (unsigned char) (nameLen >> 16);
*headerBuffPtr++ = (unsigned char) (nameLen >> 8);
*headerBuffPtr++ = (unsigned char) nameLen;
}
ASSERT(valueLen >= 0);
if (valueLen < 0x80) {
*headerBuffPtr++ = (unsigned char) valueLen;
} else {
*headerBuffPtr++ = (unsigned char) ((valueLen >> 24) | 0x80);
*headerBuffPtr++ = (unsigned char) (valueLen >> 16);
*headerBuffPtr++ = (unsigned char) (valueLen >> 8);
*headerBuffPtr++ = (unsigned char) valueLen;
}
*headerLenPtr = headerBuffPtr - startHeaderBuffPtr;
}
#define MAXARGS 16
static int ParseArgs(int argc, char *argv[],
int *doBindPtr, int *doStartPtr,
char *connectPathPtr, char *appPathPtr, int *nServersPtr) {
int i,
x,
err = 0,
ac;
char *tp1,
*tp2,
*av[MAXARGS];
FILE *fp;
char line[BUFSIZ];
*doBindPtr = TRUE;
*doStartPtr = TRUE;
*connectPathPtr = '\0';
*appPathPtr = '\0';
*nServersPtr = 0;
for(i = 0; i < MAXARGS; i++)
av[i] = NULL;
for(i = 1; i < argc; i++) {
if(argv[i][0] == '-') {
if(!strcmp(argv[i], "-f")) {
if(++i == argc) {
fprintf(stderr,
"Missing command file name after -f\n");
return 1;
}
if((fp = fopen(argv[i], "r")) == NULL) {
fprintf(stderr, "Cannot open command file %s\n", argv[i]);
return 1;
}
ac = 1;
while(fgets(line, BUFSIZ, fp)) {
if(line[0] == '#') {
continue;
}
if((tp1 = (char *) strrchr(line,'\n')) != NULL) {
*tp1-- = 0;
while(*tp1 == ' ' || *tp1 =='\t') {
*tp1-- = 0;
}
} else {
fprintf(stderr, "Line to long\n");
return 1;
}
tp1 = line;
while(tp1) {
if((tp2 = strchr(tp1, ' ')) != NULL) {
*tp2++ = 0;
}
if(ac >= MAXARGS) {
fprintf(stderr,
"To many arguments, "
"%d is max from a file\n", MAXARGS);
exit(-1);
}
if((av[ac] = (char *)malloc(strlen(tp1)+1)) == NULL) {
fprintf(stderr, "Cannot allocate %d bytes\n",
strlen(tp1)+1);
exit(-1);
}
strcpy(av[ac++], tp1);
tp1 = tp2;
}
}
err = ParseArgs(ac, av, doBindPtr, doStartPtr,
connectPathPtr, appPathPtr, nServersPtr);
for(x = 1; x < ac; x++) {
ASSERT(av[x] != NULL);
free(av[x]);
}
return err;
#ifdef _WIN32
} else if (!strcmp(argv[i], "-jitcgi")) {
DebugBreak();
} else if (!strcmp(argv[i], "-dbgfcgi")) {
putenv("DEBUG_FCGI=TRUE");
#endif
} else if(!strcmp(argv[i], "-start")) {
*doBindPtr = FALSE;
} else if(!strcmp(argv[i], "-bind")) {
*doStartPtr = FALSE;
} else if(!strcmp(argv[i], "-connect")) {
if(++i == argc) {
fprintf(stderr,
"Missing connection name after -connect\n");
err++;
} else {
strcpy(connectPathPtr, argv[i]);
}
} else {
fprintf(stderr, "Unknown option %s\n", argv[i]);
err++;
}
} else if(*appPathPtr == '\0') {
strcpy(appPathPtr, argv[i]);
} else if(isdigit((int)argv[i][0]) && *nServersPtr == 0) {
*nServersPtr = atoi(argv[i]);
if(*nServersPtr <= 0) {
fprintf(stderr, "Number of servers must be greater than 0\n");
err++;
}
} else {
fprintf(stderr, "Unknown argument %s\n", argv[i]);
err++;
}
}
if(*doStartPtr && *appPathPtr == 0) {
fprintf(stderr, "Missing application pathname\n");
err++;
}
if(*connectPathPtr == 0) {
fprintf(stderr, "Missing -connect <connName>\n");
err++;
} else if(strchr(connectPathPtr, ':')) {
}
if(*nServersPtr == 0) {
*nServersPtr = 1;
}
return err;
}
int main(int argc, char **argv)
{
char **envp = environ;
int count;
FCGX_Stream *paramsStream;
int numFDs;
unsigned char headerBuff[8];
int headerLen, valueLen;
char *equalPtr;
FCGI_BeginRequestRecord beginRecord;
int doBind, doStart, nServers;
char appPath[MAXPATHLEN], bindPath[MAXPATHLEN];
if(ParseArgs(argc, argv, &doBind, &doStart,
(char *) &bindPath, (char *) &appPath, &nServers)) {
fprintf(stderr,
"Usage:\n"
" cgi-fcgi -f <cmdPath> , or\n"
" cgi-fcgi -connect <connName> <appPath> [<nServers>] , or\n"
" cgi-fcgi -start -connect <connName> <appPath> [<nServers>] , or\n"
" cgi-fcgi -bind -connect <connName> ,\n"
"where <connName> is either the pathname of a UNIX domain socket\n"
"or (if -bind is given) a hostName:portNumber specification\n"
"or (if -start is given) a :portNumber specification (uses local host).\n");
exit(1);
}
if(OS_LibInit(stdinFds)) {
fprintf(stderr, "Error initializing OS library: %d\n", OS_Errno);
exit(0);
}
equalPtr = getenv("CONTENT_LENGTH");
if(equalPtr != NULL) {
bytesToRead = atoi(equalPtr);
} else {
bytesToRead = 0;
}
if(doBind) {
appServerSock = OS_FcgiConnect(bindPath);
}
if(doStart && (!doBind || appServerSock < 0)) {
FCGI_Start(bindPath, appPath, nServers);
if(!doBind) {
exit(0);
} else {
appServerSock = OS_FcgiConnect(bindPath);
}
}
if(appServerSock < 0) {
fprintf(stderr, "Could not connect to %s\n", bindPath);
exit(OS_Errno);
}
requestId = 1;
beginRecord.header = MakeHeader(FCGI_BEGIN_REQUEST, requestId,
sizeof(beginRecord.body), 0);
beginRecord.body = MakeBeginRequestBody(FCGI_RESPONDER, FALSE);
count = OS_Write(appServerSock, (char *)&beginRecord, sizeof(beginRecord));
if(count != sizeof(beginRecord)) {
exit(OS_Errno);
}
paramsStream = FCGX_CreateWriter(appServerSock, requestId, 8192, FCGI_PARAMS);
for( ; *envp != NULL; envp++) {
equalPtr = strchr(*envp, '=');
if(equalPtr == NULL) {
exit(1000);
}
valueLen = strlen(equalPtr + 1);
FCGIUtil_BuildNameValueHeader(
equalPtr - *envp,
valueLen,
&headerBuff[0],
&headerLen);
if(FCGX_PutStr((char *) &headerBuff[0], headerLen, paramsStream) < 0
|| FCGX_PutStr(*envp, equalPtr - *envp, paramsStream) < 0
|| FCGX_PutStr(equalPtr + 1, valueLen, paramsStream) < 0) {
exit(FCGX_GetError(paramsStream));
}
}
FCGX_FClose(paramsStream);
FCGX_FreeStream(¶msStream);
fromWS.stop = fromWS.next = &fromWS.buff[0];
webServerReadHandlerEOF = FALSE;
numFDs = max(appServerSock, STDIN_FILENO) + 1;
OS_SetFlags(appServerSock, O_NONBLOCK);
if (bytesToRead <= 0)
WriteStdinEof();
ScheduleIo();
while(!exitStatusSet) {
OS_DoIo(NULL);
}
if(exitStatusSet) {
FCGIexit(exitStatus);
} else {
FCGIexit(999);
}
return 0;
}