/*
main.c
This file is part of pserv
http://pserv.sourceforge.net
Copyright (c) 2001-2002-2003-2004-2005 Riccardo Mottola. All rights reserved.
mail: rmottola@users.sourceforge.net
This file is free software, distributed under GPL. Please read acclosed license
*/
#include "main.h"
#include "log.h"
#include "mime.h"
#include "handlers.h"
/* ------ Global Variables ---- */
int theSocket, newSocket;
int error;
int newSocketReady;
char homePath[MAX_PATH_LEN+1];
char defaultFileName[MAX_PATH_LEN+1];
char logFileName[MAX_PATH_LEN+1];
char mimeTypesFileName[MAX_PATH_LEN+1];
char phpFileName[MAX_PATH_LEN+1];
char cgiRoot[MAX_PATH_LEN+1]; /* root for CGI scripts exec */
struct timeval sockTimeVal;
mimeData *mimeArray; /* here we will hold all MIME data, inited once, never to be changed */
int mimeEntries; /* the number of known mimetypes present in *mimeArray and loaded at startup */
FILE *lf; /* log file */
/* ------ Signal Handlers ------- */
RETSIGTYPE intCatch ()
{
signal_MACRO(SIGINT, intCatch);
printf("shutting down the server....\n");
if (newSocketReady)
{
if (close(newSocket))
printf("error closing new socket\n");
}
if (close(theSocket))
printf("error closing mother socket\n");
logFileClose();
exit(0);
}
RETSIGTYPE killCatch ()
{
signal_MACRO(SIGINT, killCatch);
printf("shutting down the server....\n");
if (newSocketReady)
{
if (close(newSocket))
printf("error closing ns socket\n");
}
if (close(theSocket))
printf("error closing mother socket\n");
logFileClose();
exit(0);
}
RETSIGTYPE brokenPipe()
{
signal_MACRO(SIGPIPE, brokenPipe);
printf("BrokenPipe!\n");
if (newSocketReady)
{
printf ("new socket was ready....\n");
if (!close(newSocket))
printf("closed ns\n");
} else {
printf("This makes no sense!!! broken pipe on a closed socket!\n");
exit(0);
}
newSocketReady = NO;
}
/* ------- General Purpose Functions -------*/
int sayError(sock, err, errorFilePath, req)
/* output HTML code for given error code */
int sock;
int err;
char *errorFilePath;
struct request req;
{
char outBuff[512+MAX_PATH_LEN]; /* should be enough since we hard-code output and we must contain the path! */
switch (err) {
case INPUT_LINE_TOO_LONG:
generateMimeHeader(sock, 400, "text/html", NULL, req.protocolVersion, FULL_HEADER);
strcpy(outBuff, "\n
\nError\n\n\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "Error: Browser request line too long.\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "\n\n");
write (sock, outBuff, strlen(outBuff));
logWriter(LOG_INPUT_LINE_TOO_LONG, NULL, 0, NULL, 0);
break;
case POST_BUFFER_OVERFLOW:
generateMimeHeader(sock, 500, "text/html", NULL, req.protocolVersion, FULL_HEADER);
strcpy(outBuff, "\n\nError\n\n\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "Error: Internal Server Error (Content-length in post data too long)\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "\n\n");
write (sock, outBuff, strlen(outBuff));
logWriter(LOG_POST_BUFFER_OVERFLOW, NULL, 0, NULL, 0);
break;
case FORBIDDEN:
generateMimeHeader(sock, 403, "text/html", NULL, req.protocolVersion, FULL_HEADER);
strcpy(outBuff, "\n\nError\n\n\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "Error 403: Forbidden.
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, errorFilePath);
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "\n\n");
write (sock, outBuff, strlen(outBuff));
logWriter(LOG_FORBIDDEN, errorFilePath, 0, req, 0);
break;
case NOT_FOUND:
generateMimeHeader(sock, 404, "text/html", NULL, req.protocolVersion, FULL_HEADER);
strcpy(outBuff, "\n\nError\n\n\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "Error 404: File not found.
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, errorFilePath);
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "\n\n");
write (sock, outBuff, strlen(outBuff));
logWriter(LOG_FILE_NOT_FOUND, errorFilePath, 0, req, 0);
break;
case LENGTH_REQUIRED:
generateMimeHeader(sock, 411, "text/html", NULL, req.protocolVersion, FULL_HEADER);
strcpy(outBuff, "\n\nError\n\n\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "Error 411: Length Required
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, errorFilePath);
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "
\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "\n\n");
write (sock, outBuff, strlen(outBuff));
logWriter(LOG_LENGTH_REQUIRED, 0, req, 0);
break;
case UNHANDLED_METHOD:
#ifdef PRINTF_DEBUG
printf("unhandled method case\n");
#endif
generateMimeHeader(sock, 501, "text/html", NULL, req.protocolVersion, FULL_HEADER);
strcpy(outBuff, "\n\nError\n\n\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "Error: Not Implemented: Unhandled Method.\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "\n\n");
write (sock, outBuff, strlen(outBuff));
break;
default:
printf("generic SayError case\n");
generateMimeHeader(sock, 500, "text/html", NULL, req.protocolVersion, FULL_HEADER);
strcpy(outBuff, "\n\nError\n\n\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "Error: Unknown error trapped.\n");
write (sock, outBuff, strlen(outBuff));
strcpy(outBuff, "\n\n");
write (sock, outBuff, strlen(outBuff));
logWriter(LOG_GENERIC_ERROR, NULL, 0, NULL, 0);
break;
} /* of switch */
return 0;
}
/* substitute escaped % characters with their equivalent */
int convertPercents(s, l)
char s[];
int l;
{
char tok[4];
char tokSubst;
size_t tokLen;
char *tokPtr;
size_t tokPos;
/* we match and subsitute the %20 with a blank space */
strcpy(tok, "%20");
tokLen = strlen(tok);
tokSubst = ' ';
tokPtr = strstr(s, tok);
while (tokPtr)
{
tokPos = tokPtr - s; /* calculate the position */
s[tokPos] = tokSubst;
/* now we shift all characters left including the terminator */
/* assumes tokSubst len of 1 (= one char) */
memmove(tokPtr + 1, tokPtr + tokLen, l - tokPos - tokLen + 1);
tokLen = strlen(tok);
tokPtr = strstr(s, tok);
}
return 0;
}
int analyzeRequest (sock, req, reqStruct)
/* extracts meaningful tokens from the client request and stores them in the request struct */
int sock;
char req[];
struct request *reqStruct;
{
char token[BUFFER_SIZE + 1]; /* we add one to be able the trailing new line we append for security */
char reqArray[MAX_REQUEST_LINES][BUFFER_SIZE]; /* no need to add one here since we trim newline */
int i, j, k;
int reqSize;
int readLines;
int tokenEnd;
/* we copy the header lines to an array for easier parsing */
/* but first we make sure that our string has a newline and an end */
req[BUFFER_SIZE] = '\0';
reqSize = strlen(req);
req[reqSize] = '\n';
reqSize++;
req[reqSize] = '\0';
i = 0; j = 0;
while (i < MAX_REQUEST_LINES && j < reqSize)
{
k = 0;
while (req[j] != '\n')
token[k++] = req[j++];
token[k-1] = '\0'; /* the line read ends with an \n, we skip it and count it as read */
j++;
strcpy(reqArray[i], token);
i++;
}
readLines = i - 1;
#ifdef PRINTF_DEBUG
for (k = 0; k < readLines; k++)
printf("%d - |%s|\n", k, reqArray[k]);
#endif
/* first line: method, path and protocol version */
/* we copy to a temporary buffer to be more secure against overflows */
i = j = 0;
while (reqArray[0][i] != ' ' && reqArray[0][i] != '\0' && j < METHOD_LEN)
token[j++] = reqArray[0][i++];
token[j] = '\0'; /* to make sure we have a terminated string */
strcpy(reqStruct->method, token); /* copy back */
if (reqArray[0][i] == '\0')
tokenEnd = YES;
else
tokenEnd = NO;
i++;
/* we look for the document address */
j = 0;
reqStruct->documentAddress[0] = '\0';
if (!tokenEnd)
{
while (reqArray[0][i] != ' ' && reqArray[0][i] != '\0' && reqArray[0][i] != '?' && j < MAX_PATH_LEN)
token[j++] = reqArray[0][i++];
token[j] = '\0'; /* to make sure we have a terminated string */
/* now we need to convert some escapings from the path like %20 */
convertPercents(token, j);
strcpy(reqStruct->documentAddress, token); /* copy back */
if (reqArray[0][i] == '\0')
tokenEnd = YES;
else
tokenEnd = NO;
i++;
/* we need now to separate path from query string ("?" separated) */
if (reqArray[0][i-1] == '?')
{
k = 0;
token[0] = '\0';
while (reqArray[0][i] != ' ' && reqArray[0][i] != '?' && reqArray[0][i] != '\0' && k < MAX_QUERY_STRING_LEN)
token[k++] = reqArray[0][i++];
token[k] = '\0'; /* to make sure we have a terminated string */
strcpy(reqStruct->queryString, token); /* copy back */
i++;
}
}
/* we analyze the HTTP protocol version */
/* default is 0.9 since that version didn't report itself */
strcpy(reqStruct->protocolVersion, "HTTP/0.9");
j = 0;
if (!tokenEnd)
{
while (reqArray[0][i] != ' ' && reqArray[0][i] != '\0' && j < PROTOCOL_LEN)
token[j++] = reqArray[0][i++];
token[j] = '\0'; /* to make sure we have a terminated string */
if (j)
strcpy(reqStruct->protocolVersion, token); /* copy back */
}
/* Connection type */
reqStruct->keepAlive = NO;
if (!strncmp(reqArray[1], "Connection: Keep-Alive", strlen("Connection: Keep-Alive")))
reqStruct->keepAlive = YES;
else if (!strncmp(reqArray[1], "Connection: keep-alive", strlen("Connection: keep-alive")))
reqStruct->keepAlive = YES;
/* user-agent, content-length, content-type, cookie and else */
i = 1;
j = NO;
reqStruct->userAgent[0] = '\0';
reqStruct->contentLength = -1;
reqStruct->contentType[0] = '\0';
reqStruct->cookie[0] = '\0';
while (i < readLines)
{
if (!strncmp(reqArray[i], "User-Agent:", strlen("User-Agent:")))
{
strncpy(reqStruct->userAgent, &reqArray[i][strlen("User-Agent: ")], USER_AGENT_LEN - 1);
reqStruct->userAgent[USER_AGENT_LEN] = '\0';
}
else if (!strncmp(reqArray[i], "Content-Length:", strlen("Content-length:")) || !strncmp(reqArray[i], "Content-length:", strlen("Content-length:")))
{
strcpy(token, &reqArray[i][strlen("Content-length: ")]);
sscanf(token, "%ld", &(reqStruct->contentLength));
#ifdef PRINTF_DEBUG
printf("content length %ld\n", reqStruct->contentLength);
#endif
}
else if (!strncmp(reqArray[i], "Content-Type:", strlen("Content-type:")) || !strncmp(reqArray[i], "Content-type:", strlen("Content-type:")))
{
strncpy(reqStruct->contentType, &reqArray[i][strlen("Content-type: ")], CONTENT_TYPE_LEN - 1);
#ifdef PRINTF_DEBUG
printf("content type %s\n", reqStruct->contentType);
#endif
}
else if (!strncmp(reqArray[i], "Cookie:", strlen("Cookie:")))
{
strncpy(reqStruct->cookie, &reqArray[i][strlen("Cookie: ")], MAX_COOKIE_LEN - 1);
#ifdef PRINTF_DEBUG
printf("cookie %s\n", reqStruct->cookie);
#endif
}
i++;
}
/* if we didn't find a User-Agent we fill in a (N)ot(R)ecognized */
if (reqStruct->userAgent[0] == '\0')
strcpy(reqStruct->userAgent, "NR");
return 0;
}
int handleMethod (port, sock, req)
/* analyze method of Request and take necessary actions */
int port;
int sock;
struct request req;
{
char completeFilePath[MAX_PATH_LEN+MAX_PATH_LEN+MAX_INDEX_NAME_LEN+1]; /* to be sure root+path+indexname stay inside */
char mimeType[MAX_MIMETYPE_LEN+1];
struct stat fileStats;
/* now we check if the given path tries to get out of the root
* POSIX file name have / as a separator, so // is still a separator */
{
register int i,j;
register int sL;
char dirName[MAX_PATH_LEN+1];
register int depthCount;
depthCount = 0;
sL = strlen(req.documentAddress);
if (sL > 3)
{
if (req.documentAddress[1] == '.' && req.documentAddress[2] == '.' && req.documentAddress[3] == '/')
{
sayError(sock, FORBIDDEN, req.documentAddress, req);
return -1;
}
}
dirName[0] = '\0';
j = 0;
for (i = 1; i < sL; i++)
{
if (req.documentAddress[i] == '/')
{
dirName[j] = '\0';
if (strcmp(dirName, ".."))
{
/* count only the first of multiple / spearators */
if (req.documentAddress[i-1] != '/')
depthCount++;
}
else
depthCount--;
j = 0;
} else
dirName[j++] = req.documentAddress[i];
}
if (depthCount < 0)
{
sayError(sock, FORBIDDEN, req.documentAddress, req);
return -1;
}
}
if (!strcmp(req.method, "GET"))
{
#ifdef PRINTF_DEBUG
printf ("handling get of %s\n", req.documentAddress);
#endif
#ifdef ENABLE_CGI
/* first we check if the path contains the directory selected for cgi's and in case handle it */
if (!strncmp(req.documentAddress, CGI_MATCH_STRING, strlen(CGI_MATCH_STRING)))
{
cgiHandler(port, sock, req, NULL);
} else
#endif /* ENABLE_CGI */
{ /* GET for standard files */
#ifdef ENABLE_CGI
/* we check that the path doesn't contain the cgi match string
* we don't want to serve scripts as files */
if (strstr(req.documentAddress, CGI_MATCH_STRING))
{
sayError(sock, FORBIDDEN, req.documentAddress, req);
return -1;
}
#endif /* ENABLE_CGI */
strcpy(completeFilePath, homePath);
strcat(completeFilePath, req.documentAddress);
/* now we check if the given file is a directory or a plain file */
stat(completeFilePath, & fileStats);
if ((fileStats.st_mode & S_IFDIR) == S_IFDIR)
{
/* if does not end with a slash, we get an error */
if(completeFilePath[strlen(completeFilePath)-1] != '/')
{
sayError(sock, NOT_FOUND, req.documentAddress, req);
return -1;
}
#ifdef AUTO_INDEX
if (generateIndex(sock, completeFilePath, mimeType, req))
{
/* we got an error, generateIndex was not able to handle the request itself
* this means that there already exists and index
* we append the default file name */
strcat(completeFilePath, defaultFileName);
analyzeExtension(mimeType, completeFilePath);
#ifdef PHP
if (strncmp(mimeType, "application/x-httpd-php", 23))
#endif
dumpFile(sock, completeFilePath, mimeType, req);
#ifdef PHP
else
phpHandler(port, sock, phpFileName, completeFilePath, req, NULL);
#endif
}
#else
/* we append the default file name */
strcat(completeFilePath, defaultFileName);
analyzeExtension(mimeType, completeFilePath);
#ifdef PHP
if (strncmp(mimeType, "application/x-httpd-php", 23))
#endif
dumpFile(sock, completeFilePath, mimeType, req);
#ifdef PHP
else
phpHandler(port, sock, phpFileName, completeFilePath, req, NULL);
#endif
#endif
} else
{ /* it is a plain file */
analyzeExtension(mimeType, completeFilePath);
#ifdef PHP
if (strncmp(mimeType, "application/x-httpd-php", 23))
#endif
dumpFile(sock, completeFilePath, mimeType, req);
#ifdef PHP
else
phpHandler(port, sock, phpFileName, completeFilePath, req, NULL);
#endif
}
}
} else if (!strcmp(req.method, "HEAD"))
{
#ifdef PRINTF_DEBUG
printf ("handling head of %s\n", req.documentAddress);
#endif
/* first we check if the path contains the directory selected for cgi's and in case handle it */
#ifdef ENABLE_CGI
if (!strncmp(req.documentAddress, CGI_MATCH_STRING, strlen(CGI_MATCH_STRING)))
{
cgiHandler(port, sock, req, NULL);
} else
#endif /* ENABLE_CGI */
{
strcpy(completeFilePath, homePath);
strcat(completeFilePath, req.documentAddress);
/* now we check if the given file is a directory or a plain file */
stat(completeFilePath, &fileStats);
if ((fileStats.st_mode & S_IFDIR) == S_IFDIR)
{
/* if does not end with a slash, we get an error */
if(completeFilePath[strlen(completeFilePath)-1] != '/')
{
sayError(sock, NOT_FOUND, req.documentAddress, req);
return -1;
}
/* we append the default file name */
strcat(completeFilePath, defaultFileName);
}
analyzeExtension(mimeType, completeFilePath);
#ifdef PHP
if (strncmp(mimeType, "application/x-httpd-php", 23))
#endif
dumpFile(sock, completeFilePath, mimeType, req);
#ifdef PHP
else
phpHandler(port, sock, phpFileName, completeFilePath, req, NULL);
#endif
}
} else if (!strcmp(req.method, "POST"))
{
/* we add 5 characters to be able to hold a \r\n\r\n\0 sequence at the end */
char buff[POST_BUFFER_SIZE+5];
char tempBuff[POST_BUFFER_SIZE+5];
int howMany;
int totalRead;
int stuckCounter; /* if we receive to many errors */
int timeOutCounter; /* to check how many empty reads we make */
int readFinished;
printf("Handling of POST method\n");
#ifdef ENABLE_CGI
#ifdef PRINTF_DEBUG
printf ("begin of post handling\n");
#endif
buff[0] = '\0';
readFinished = NO;
totalRead = 0;
stuckCounter = 0;
timeOutCounter = 0;
if (req.contentLength < 0)
{
sayError(sock, LENGTH_REQUIRED, "", req);
return -1;
} else if (req.contentLength >= BUFFER_SIZE)
{
sayError(sock, POST_BUFFER_OVERFLOW, "", req);
return -1;
}
while (!readFinished)
{
howMany = recv(newSocket, tempBuff, POST_BUFFER_SIZE, 0);
tempBuff[howMany] = '\0'; /* seems that some Unices need this */
#ifdef PRINTF_DEBUG
printf ("read: %d\n%s\n", howMany, tempBuff);
#endif
if (howMany < 0 && totalRead <= POST_BUFFER_SIZE)
{
if (errno == EAGAIN)
{
printf("resource not available on POST data read\n");
timeOutCounter++;
if (timeOutCounter > MAX_EMPTY_READS_COUNTER) /* we have no terminator at header's end */
{
readFinished = YES;
}
} else
{
printf("read error: %d\n", errno);
newSocketReady = NO;
readFinished = YES;
close(newSocket);
}
} else if (howMany == 0)
{
stuckCounter++;
if (stuckCounter >= MAX_STUCK_COUNTER)
{
printf("Loop in read catched! closing connection.\n");
if (newSocketReady)
{
#ifdef PRINTF_DEBUG
printf ("new socket was ready....\n");
#endif
if (!close(newSocket))
printf("closed ns\n");
}
newSocketReady = NO;
}
readFinished = YES;
} else
{
timeOutCounter = 0;
totalRead += howMany;
if (totalRead >= POST_BUFFER_SIZE)
{ /* checking for buffer overflow */
printf("Buffer overflow on POST read\n");
readFinished = YES;
} else
{ /* finally we know we read something and add it to the buffer */
strcat (buff, tempBuff);
}
if (tempBuff[howMany-1] == '\n')
readFinished = YES;
if (howMany == req.contentLength)
readFinished = YES;
}
}
#ifdef PRINTF_DEBUG
printf("total read %d\n", totalRead);
#endif
if (totalRead == 0)
{
printf("Request read error\n");
} else
{
if (buff[totalRead - 1] != '\n') /* we need a trailing \n or the script will wait forever */
{
buff[totalRead++] = '\n';
buff[totalRead] = '\0';
}
#ifdef PRINTF_DEBUG
printf("buff: |%s|\n", buff);
#endif
if (!strncmp(req.documentAddress, CGI_MATCH_STRING, strlen(CGI_MATCH_STRING)))
{
cgiHandler(port, sock, req, buff);
} else
{
#ifdef PHP
strcpy(completeFilePath, homePath);
strcat(completeFilePath, req.documentAddress);
/* now we check if the given path tries to get out of the root */
{
int i,j;
int sL;
char dirName[MAX_PATH_LEN+1];
int depthCount = 0;
sL = strlen(req.documentAddress);
dirName[0] = '\0';
if (sL > 3) {
dirName[0] = req.documentAddress[1];
dirName[1] = req.documentAddress[2];
dirName[2] = req.documentAddress[3];
dirName[3] ='\0';
if (!strcmp(dirName, "../"))
{
sayError(sock, FORBIDDEN, req.documentAddress, req);
return -1;
}
}
j = 0;
for (i = 1; i < sL; i++) {
if (req.documentAddress[i] == '/')
{
dirName[j] = '\0';
if (strcmp(dirName, ".."))
depthCount ++;
else
depthCount--;
j = 0;
} else
dirName[j++] = req.documentAddress[i];
}
if (depthCount < 0)
{
sayError(sock, FORBIDDEN, req.documentAddress, req);
return -1;
}
}
/* now we check if the given file is a directory or a plain file */
stat(completeFilePath, &fileStats);
if ((fileStats.st_mode & S_IFDIR) == S_IFDIR)
{
/* if does not end with a slash, we get an error */
if(completeFilePath[strlen(completeFilePath)-1] != '/')
{
sayError(sock, NOT_FOUND, req.documentAddress, req);
return -1;
}
/* we append the default file name */
strcat(completeFilePath, defaultFileName);
}
analyzeExtension(mimeType, completeFilePath);
if (strncmp(mimeType, "application/x-httpd-php", 23))
{
#endif
/* non cgi POST is not supported */
sayError(sock, UNHANDLED_METHOD, "", req);
return -1;
#ifdef PHP
} else phpHandler(port, sock, phpFileName, completeFilePath, req, buff);
#endif
}
}
#endif /* ENABLE_CGI */
#ifndef ENABLE_CGI
/* we can't handle post with CGI since CGI is disabled... */
sayError(sock, UNHANDLED_METHOD, "", req);
return -1;
#endif /* not ENABLE_CGI */
/* end of POST */
} else
{
sayError(sock, UNHANDLED_METHOD, "", req);
return -1;
}
return 0;
}
int initParameters (serverPort, maxChildren, argc, argv)
/* initializes the operation parameters by reading them from the config file specified in the config file */
int *serverPort;
int *maxChildren;
int argc;
char *argv[];
{
char configFile[MAX_PATH_LEN+1];
char str1[BUFFER_SIZE+1];
char str2[BUFFER_SIZE+1];
FILE *f;
strcpy(configFile, DEFAULT_CONFIG_LOCATION);
strcat(configFile, CONFIG_FILE_NAME);
if (argc > 0)
printf("%s\n", *argv); /* we shall insert here command-line arguments processing */
f = fopen(configFile, "r");
if (f == NULL)
{
printf("Config file not found. Setting defaults.\n");
*serverPort = DEFAULT_PORT;
*maxChildren = DEFAULT_MAX_CHILDREN;
strcpy(homePath, DEFAULT_DOCS_LOCATION);
strcpy(defaultFileName, DEFAULT_FILE_NAME);
sockTimeVal.tv_sec = DEFAULT_SEC_TO;
sockTimeVal.tv_usec = DEFAULT_USEC_TO;
strcpy(logFileName, DEFAULT_LOG_FILE);
strcpy(mimeTypesFileName, DEFAULT_MIME_FILE);
strcpy(phpFileName, DEFAULT_PHP_FILE);
strcpy(cgiRoot, DEFAULT_CGI_ROOT);
initMimeTypes();
return -1;
}
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
*serverPort = 0;
if (str1 != NULL && str2 != NULL && !strcmp(str1, "port"))
sscanf(str2, "%d", serverPort);
if (*serverPort <= 0)
{
*serverPort = DEFAULT_PORT;
printf("Error reading port from file, setting default, %d\n", *serverPort);
}
printf("port: %d\n", *serverPort);
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
*maxChildren = 0;
if (str1 != NULL && str2 != NULL && !strcmp(str1, "maxChildren"))
sscanf(str2, "%d", maxChildren);
if (*maxChildren <= 0)
{
*maxChildren = DEFAULT_MAX_CHILDREN;
printf("Error reading maxChildren from file, setting default, %d\n", *maxChildren);
}
printf("maxChildren: %d", *maxChildren);
#ifndef FORKING_REQUEST
printf(" (but forking disabled at compile time)");
#endif
printf("\n");
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
if (str1 != NULL && str2 != NULL && !strcmp(str1, "documentsPath"))
{
sscanf(str2, "%s", homePath);
if (homePath == NULL)
{
strcpy(homePath, DEFAULT_DOCS_LOCATION);
printf("Error reading documentPath from file, setting default, %s\n", homePath);
}
} else
{
strcpy(homePath, DEFAULT_DOCS_LOCATION);
printf("Error reading documentPath from file, setting default, %s\n", homePath);
}
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
if (str1 != NULL && str2 != NULL && !strcmp(str1, "defaultFile"))
{
sscanf(str2, "%s", defaultFileName);
if (defaultFileName == NULL) {
strcpy(defaultFileName, DEFAULT_FILE_NAME);
printf("Error reading defaultFile from file, setting default, %s\n", defaultFileName);
}
} else
{
strcpy(defaultFileName, DEFAULT_FILE_NAME);
printf("Error reading defaultFile from file, setting default, %s\n", defaultFileName);
}
if (strlen(defaultFileName) > MAX_INDEX_NAME_LEN)
{
printf("Error: the default file name is too long, exiting.\n");
return -1;
}
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
sockTimeVal.tv_sec = -1;
if (str1 != NULL && str2 != NULL && !strcmp(str1, "secTimeout"))
sscanf(str2, "%ld", (long int *)&(sockTimeVal.tv_sec));
if (sockTimeVal.tv_sec < 0)
{
sockTimeVal.tv_sec = DEFAULT_SEC_TO;
printf("Error reading secTimeout from file, setting default, %ld\n", (long int)sockTimeVal.tv_sec);
}
printf("timeout sec: %ld, ", (long int)sockTimeVal.tv_sec);
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
sockTimeVal.tv_usec = -1;
if (str1 != NULL && str2 != NULL && !strcmp(str1, "uSecTimeout"))
sscanf(str2, "%ld", (long int *)&(sockTimeVal.tv_usec));
if (sockTimeVal.tv_usec < 0)
{
sockTimeVal.tv_usec = DEFAULT_USEC_TO;
printf("Error reading usecTimeout from file, setting default, %ld\n", (long int)sockTimeVal.tv_usec);
}
printf("usec: %ld\n", (long int)sockTimeVal.tv_usec);
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
if (str1 != NULL && str2 != NULL && !strcmp(str1, "logFile"))
{
sscanf(str2, "%s", logFileName);
if (logFileName == NULL)
{
strcpy(logFileName, DEFAULT_LOG_FILE);
printf("Error reading logFile from file, setting default, %s\n", logFileName);
}
} else
{
strcpy(logFileName, DEFAULT_LOG_FILE);
printf("Error reading logFile from file, setting default, %s\n", logFileName);
}
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
if (str1 != NULL && str2 != NULL && !strcmp(str1, "mimeTypesFile"))
{
sscanf(str2, "%s", mimeTypesFileName);
if (mimeTypesFileName == NULL)
{
strcpy(mimeTypesFileName, DEFAULT_MIME_FILE);
printf("Error reading mimeTypesFile from file, setting default, %s\n", mimeTypesFileName);
}
} else {
strcpy(mimeTypesFileName, DEFAULT_MIME_FILE);
printf("Error reading mimeTypesFile from file, setting default, %s\n", mimeTypesFileName);
}
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
if (str1 != NULL && str2 != NULL && !strcmp(str1, "phpFile"))
{
sscanf(str2, "%s", phpFileName);
if (logFileName == NULL)
{
strcpy(phpFileName, DEFAULT_LOG_FILE);
printf("Error reading phpFile from file, setting default, %s\n", phpFileName);
}
} else
{
strcpy(phpFileName, DEFAULT_PHP_FILE);
printf("Error reading phpFile from file, setting default, %s\n", phpFileName);
}
if (!feof(f)) fscanf(f, "%s %s", str1, str2);
if (str1 != NULL && str2 != NULL && !strcmp(str1, "cgiRoot"))
{
sscanf(str2, "%s", cgiRoot);
if (cgiRoot == NULL)
{
strcpy(cgiRoot, DEFAULT_CGI_ROOT);
printf("Error reading cgiRoot from file, setting default, %s\n", cgiRoot);
}
} else
{
strcpy(cgiRoot, DEFAULT_CGI_ROOT);
printf("Error reading cgiRoot from file, setting default, %s\n", cgiRoot);
}
fclose(f);
initMimeTypes();
return 0;
}
int main (argc, argv)
/* contains the main connection accept loop which calls analyzers and handlers */
int argc;
char *argv[];
{
int port; /* the port the server is listening to */
int maxChildren; /* maximum number of running children, configured even if without fork() */
char buff[BUFFER_SIZE+1];
char tempBuff[2]; /* socket read buffer */
int howMany;
int totalRead;
int stuckCounter; /* to behold read progress and catch nasty loops */
int readFinished;
struct request gottenReq;
int isKeepAlive;
struct sockaddr_in listenName; /* data struct for the listen port */
struct sockaddr_in acceptedSockStruct; /* sockaddr for the internetworking */
socklen_t acceptedSocketLen; /* size of the structure */
#ifdef SOCKADDR_REUSE
int sockReuse;
#endif
#ifdef FORKING_REQUEST
int pid; /* here we record the childs' pid */
int runningChildren;
#endif
initParameters(&port, &maxChildren, argc, argv); /* read config and settings */
listenName.sin_family = AF_INET;
listenName.sin_addr.s_addr=INADDR_ANY;
listenName.sin_port = htons(port);
acceptedSocketLen = sizeof(acceptedSockStruct);
/* signal init */
signal_MACRO(SIGINT, intCatch);
signal_MACRO(SIGPIPE, brokenPipe);
signal_MACRO(SIGKILL, killCatch);
if (logFileOpen())
{
printf("log file creation error or other misconfiguration\n");
exit(0);
}
theSocket = socket (AF_INET, SOCK_STREAM, 0);
if (theSocket == -1)
{
printf("socket creation error occoured\n");
return -1;
}
#ifdef SOCKADDR_REUSE
sockReuse = YES;
error = setsockopt (theSocket, SOL_SOCKET, SO_REUSEADDR, &sockReuse, sizeof(sockReuse));
if (error == -1)
{
printf("socket reuse option setting failed\n");
}
#endif
error = bind (theSocket, (struct sockaddr*) &listenName, sizeof(listenName));
if (error == -1)
{
printf("socket binding error occoured\n");
return -2;
}
error = listen(theSocket, BACK_LOG);
if (error)
{
printf ("listen error\n");
return -1;
}
isKeepAlive = NO;
#ifdef FORKING_REQUEST
runningChildren = 0;
#endif
while (1)
{
DBGPRINTF(("listen\n"));
newSocket = accept (theSocket, (struct sockaddr *) &acceptedSockStruct, &acceptedSocketLen);
#ifdef FORKING_REQUEST
pid = fork();
if (pid)
{
/* the parent process */
int exitResult;
int i;
if (pid < 0)
{
/* we checked for a fork error */
printf ("A forking error occoured!\n");
}
runningChildren++;
newSocketReady = NO;
close(newSocket);
/* we take care of the running children and their status */
/* if too many childrends are running, we block and wait for an exit */
if (runningChildren >= maxChildren)
{
wait(&exitResult);
DBGPRINTF(("Child exited with: %d\n", exitResult));
runningChildren--;
}
/* here we collect quickly possible exits without blocking */
for (i = 0; i < runningChildren; i++);
{
if (waitpid(-1, &exitResult, WNOHANG) > 0)
{
DBGPRINTF(("Child exited with: %d\n", exitResult));
runningChildren--;
}
}
} else
{
#endif
/* the child process */
if (newSocket == -1)
{
newSocketReady = NO;
printf("error accepting\n");
} else
{
#ifdef BRAIN_DEAD_CAST
strcpy(gottenReq.address, (char *)inet_ntoa(acceptedSockStruct.sin_addr));
#else
strcpy(gottenReq.address, (char *)inet_ntoa((struct in_addr)acceptedSockStruct.sin_addr));
#endif
DBGPRINTF(("accepted from %s\n", gottenReq.address));
newSocketReady = YES;
error = setsockopt (newSocket, SOL_SOCKET, SO_RCVTIMEO, &sockTimeVal, sizeof(sockTimeVal));
#ifdef PRINTF_DEBUG
if(error)
perror("setsockopt: ");
#endif
buff[0] = '\0';
readFinished = NO;
totalRead = 0;
stuckCounter = 0;
while (!readFinished)
{
howMany = recv(newSocket, tempBuff, 1, 0);
#ifdef PRINTF_DEBUG
if (!howMany)
DBGPRINTF(("read %d bytes\n ", howMany));
#endif
if (howMany <= 0)
{
if (errno == EAGAIN)
{
DBGPRINTF(("resource not available on header read\n"));
stuckCounter++;
if (stuckCounter >= MAX_STUCK_COUNTER)
{
DBGPRINTF(("Loop in read catched! closing connection.\n"));
if (newSocketReady)
{
DBGPRINTF(("new socket was ready....\n"));
if (!close(newSocket))
DBGPRINTF(("closed ns\n"));
}
newSocketReady = NO;
readFinished = YES;
}
} else
{
DBGPRINTF(("read error: %d\n", errno));
newSocketReady = NO;
readFinished = YES;
close(newSocket);
}
} else
{
tempBuff[howMany] = '\0';
totalRead += howMany;
if (totalRead >= BUFFER_SIZE) /* checking for buffer overflow */
{
DBGPRINTF(("Buffer overflow on request read\n"));
sayError(newSocket, INPUT_LINE_TOO_LONG, "", gottenReq);
readFinished = YES;
} else
{
buff[totalRead-1] = tempBuff[0];
buff[totalRead] = '\0';
if (buff[totalRead-1] == '\n' && buff[totalRead-3] == '\n')
{ /* Header should normally end with cr-cr */
readFinished = YES;
}
}
}
} /* end of header read while */
if (totalRead <= 0)
{
DBGPRINTF(("Request read error\n"));
} else if (buff[totalRead-1] != '\n' && strncmp(buff, "POST", 4)) /* 4 = strlen(POST) */
{
/* POST doesn't terminate data with a newline but GET should */
DBGPRINTF(("Unterminated request header\n"));
sayError(newSocket, INPUT_LINE_TOO_LONG, "", gottenReq);
} else
{
analyzeRequest(newSocket, buff, &gottenReq);
error = setsockopt (newSocket, SOL_SOCKET, SO_SNDTIMEO, &sockTimeVal, sizeof(sockTimeVal));
handleMethod(port, newSocket, gottenReq);
/* detecting the keep-alive connection, if it shall be ever better implemented
if (gottenReq.keepAlive) {
isKeepAlive = YES;
DBGPRINTF(("bogus keep alive thing\n"));
} */
}
if (close(newSocket))
DBGPRINTF(("error closing socket after connection\n"));
else
newSocketReady = NO;
}
#ifdef FORKING_REQUEST
exit(0);
} /* end of fork if */
#endif
} /* end of listen error if */
close(theSocket); /* if we shall ever exit the infinite loop... */
}