initial load of upstream version 1.06.32
[xmlrpc-c] / lib / abyss / src / server.c
diff --git a/lib/abyss/src/server.c b/lib/abyss/src/server.c
new file mode 100644 (file)
index 0000000..8e5af56
--- /dev/null
@@ -0,0 +1,1794 @@
+/* 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.
+**
+******************************************************************************/