--- /dev/null
+/* Copyright information is at end of file */
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#ifdef WIN32
+ #include <io.h>
+#else
+ #include <unistd.h>
+ #include <grp.h>
+#endif
+#include <fcntl.h>
+
+#include "xmlrpc_config.h"
+#include "mallocvar.h"
+#include "xmlrpc-c/string_int.h"
+#include "xmlrpc-c/sleep_int.h"
+
+#include "xmlrpc-c/abyss.h"
+#include "trace.h"
+#include "session.h"
+#include "conn.h"
+#include "socket.h"
+#ifdef WIN32
+ #include "socket_win.h"
+#else
+ #include "socket_unix.h"
+#endif
+#include "http.h"
+#include "date.h"
+#include "abyss_info.h"
+
+#include "server.h"
+
+
+void
+ServerTerminate(TServer * const serverP) {
+
+ struct _TServer * const srvP = serverP->srvP;
+
+ srvP->terminationRequested = true;
+}
+
+
+
+void
+ServerResetTerminate(TServer * const serverP) {
+
+ struct _TServer * const srvP = serverP->srvP;
+
+ srvP->terminationRequested = false;
+}
+
+
+
+typedef int (*TQSortProc)(const void *, const void *);
+
+static int
+cmpfilenames(const TFileInfo **f1,const TFileInfo **f2) {
+ if (((*f1)->attrib & A_SUBDIR) && !((*f2)->attrib & A_SUBDIR))
+ return (-1);
+ if (!((*f1)->attrib & A_SUBDIR) && ((*f2)->attrib & A_SUBDIR))
+ return 1;
+
+ return strcmp((*f1)->name,(*f2)->name);
+}
+
+static int
+cmpfiledates(const TFileInfo **f1,const TFileInfo **f2) {
+ if (((*f1)->attrib & A_SUBDIR) && !((*f2)->attrib & A_SUBDIR))
+ return (-1);
+ if (!((*f1)->attrib & A_SUBDIR) && ((*f2)->attrib & A_SUBDIR))
+ return 1;
+
+ return ((*f1)->time_write-(*f2)->time_write);
+}
+
+
+
+static void
+determineSortType(const char * const query,
+ abyss_bool * const ascendingP,
+ uint16_t * const sortP,
+ abyss_bool * const textP,
+ const char ** const errorP) {
+
+ *ascendingP = TRUE;
+ *sortP = 1;
+ *textP = FALSE;
+ *errorP = NULL;
+
+ if (query) {
+ if (xmlrpc_streq(query, "plain"))
+ *textP = TRUE;
+ else if (xmlrpc_streq(query, "name-up")) {
+ *sortP = 1;
+ *ascendingP = TRUE;
+ } else if (xmlrpc_streq(query, "name-down")) {
+ *sortP = 1;
+ *ascendingP = FALSE;
+ } else if (xmlrpc_streq(query, "date-up")) {
+ *sortP = 2;
+ *ascendingP = TRUE;
+ } else if (xmlrpc_streq(query, "date-down")) {
+ *sortP = 2;
+ *ascendingP = FALSE;
+ } else {
+ xmlrpc_asprintf(errorP, "invalid query value '%s'", query);
+ }
+ }
+}
+
+
+
+static void
+generateListing(TList * const listP,
+ char * const z,
+ const char * const uri,
+ TPool * const poolP,
+ const char ** const errorP,
+ uint16_t * const responseStatusP) {
+
+ TFileInfo fileinfo;
+ TFileFind findhandle;
+
+ *errorP = NULL;
+
+ if (!FileFindFirst(&findhandle, z, &fileinfo)) {
+ *responseStatusP = ResponseStatusFromErrno(errno);
+ xmlrpc_asprintf(errorP, "Can't read first entry in directory");
+ } else {
+ ListInit(listP);
+
+ do {
+ TFileInfo * fi;
+ /* Files whose names start with a dot are ignored */
+ /* This includes implicitly the ./ and ../ */
+ if (*fileinfo.name == '.') {
+ if (xmlrpc_streq(fileinfo.name, "..")) {
+ if (xmlrpc_streq(uri, "/"))
+ continue;
+ } else
+ continue;
+ }
+ fi = (TFileInfo *)PoolAlloc(poolP, sizeof(fileinfo));
+ if (fi) {
+ abyss_bool success;
+ memcpy(fi, &fileinfo, sizeof(fileinfo));
+ success = ListAdd(listP, fi);
+ if (!success)
+ xmlrpc_asprintf(errorP, "ListAdd() failed");
+ } else
+ xmlrpc_asprintf(errorP, "PoolAlloc() failed.");
+ } while (!*errorP && FileFindNext(&findhandle, &fileinfo));
+
+ if (*errorP) {
+ *responseStatusP = 500;
+ ListFree(listP);
+ }
+ FileFindClose(&findhandle);
+ }
+}
+
+
+
+static void
+sendDirectoryDocument(TList * const listP,
+ abyss_bool const ascending,
+ uint16_t const sort,
+ abyss_bool const text,
+ const char * const uri,
+ MIMEType * const mimeTypeP,
+ TSession * const sessionP,
+ char * const z) {
+
+ char *p,z1[26],z2[20],z3[9],u;
+ const char * z4;
+ int16_t i;
+ uint32_t k;
+
+ if (text) {
+ sprintf(z, "Index of %s" CRLF, uri);
+ i = strlen(z)-2;
+ p = z + i + 2;
+
+ while (i > 0) {
+ *(p++) = '-';
+ --i;
+ }
+
+ *p = '\0';
+ strcat(z, CRLF CRLF
+ "Name Size "
+ "Date-Time Type" CRLF
+ "------------------------------------"
+ "--------------------------------------------"CRLF);
+ } else {
+ sprintf(z, "<HTML><HEAD><TITLE>Index of %s</TITLE></HEAD><BODY>"
+ "<H1>Index of %s</H1><PRE>",
+ uri, uri);
+ strcat(z, "Name Size "
+ "Date-Time Type<HR WIDTH=100%>"CRLF);
+ }
+
+ HTTPWriteBodyChunk(sessionP, z, strlen(z));
+
+ /* Sort the files */
+ qsort(listP->item, listP->size, sizeof(void *),
+ (TQSortProc)(sort == 1 ? cmpfilenames : cmpfiledates));
+
+ /* Write the listing */
+ if (ascending)
+ i = 0;
+ else
+ i = listP->size - 1;
+
+ while ((i < listP->size) && (i >= 0)) {
+ TFileInfo * fi;
+ struct tm ftm;
+
+ fi = listP->item[i];
+
+ if (ascending)
+ ++i;
+ else
+ --i;
+
+ strcpy(z, fi->name);
+
+ k = strlen(z);
+
+ if (fi->attrib & A_SUBDIR) {
+ z[k++] = '/';
+ z[k] = '\0';
+ }
+
+ if (k > 24) {
+ z[10] = '\0';
+ strcpy(z1, z);
+ strcat(z1, "...");
+ strcat(z1, z + k - 11);
+ k = 24;
+ p = z1 + 24;
+ } else {
+ strcpy(z1, z);
+
+ ++k;
+ p = z1 + k;
+ while (k < 25)
+ z1[k++] = ' ';
+
+ z1[25] = '\0';
+ }
+
+ ftm = *gmtime(&fi->time_write);
+ sprintf(z2, "%02u/%02u/%04u %02u:%02u:%02u",ftm.tm_mday,ftm.tm_mon+1,
+ ftm.tm_year+1900,ftm.tm_hour,ftm.tm_min,ftm.tm_sec);
+
+ if (fi->attrib & A_SUBDIR) {
+ strcpy(z3, " -- ");
+ z4 = "Directory";
+ } else {
+ if (fi->size < 9999)
+ u = 'b';
+ else {
+ fi->size /= 1024;
+ if (fi->size < 9999)
+ u = 'K';
+ else {
+ fi->size /= 1024;
+ if (fi->size < 9999)
+ u = 'M';
+ else
+ u = 'G';
+ }
+ }
+
+ sprintf(z3, "%5llu %c", fi->size, u);
+
+ if (xmlrpc_streq(fi->name, ".."))
+ z4 = "";
+ else
+ z4 = MIMETypeFromFileName2(mimeTypeP, fi->name);
+
+ if (!z4)
+ z4 = "Unknown";
+ }
+
+ if (text)
+ sprintf(z, "%s%s %s %s %s"CRLF, z1, p, z3, z2, z4);
+ else
+ sprintf(z, "<A HREF=\"%s%s\">%s</A>%s %s %s %s"CRLF,
+ fi->name, fi->attrib & A_SUBDIR ? "/" : "",
+ z1, p, z3, z2, z4);
+
+ HTTPWriteBodyChunk(sessionP, z, strlen(z));
+ }
+
+ /* Write the tail of the file */
+ if (text)
+ strcpy(z, SERVER_PLAIN_INFO);
+ else
+ strcpy(z, "</PRE>" SERVER_HTML_INFO "</BODY></HTML>" CRLF CRLF);
+
+ HTTPWriteBodyChunk(sessionP, z, strlen(z));
+}
+
+
+
+static void
+fileDate(TSession * const sessionP,
+ time_t const statFilemodTime,
+ TDate * const fileDateP) {
+
+ abyss_bool haveDate;
+ TDate filemodDate;
+
+ haveDate = DateFromLocal(&filemodDate, statFilemodTime);
+
+ if (haveDate) {
+ if (DateCompare(&sessionP->date, &filemodDate) < 0)
+ *fileDateP = sessionP->date;
+ else
+ *fileDateP = filemodDate;
+ } else
+ *fileDateP = sessionP->date;
+}
+
+
+
+static abyss_bool
+ServerDirectoryHandler(TSession * const r,
+ char * const z,
+ time_t const fileModTime,
+ MIMEType * const mimeTypeP) {
+
+ TList list;
+ abyss_bool text;
+ abyss_bool ascending;
+ uint16_t sort; /* 1=by name, 2=by date */
+ TPool pool;
+ TDate date;
+ const char * error;
+ uint16_t responseStatus;
+ TDate dirdate;
+ const char * imsHdr;
+
+ determineSortType(r->request_info.query, &ascending, &sort, &text, &error);
+
+ if (error) {
+ ResponseStatus(r,400);
+ xmlrpc_strfree(error);
+ return TRUE;
+ }
+
+ fileDate(r, fileModTime, &dirdate);
+
+ imsHdr = RequestHeaderValue(r, "If-Modified-Since");
+ if (imsHdr) {
+ if (DateDecode(imsHdr, &date)) {
+ if (DateCompare(&dirdate, &date) <= 0) {
+ ResponseStatus(r, 304);
+ ResponseWrite(r);
+ return TRUE;
+ }
+ }
+ }
+
+ if (!PoolCreate(&pool, 1024)) {
+ ResponseStatus(r, 500);
+ return TRUE;
+ }
+
+ generateListing(&list, z, r->request_info.uri,
+ &pool, &error, &responseStatus);
+ if (error) {
+ ResponseStatus(r, responseStatus);
+ xmlrpc_strfree(error);
+ PoolFree(&pool);
+ return TRUE;
+ }
+
+ /* Send something to the user to show that we are still alive */
+ ResponseStatus(r, 200);
+ ResponseContentType(r, (text ? "text/plain" : "text/html"));
+
+ if (DateToString(&dirdate, z))
+ ResponseAddField(r, "Last-Modified", z);
+
+ ResponseChunked(r);
+ ResponseWrite(r);
+
+ if (r->request_info.method!=m_head)
+ sendDirectoryDocument(&list, ascending, sort, text,
+ r->request_info.uri, mimeTypeP, r, z);
+
+ HTTPWriteEndChunk(r);
+
+ /* Free memory and exit */
+ ListFree(&list);
+ PoolFree(&pool);
+
+ return TRUE;
+}
+
+
+
+#define BOUNDARY "##123456789###BOUNDARY"
+
+static void
+sendBody(TSession * const sessionP,
+ TFile * const fileP,
+ uint64_t const filesize,
+ const char * const mediatype,
+ uint64_t const start0,
+ uint64_t const end0,
+ char * const z) {
+
+ if (sessionP->ranges.size == 0)
+ ConnWriteFromFile(sessionP->conn, fileP, 0, filesize - 1, z, 4096, 0);
+ else if (sessionP->ranges.size == 1)
+ ConnWriteFromFile(sessionP->conn, fileP, start0, end0, z, 4096, 0);
+ else {
+ uint64_t i;
+ for (i = 0; i <= sessionP->ranges.size; ++i) {
+ ConnWrite(sessionP->conn,"--", 2);
+ ConnWrite(sessionP->conn, BOUNDARY, strlen(BOUNDARY));
+ ConnWrite(sessionP->conn, CRLF, 2);
+
+ if (i < sessionP->ranges.size) {
+ uint64_t start;
+ uint64_t end;
+ abyss_bool decoded;
+
+ decoded = RangeDecode((char *)(sessionP->ranges.item[i]),
+ filesize,
+ &start, &end);
+ if (decoded) {
+ /* Entity header, not response header */
+ sprintf(z, "Content-type: %s" CRLF
+ "Content-range: bytes %llu-%llu/%llu" CRLF
+ "Content-length: %llu" CRLF
+ CRLF, mediatype, start, end,
+ filesize, end-start+1);
+
+ ConnWrite(sessionP->conn, z, strlen(z));
+
+ ConnWriteFromFile(sessionP->conn, fileP, start, end, z,
+ 4096, 0);
+ }
+ }
+ }
+ }
+}
+
+
+
+static abyss_bool
+ServerFileHandler(TSession * const r,
+ char * const z,
+ time_t const fileModTime,
+ MIMEType * const mimeTypeP) {
+
+ const char * mediatype;
+ TFile file;
+ uint64_t filesize;
+ uint64_t start;
+ uint64_t end;
+ TDate date;
+ char * p;
+ TDate filedate;
+
+ mediatype = MIMETypeGuessFromFile2(mimeTypeP, z);
+
+ if (!FileOpen(&file,z,O_BINARY | O_RDONLY)) {
+ ResponseStatusErrno(r);
+ return TRUE;
+ }
+
+ fileDate(r, fileModTime, &filedate);
+
+ p = RequestHeaderValue(r, "if-modified-since");
+ if (p) {
+ if (DateDecode(p,&date)) {
+ if (DateCompare(&filedate, &date) <= 0) {
+ ResponseStatus(r, 304);
+ ResponseWrite(r);
+ return TRUE;
+ } else
+ r->ranges.size = 0;
+ }
+ }
+ filesize = FileSize(&file);
+
+ switch (r->ranges.size) {
+ case 0:
+ ResponseStatus(r, 200);
+ break;
+
+ case 1: {
+ abyss_bool decoded;
+ decoded = RangeDecode((char *)(r->ranges.item[0]), filesize,
+ &start, &end);
+ if (!decoded) {
+ ListFree(&(r->ranges));
+ ResponseStatus(r, 200);
+ break;
+ }
+
+ sprintf(z, "bytes %llu-%llu/%llu", start, end, filesize);
+
+ ResponseAddField(r, "Content-range", z);
+ ResponseContentLength(r, end - start + 1);
+ ResponseStatus(r, 206);
+ } break;
+
+ default:
+ ResponseContentType(r, "multipart/ranges; boundary=" BOUNDARY);
+ ResponseStatus(r, 206);
+ break;
+ }
+
+ if (r->ranges.size == 0) {
+ ResponseContentLength(r, filesize);
+ ResponseContentType(r, mediatype);
+ }
+
+ if (DateToString(&filedate, z))
+ ResponseAddField(r, "Last-Modified", z);
+
+ ResponseWrite(r);
+
+ if (r->request_info.method != m_head)
+ sendBody(r, &file, filesize, mediatype, start, end, z);
+
+ FileClose(&file);
+
+ return TRUE;
+}
+
+
+
+static abyss_bool
+ServerDefaultHandlerFunc(TSession * const sessionP) {
+
+ struct _TServer * const srvP = ConnServer(sessionP->conn)->srvP;
+
+ char *p;
+ char z[4096];
+ TFileStat fs;
+ unsigned int i;
+ abyss_bool endingslash=FALSE;
+
+ if (!RequestValidURIPath(sessionP)) {
+ ResponseStatus(sessionP, 400);
+ return TRUE;
+ }
+
+ /* Must check for * (asterisk uri) in the future */
+ if (sessionP->request_info.method == m_options) {
+ ResponseAddField(sessionP, "Allow", "GET, HEAD");
+ ResponseContentLength(sessionP, 0);
+ ResponseStatus(sessionP, 200);
+ return TRUE;
+ }
+
+ if ((sessionP->request_info.method != m_get) &&
+ (sessionP->request_info.method != m_head)) {
+ ResponseAddField(sessionP, "Allow", "GET, HEAD");
+ ResponseStatus(sessionP, 405);
+ return TRUE;
+ }
+
+ strcpy(z, srvP->filespath);
+ strcat(z, sessionP->request_info.uri);
+
+ p = z + strlen(z) - 1;
+ if (*p == '/') {
+ endingslash = TRUE;
+ *p = '\0';
+ }
+
+#ifdef WIN32
+ p = z;
+ while (*p) {
+ if ((*p) == '/')
+ *p= '\\';
+
+ ++p;
+ }
+#endif /* WIN32 */
+
+ if (!FileStat(z, &fs)) {
+ ResponseStatusErrno(sessionP);
+ return TRUE;
+ }
+
+ if (fs.st_mode & S_IFDIR) {
+ /* Redirect to the same directory but with the ending slash
+ ** to avoid problems with some browsers (IE for examples) when
+ ** they generate relative urls */
+ if (!endingslash) {
+ strcpy(z, sessionP->request_info.uri);
+ p = z+strlen(z);
+ *p = '/';
+ *(p+1) = '\0';
+ ResponseAddField(sessionP, "Location", z);
+ ResponseStatus(sessionP, 302);
+ ResponseWrite(sessionP);
+ return TRUE;
+ }
+
+ *p = DIRECTORY_SEPARATOR[0];
+ ++p;
+
+ i = srvP->defaultfilenames.size;
+ while (i-- > 0) {
+ *p = '\0';
+ strcat(z, (srvP->defaultfilenames.item[i]));
+ if (FileStat(z, &fs)) {
+ if (!(fs.st_mode & S_IFDIR))
+ return ServerFileHandler(sessionP, z, fs.st_mtime,
+ srvP->mimeTypeP);
+ }
+ }
+
+ *(p-1) = '\0';
+
+ if (!FileStat(z, &fs)) {
+ ResponseStatusErrno(sessionP);
+ return TRUE;
+ }
+ return ServerDirectoryHandler(sessionP, z, fs.st_mtime,
+ srvP->mimeTypeP);
+ } else
+ return ServerFileHandler(sessionP, z, fs.st_mtime,
+ srvP->mimeTypeP);
+}
+
+
+
+static void
+initUnixStuff(struct _TServer * const srvP) {
+#ifndef WIN32
+ srvP->pidfile = srvP->uid = srvP->gid = -1;
+#endif
+}
+
+
+
+static abyss_bool
+logOpen(struct _TServer * const srvP) {
+
+ abyss_bool success;
+
+ success = FileOpenCreate(&srvP->logfile, srvP->logfilename,
+ O_WRONLY | O_APPEND);
+ if (success) {
+ abyss_bool success;
+ success = MutexCreate(&srvP->logmutex);
+ if (success)
+ srvP->logfileisopen = TRUE;
+ else
+ TraceMsg("Can't create mutex for log file");
+
+ if (!success)
+ FileClose(&srvP->logfile);
+ } else
+ TraceMsg("Can't open log file '%s'", srvP->logfilename);
+
+ return success;
+}
+
+
+
+static void
+logClose(struct _TServer * const srvP) {
+
+ if (srvP->logfileisopen) {
+ FileClose(&srvP->logfile);
+ MutexFree(&srvP->logmutex);
+ srvP->logfileisopen = FALSE;
+ }
+}
+
+
+
+static void
+initSocketStuff(struct _TServer * const srvP,
+ abyss_bool const noAccept,
+ TSocket * const userSocketP,
+ uint16_t const port,
+ const char ** const errorP) {
+
+ if (userSocketP) {
+ *errorP = NULL;
+ srvP->serverAcceptsConnections = TRUE;
+ srvP->socketBound = TRUE;
+ srvP->listenSocketP = userSocketP;
+ } else if (noAccept) {
+ *errorP = NULL;
+ srvP->serverAcceptsConnections = FALSE;
+ srvP->socketBound = FALSE;
+ } else {
+ *errorP = NULL;
+ srvP->serverAcceptsConnections = TRUE;
+ srvP->socketBound = FALSE;
+ srvP->port = port;
+ }
+ srvP->weCreatedListenSocket = FALSE;
+}
+
+
+
+static void
+createServer(struct _TServer ** const srvPP,
+ abyss_bool const noAccept,
+ TSocket * const userSocketP,
+ uint16_t const portNumber,
+ const char ** const errorP) {
+
+ struct _TServer * srvP;
+
+ MALLOCVAR(srvP);
+
+ if (srvP == NULL) {
+ xmlrpc_asprintf(errorP,
+ "Unable to allocate space for server descriptor");
+ } else {
+ srvP->terminationRequested = false;
+
+ initSocketStuff(srvP, noAccept, userSocketP, portNumber, errorP);
+
+ if (!*errorP) {
+ srvP->defaulthandler = ServerDefaultHandlerFunc;
+
+ srvP->name = strdup("unnamed");
+ srvP->filespath = strdup(DEFAULT_DOCS);
+ srvP->logfilename = NULL;
+ srvP->keepalivetimeout = 15;
+ srvP->keepalivemaxconn = 30;
+ srvP->timeout = 15;
+ srvP->advertise = TRUE;
+ srvP->mimeTypeP = NULL;
+ srvP->useSigchld = FALSE;
+
+ initUnixStuff(srvP);
+
+ ListInitAutoFree(&srvP->handlers);
+ ListInitAutoFree(&srvP->defaultfilenames);
+
+ srvP->logfileisopen = FALSE;
+
+ *errorP = NULL;
+ }
+ if (*errorP)
+ free(srvP);
+ }
+ *srvPP = srvP;
+}
+
+
+
+static void
+setNamePathLog(TServer * const serverP,
+ const char * const name,
+ const char * const filesPath,
+ const char * const logFileName) {
+/*----------------------------------------------------------------------------
+ This odd function exists to help with backward compatibility.
+ Today, we have the expandable model where you create a server with
+ default parameters, then use ServerSet... functions to choose
+ non-default parameters. But before, you specified these three
+ parameters right in the arguments of various create functions.
+-----------------------------------------------------------------------------*/
+ if (name)
+ ServerSetName(serverP, name);
+ if (filesPath)
+ ServerSetFilesPath(serverP, filesPath);
+ if (logFileName)
+ ServerSetLogFileName(serverP, logFileName);
+}
+
+
+
+abyss_bool
+ServerCreate(TServer * const serverP,
+ const char * const name,
+ uint16_t const portNumber,
+ const char * const filesPath,
+ const char * const logFileName) {
+
+ abyss_bool const noAcceptFalse = FALSE;
+
+ abyss_bool success;
+ const char * error;
+
+ createServer(&serverP->srvP, noAcceptFalse, NULL, portNumber, &error);
+
+ if (error) {
+ TraceMsg(error);
+ xmlrpc_strfree(error);
+ success = FALSE;
+ } else {
+ success = TRUE;
+
+ setNamePathLog(serverP, name, filesPath, logFileName);
+ }
+
+ return success;
+}
+
+
+
+static void
+createSocketFromOsSocket(TOsSocket const osSocket,
+ TSocket ** const socketPP) {
+
+#ifdef WIN32
+ SocketWinCreateWinsock(osSocket, socketPP);
+#else
+ SocketUnixCreateFd(osSocket, socketPP);
+#endif
+}
+
+
+
+abyss_bool
+ServerCreateSocket(TServer * const serverP,
+ const char * const name,
+ TOsSocket const socketFd,
+ const char * const filesPath,
+ const char * const logFileName) {
+
+ abyss_bool success;
+ TSocket * socketP;
+
+ createSocketFromOsSocket(socketFd, &socketP);
+
+ if (socketP) {
+ abyss_bool const noAcceptFalse = FALSE;
+
+ const char * error;
+
+ createServer(&serverP->srvP, noAcceptFalse, socketP, 0, &error);
+
+ if (error) {
+ TraceMsg(error);
+ success = FALSE;
+ xmlrpc_strfree(error);
+ } else {
+ success = TRUE;
+
+ setNamePathLog(serverP, name, filesPath, logFileName);
+ }
+ } else
+ success = FALSE;
+
+ return success;
+}
+
+
+
+abyss_bool
+ServerCreateNoAccept(TServer * const serverP,
+ const char * const name,
+ const char * const filesPath,
+ const char * const logFileName) {
+
+ abyss_bool const noAcceptTrue = TRUE;
+
+ abyss_bool success;
+ const char * error;
+
+ createServer(&serverP->srvP, noAcceptTrue, NULL, 0, &error);
+
+ if (error) {
+ TraceMsg(error);
+ success = FALSE;
+ xmlrpc_strfree(error);
+ } else {
+ success = TRUE;
+
+ setNamePathLog(serverP, name, filesPath, logFileName);
+ }
+ return success;
+}
+
+
+
+void
+ServerCreateSocket2(TServer * const serverP,
+ TSocket * const socketP,
+ const char ** const errorP) {
+
+ abyss_bool const noAcceptFalse = FALSE;
+
+ assert(socketP);
+
+ createServer(&serverP->srvP, noAcceptFalse, socketP, 0, errorP);
+}
+
+
+
+static void
+terminateHandlers(TList * const handlersP) {
+/*----------------------------------------------------------------------------
+ Terminate all handlers in the list '*handlersP'.
+
+ I.e. call each handler's terminate function.
+-----------------------------------------------------------------------------*/
+ if (handlersP->item) {
+ unsigned int i;
+ for (i = handlersP->size; i > 0; --i) {
+ URIHandler2 * const handlerP = handlersP->item[i-1];
+ if (handlerP->term)
+ handlerP->term(handlerP->userdata);
+ }
+ }
+}
+
+
+
+void
+ServerFree(TServer * const serverP) {
+
+ struct _TServer * const srvP = serverP->srvP;
+
+ if (srvP->weCreatedListenSocket)
+ SocketDestroy(srvP->listenSocketP);
+
+ xmlrpc_strfree(srvP->name);
+
+ xmlrpc_strfree(srvP->filespath);
+
+ ListFree(&srvP->defaultfilenames);
+
+ terminateHandlers(&srvP->handlers);
+
+ ListFree(&srvP->handlers);
+
+ logClose(srvP);
+
+ if (srvP->logfilename)
+ xmlrpc_strfree(srvP->logfilename);
+
+ free(srvP);
+}
+
+
+
+void
+ServerSetName(TServer * const serverP,
+ const char * const name) {
+
+ xmlrpc_strfree(serverP->srvP->name);
+
+ serverP->srvP->name = strdup(name);
+}
+
+
+
+void
+ServerSetFilesPath(TServer * const serverP,
+ const char * const filesPath) {
+
+ xmlrpc_strfree(serverP->srvP->filespath);
+
+ serverP->srvP->filespath = strdup(filesPath);
+}
+
+
+
+void
+ServerSetLogFileName(TServer * const serverP,
+ const char * const logFileName) {
+
+ struct _TServer * const srvP = serverP->srvP;
+
+ if (srvP->logfilename)
+ xmlrpc_strfree(srvP->logfilename);
+
+ srvP->logfilename = strdup(logFileName);
+}
+
+
+
+void
+ServerSetKeepaliveTimeout(TServer * const serverP,
+ uint32_t const keepaliveTimeout) {
+
+ serverP->srvP->keepalivetimeout = keepaliveTimeout;
+}
+
+
+
+void
+ServerSetKeepaliveMaxConn(TServer * const serverP,
+ uint32_t const keepaliveMaxConn) {
+
+ serverP->srvP->keepalivemaxconn = keepaliveMaxConn;
+}
+
+
+
+void
+ServerSetTimeout(TServer * const serverP,
+ uint32_t const timeout) {
+
+ serverP->srvP->timeout = timeout;
+}
+
+
+
+void
+ServerSetAdvertise(TServer * const serverP,
+ abyss_bool const advertise) {
+
+ serverP->srvP->advertise = advertise;
+}
+
+
+
+void
+ServerSetMimeType(TServer * const serverP,
+ MIMEType * const MIMETypeP) {
+
+ serverP->srvP->mimeTypeP = MIMETypeP;
+}
+
+
+
+static void
+runUserHandler(TSession * const sessionP,
+ struct _TServer * const srvP) {
+
+ abyss_bool handled;
+ int i;
+
+ for (i = srvP->handlers.size-1, handled = FALSE;
+ i >= 0 && !handled;
+ --i) {
+ URIHandler2 * const handlerP = srvP->handlers.item[i];
+
+ if (handlerP->handleReq2)
+ handlerP->handleReq2(handlerP, sessionP, &handled);
+ else if (handlerP->handleReq1)
+ handled = handlerP->handleReq1(sessionP);
+ }
+
+ if (!handled)
+ ((URIHandler)(srvP->defaulthandler))(sessionP);
+}
+
+
+
+static void
+processDataFromClient(TConn * const connectionP,
+ abyss_bool const lastReqOnConn,
+ abyss_bool * const keepAliveP) {
+
+ TSession session;
+
+ RequestInit(&session, connectionP);
+
+ session.serverDeniesKeepalive = lastReqOnConn;
+
+ RequestRead(&session);
+ if (session.status == 0) {
+ if (session.version.major >= 2)
+ ResponseStatus(&session, 505);
+ else if (!RequestValidURI(&session))
+ ResponseStatus(&session, 400);
+ else
+ runUserHandler(&session, connectionP->server->srvP);
+ }
+ assert(session.status != 0);
+
+ if (session.responseStarted)
+ HTTPWriteEndChunk(&session);
+ else
+ ResponseError(&session);
+
+ *keepAliveP = HTTPKeepalive(&session);
+
+ SessionLog(&session);
+
+ RequestFree(&session);
+}
+
+
+static TThreadProc serverFunc;
+
+static void
+serverFunc(void * const userHandle) {
+/*----------------------------------------------------------------------------
+ Do server stuff on one connection. At its simplest, this means do
+ one HTTP request. But with keepalive, it can be many requests.
+-----------------------------------------------------------------------------*/
+ TConn * const connectionP = userHandle;
+ struct _TServer * const srvP = connectionP->server->srvP;
+
+ unsigned int requestCount;
+ /* Number of requests we've handled so far on this connection */
+ abyss_bool connectionDone;
+ /* No more need for this HTTP connection */
+
+ requestCount = 0;
+ connectionDone = FALSE;
+
+ while (!connectionDone) {
+ abyss_bool success;
+
+ /* Wait to read until timeout */
+ success = ConnRead(connectionP, srvP->keepalivetimeout);
+
+ if (!success)
+ connectionDone = TRUE;
+ else {
+ abyss_bool const lastReqOnConn =
+ requestCount + 1 >= srvP->keepalivemaxconn;
+
+ abyss_bool keepalive;
+
+ processDataFromClient(connectionP, lastReqOnConn, &keepalive);
+
+ ++requestCount;
+
+ if (!keepalive)
+ connectionDone = TRUE;
+
+ /**************** Must adjust the read buffer *****************/
+ ConnReadInit(connectionP);
+ }
+ }
+}
+
+
+
+static void
+createAndBindSocket(struct _TServer * const srvP) {
+
+ abyss_bool success;
+
+ success = SocketInit();
+ if (!success)
+ TraceMsg("Can't initialize TCP sockets");
+ else {
+ TSocket * socketP;
+
+ SocketUnixCreate(&socketP);
+
+ if (!socketP)
+ TraceMsg("Can't create a socket");
+ else {
+ abyss_bool success;
+
+ success = SocketBind(socketP, NULL, srvP->port);
+
+ if (!success)
+ TraceMsg("Failed to bind listening socket to port number %u",
+ srvP->port);
+ else {
+ srvP->weCreatedListenSocket = TRUE;
+ srvP->socketBound = TRUE;
+ srvP->listenSocketP = socketP;
+ }
+ if (!success)
+ SocketDestroy(socketP);
+ }
+ }
+}
+
+
+
+void
+ServerInit(TServer * const serverP) {
+/*----------------------------------------------------------------------------
+ Initialize a server to accept connections.
+
+ Do not confuse this with creating the server -- ServerCreate().
+
+ Not necessary or valid with a server that doesn't accept connections (i.e.
+ user supplies the TCP connections).
+-----------------------------------------------------------------------------*/
+ struct _TServer * const srvP = serverP->srvP;
+ abyss_bool success;
+
+ if (!srvP->serverAcceptsConnections) {
+ TraceMsg("ServerInit() is not valid on a server that doesn't "
+ "accept connections "
+ "(i.e. created with ServerCreateNoAccept)");
+ success = FALSE;
+ } else {
+ if (!srvP->socketBound)
+ createAndBindSocket(srvP);
+
+ if (srvP->socketBound) {
+ success = SocketListen(srvP->listenSocketP, MAX_CONN);
+
+ if (!success)
+ TraceMsg("Failed to listen on bound socket.");
+ } else
+ success = FALSE;
+ }
+ if (!success)
+ exit(1);
+}
+
+
+
+/* We don't do any locking on the outstanding connections list, so
+ we must make sure that only the master thread (the one that listens
+ for connections) ever accesses it.
+
+ That's why when a thread completes, it places the connection in
+ "finished" status, but doesn't destroy the connection.
+*/
+
+typedef struct {
+
+ TConn * firstP;
+ unsigned int count;
+ /* Redundant with 'firstP', for quick access */
+} outstandingConnList;
+
+
+
+static void
+createOutstandingConnList(outstandingConnList ** const listPP) {
+
+ outstandingConnList * listP;
+
+ MALLOCVAR_NOFAIL(listP);
+
+ listP->firstP = NULL; /* empty list */
+ listP->count = 0;
+
+ *listPP = listP;
+}
+
+
+
+static void
+destroyOutstandingConnList(outstandingConnList * const listP) {
+
+ assert(listP->firstP == NULL);
+ assert(listP->count == 0);
+
+ free(listP);
+}
+
+
+
+static void
+addToOutstandingConnList(outstandingConnList * const listP,
+ TConn * const connectionP) {
+
+ connectionP->nextOutstandingP = listP->firstP;
+
+ listP->firstP = connectionP;
+
+ ++listP->count;
+}
+
+
+
+static void
+freeFinishedConns(outstandingConnList * const listP) {
+/*----------------------------------------------------------------------------
+ Garbage-collect the resources associated with connections that are
+ finished with their jobs. Thread resources, connection pool
+ descriptor, etc.
+-----------------------------------------------------------------------------*/
+ TConn ** pp;
+
+ pp = &listP->firstP;
+
+ while (*pp) {
+ TConn * const connectionP = (*pp);
+
+ ThreadUpdateStatus(connectionP->threadP);
+
+ if (connectionP->finished) {
+ /* Take it out of the list */
+ *pp = connectionP->nextOutstandingP;
+ --listP->count;
+
+ ConnWaitAndRelease(connectionP);
+ } else {
+ /* Move to next connection in list */
+ pp = &connectionP->nextOutstandingP;
+ }
+ }
+}
+
+
+
+static void
+waitForConnectionFreed(outstandingConnList * const outstandingConnListP
+ ATTR_UNUSED) {
+/*----------------------------------------------------------------------------
+ Wait for a connection descriptor in 'connectionPool' to be probably
+ freed.
+-----------------------------------------------------------------------------*/
+
+ /* TODO: We should do something more sophisticated here. For pthreads,
+ we can have a thread signal us by semaphore when it terminates.
+ For fork, we might be able to use the "done" handler argument
+ to ConnCreate() to get interrupted when the death of a child
+ signal happens.
+ */
+
+ xmlrpc_millisecond_sleep(2000);
+}
+
+
+
+static void
+waitForNoConnections(outstandingConnList * const outstandingConnListP) {
+
+ while (outstandingConnListP->firstP) {
+ freeFinishedConns(outstandingConnListP);
+
+ if (outstandingConnListP->firstP)
+ waitForConnectionFreed(outstandingConnListP);
+ }
+}
+
+
+
+static void
+waitForConnectionCapacity(outstandingConnList * const outstandingConnListP) {
+/*----------------------------------------------------------------------------
+ Wait until there are fewer than the maximum allowed connections in
+ progress.
+-----------------------------------------------------------------------------*/
+ while (outstandingConnListP->count >= MAX_CONN) {
+ freeFinishedConns(outstandingConnListP);
+ if (outstandingConnListP->firstP)
+ waitForConnectionFreed(outstandingConnListP);
+ }
+}
+
+
+
+#ifndef WIN32
+void
+ServerHandleSigchld(pid_t const pid) {
+
+ ThreadHandleSigchld(pid);
+}
+#endif
+
+
+
+void
+ServerUseSigchld(TServer * const serverP) {
+
+ struct _TServer * const srvP = serverP->srvP;
+
+ srvP->useSigchld = TRUE;
+}
+
+
+
+TThreadDoneFn destroySocket;
+
+static void
+destroyConnSocket(void * const userHandle) {
+/*----------------------------------------------------------------------------
+ This is a "connection done" function for the connection the server
+ serves. It gets called some time after the connection has done its
+ thing. Its job is to clean up stuff the server created for use by
+ the connection, but the server can't clean up because the
+ connection might be processed asynchronously in a background
+ thread.
+
+ To wit, we destroy the connection's socket.
+-----------------------------------------------------------------------------*/
+ TConn * const connectionP = userHandle;
+
+ SocketDestroy(connectionP->socketP);
+}
+
+
+
+static void
+serverRun2(TServer * const serverP) {
+
+ struct _TServer * const srvP = serverP->srvP;
+ outstandingConnList * outstandingConnListP;
+
+ createOutstandingConnList(&outstandingConnListP);
+
+ while (!srvP->terminationRequested) {
+ TConn * connectionP;
+
+ abyss_bool connected;
+ abyss_bool failed;
+ TSocket * connectedSocketP;
+ TIPAddr peerIpAddr;
+
+ SocketAccept(srvP->listenSocketP,
+ &connected, &failed,
+ &connectedSocketP, &peerIpAddr);
+
+ if (connected) {
+ const char * error;
+
+ freeFinishedConns(outstandingConnListP);
+
+ waitForConnectionCapacity(outstandingConnListP);
+
+ ConnCreate(&connectionP, serverP, connectedSocketP,
+ &serverFunc, &destroyConnSocket, ABYSS_BACKGROUND,
+ srvP->useSigchld,
+ &error);
+ if (!error) {
+ addToOutstandingConnList(outstandingConnListP, connectionP);
+ ConnProcess(connectionP);
+ /* When connection is done (which could be later, courtesy
+ of a background thread), destroyConnSocket() will
+ destroy *connectedSocketP.
+ */
+ } else {
+ xmlrpc_strfree(error);
+ SocketDestroy(connectedSocketP);
+ }
+ } else if (failed)
+ TraceMsg("Socket Error=%d", SocketError(srvP->listenSocketP));
+ }
+ waitForNoConnections(outstandingConnListP);
+
+ destroyOutstandingConnList(outstandingConnListP);
+}
+
+
+
+void
+ServerRun(TServer * const serverP) {
+
+ struct _TServer * const srvP = serverP->srvP;
+
+ if (!srvP->socketBound)
+ TraceMsg("This server is not set up to accept connections "
+ "on its own, so you can't use ServerRun(). "
+ "Try ServerRunConn() or ServerInit()");
+ else
+ serverRun2(serverP);
+}
+
+
+
+static void
+serverRunConn(TServer * const serverP,
+ TSocket * const connectedSocketP) {
+/*----------------------------------------------------------------------------
+ Do the HTTP transaction on the TCP connection on the socket
+ 'connectedSocketP'.
+ (socket must be in connected state, with nothing having been read or
+ written on the connection yet).
+-----------------------------------------------------------------------------*/
+ struct _TServer * const srvP = serverP->srvP;
+
+ TConn * connectionP;
+ const char * error;
+
+ srvP->keepalivemaxconn = 1;
+
+ ConnCreate(&connectionP,
+ serverP, connectedSocketP,
+ &serverFunc, NULL, ABYSS_FOREGROUND, srvP->useSigchld,
+ &error);
+ if (error) {
+ TraceMsg("Couldn't create HTTP connection out of "
+ "connected socket. %s", error);
+ xmlrpc_strfree(error);
+ } else {
+ ConnProcess(connectionP);
+
+ ConnWaitAndRelease(connectionP);
+ }
+}
+
+
+
+void
+ServerRunConn2(TServer * const serverP,
+ TSocket * const connectedSocketP,
+ const char ** const errorP) {
+/*----------------------------------------------------------------------------
+ Do the HTTP transaction on the TCP connection on the socket
+ 'connectedOsSocket'.
+ (socket must be connected state, with nothing having been read or
+ written on the connection yet).
+-----------------------------------------------------------------------------*/
+ struct _TServer * const srvP = serverP->srvP;
+
+ if (srvP->serverAcceptsConnections)
+ xmlrpc_asprintf(errorP,
+ "This server is configured to accept connections on "
+ "its own socket. "
+ "Try ServerRun() or ServerCreateNoAccept().");
+ else {
+ serverRunConn(serverP, connectedSocketP);
+ *errorP = NULL;
+ }
+}
+
+
+
+void
+ServerRunConn(TServer * const serverP,
+ TOsSocket const connectedOsSocket) {
+
+ TSocket * socketP;
+ createSocketFromOsSocket(connectedOsSocket, &socketP);
+ if (!socketP)
+ TraceExit("Unable to use supplied socket");
+ else {
+ const char * error;
+
+ ServerRunConn2(serverP, socketP, &error);
+
+ if (error) {
+ TraceExit("Failed to run server on connection on file "
+ "descriptor %d. %s", connectedOsSocket, error);
+ xmlrpc_strfree(error);
+ }
+ SocketDestroy(socketP);
+ }
+}
+
+
+
+void
+ServerRunOnce(TServer * const serverP) {
+/*----------------------------------------------------------------------------
+ Accept a connection from the listening socket and do the HTTP
+ transaction that comes over it.
+
+ If no connection is presently waiting on the listening socket, wait
+ for one. But return immediately if we receive a signal during the
+ wait.
+-----------------------------------------------------------------------------*/
+ struct _TServer * const srvP = serverP->srvP;
+
+ if (!srvP->socketBound)
+ TraceMsg("This server is not set up to accept connections "
+ "on its own, so you can't use ServerRunOnce(). "
+ "Try ServerRunConn() or ServerInit()");
+ else {
+ abyss_bool connected;
+ abyss_bool failed;
+ TSocket * connectedSocketP;
+ TIPAddr remoteAddr;
+
+ srvP->keepalivemaxconn = 1;
+
+ SocketAccept(srvP->listenSocketP,
+ &connected, &failed,
+ &connectedSocketP, &remoteAddr);
+ if (connected) {
+ serverRunConn(serverP, connectedSocketP);
+ SocketDestroy(connectedSocketP);
+ } else if (failed)
+ TraceMsg("Socket Error=%d", SocketError(srvP->listenSocketP));
+ }
+}
+
+
+
+void
+ServerRunOnce2(TServer * const serverP,
+ enum abyss_foreback const foregroundBackground ATTR_UNUSED) {
+/*----------------------------------------------------------------------------
+ This is a backward compatibility interface to ServerRunOnce().
+
+ 'foregroundBackground' is meaningless. We always process the
+ connection in the foreground. The parameter exists because we once
+ thought we could do them in the background, but we really can't do
+ that in any clean way. If Caller wants background execution, he can
+ spin his own thread or process to call us. It makes much more sense
+ in Caller's context.
+-----------------------------------------------------------------------------*/
+ ServerRunOnce(serverP);
+}
+
+
+
+static void
+setGroups(void) {
+
+#ifdef HAVE_SETGROUPS
+ if (setgroups(0, NULL) == (-1))
+ TraceExit("Failed to setup the group.");
+#endif
+}
+
+
+
+void
+ServerDaemonize(TServer * const serverP) {
+/*----------------------------------------------------------------------------
+ Turn Caller into a daemon (i.e. fork a child, then exit; the child
+ returns to Caller).
+
+ NOTE: It's ridiculous, but conventional, for us to do this. It's
+ ridiculous because the task of daemonizing is not something
+ particular to Abyss. It ought to be done by a higher level. In
+ fact, it should be done before the Abyss server program is even
+ execed. The user should run a "daemonize" program that creates a
+ daemon which execs the Abyss server program.
+-----------------------------------------------------------------------------*/
+ struct _TServer * const srvP = serverP->srvP;
+
+#ifndef _WIN32
+ /* Become a daemon */
+ switch (fork()) {
+ case 0:
+ break;
+ case -1:
+ TraceExit("Unable to become a daemon");
+ default:
+ /* We are the parent */
+ exit(0);
+ }
+
+ setsid();
+
+ /* Change the current user if we are root */
+ if (getuid()==0) {
+ if (srvP->uid == (uid_t)-1)
+ TraceExit("Can't run under root privileges. "
+ "Please add a User option in your "
+ "Abyss configuration file.");
+
+ setGroups();
+
+ if (srvP->gid != (gid_t)-1)
+ if (setgid(srvP->gid)==(-1))
+ TraceExit("Failed to change the group.");
+
+ if (setuid(srvP->uid) == -1)
+ TraceExit("Failed to change the user.");
+ }
+
+ if (srvP->pidfile != -1) {
+ char z[16];
+
+ sprintf(z, "%d", getpid());
+ FileWrite(&srvP->pidfile, z, strlen(z));
+ FileClose(&srvP->pidfile);
+ }
+#endif /* _WIN32 */
+}
+
+
+
+void
+ServerAddHandler2(TServer * const serverP,
+ URIHandler2 * const handlerArgP,
+ abyss_bool * const successP) {
+
+ URIHandler2 * handlerP;
+
+ MALLOCVAR(handlerP);
+ if (handlerP == NULL)
+ *successP = FALSE;
+ else {
+ *handlerP = *handlerArgP;
+
+ if (handlerP->init == NULL)
+ *successP = TRUE;
+ else
+ handlerP->init(handlerP, successP);
+
+ if (*successP)
+ *successP = ListAdd(&serverP->srvP->handlers, handlerP);
+
+ if (!*successP)
+ free(handlerP);
+ }
+}
+
+
+
+static URIHandler2 *
+createHandler(URIHandler const function) {
+
+ URIHandler2 * handlerP;
+
+ MALLOCVAR(handlerP);
+ if (handlerP != NULL) {
+ handlerP->init = NULL;
+ handlerP->term = NULL;
+ handlerP->userdata = NULL;
+ handlerP->handleReq2 = NULL;
+ handlerP->handleReq1 = function;
+ }
+ return handlerP;
+}
+
+
+
+abyss_bool
+ServerAddHandler(TServer * const serverP,
+ URIHandler const function) {
+
+ URIHandler2 * handlerP;
+ abyss_bool success;
+
+ handlerP = createHandler(function);
+
+ if (handlerP == NULL)
+ success = FALSE;
+ else {
+ success = ListAdd(&serverP->srvP->handlers, handlerP);
+
+ if (!success)
+ free(handlerP);
+ }
+ return success;
+}
+
+
+
+void
+ServerDefaultHandler(TServer * const serverP,
+ URIHandler const handler) {
+
+ serverP->srvP->defaulthandler =
+ handler ? handler : ServerDefaultHandlerFunc;
+}
+
+
+
+void
+LogWrite(TServer * const serverP,
+ const char * const msg) {
+
+ struct _TServer * const srvP = serverP->srvP;
+
+ if (!srvP->logfileisopen && srvP->logfilename)
+ logOpen(srvP);
+
+ if (srvP->logfileisopen) {
+ abyss_bool success;
+ success = MutexLock(&srvP->logmutex);
+ if (success) {
+ const char * const lbr = "\n";
+ FileWrite(&srvP->logfile, msg, strlen(msg));
+ FileWrite(&srvP->logfile, lbr, strlen(lbr));
+
+ MutexUnlock(&srvP->logmutex);
+ }
+ }
+}
+/*******************************************************************************
+**
+** server.c
+**
+** This file is part of the ABYSS Web server project.
+**
+** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+** SUCH DAMAGE.
+**
+******************************************************************************/