Skip to content
Snippets Groups Projects
sws.c 23.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • /***************************************************************************
    
     *                                  _   _ ____  _
     *  Project                     ___| | | |  _ \| |
     *                             / __| | | | |_) | |
     *                            | (__| |_| |  _ <| |___
    
     * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
    
     * This software is licensed as described in the file COPYING, which
     * you should have received as part of this distribution. The terms
     * are also available at http://curl.haxx.se/docs/copyright.html.
    
     * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     * copies of the Software, and permit persons to whom the Software is
    
     * furnished to do so, under the terms of the COPYING file.
    
     *
     * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     * KIND, either express or implied.
     *
     * $Id$
    
     ***************************************************************************/
    
       This code was originally graciously donated to the project by Juergen
    
    #include "setup.h" /* portability help from the lib directory */
    
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include <signal.h>
    
    #include <sys/time.h>
    #include <sys/types.h>
    
    #include <ctype.h>
    
    
    #ifdef HAVE_UNISTD_H
    #include <unistd.h>
    #endif
    
    #include <sys/socket.h>
    
    #include <netinet/in.h>
    
    #ifdef _XOPEN_SOURCE_EXTENDED
    /* This define is "almost" required to build on HPUX 11 */
    
    #include <arpa/inet.h>
    
    #ifdef HAVE_NETDB_H
    
    #include <netdb.h>
    
    #define ENABLE_CURLX_PRINTF
    /* make the curlx header define all printf() functions to use the curlx_*
       versions instead */
    
    #include "curlx.h" /* from the private lib dir */
    
    #include "getpart.h"
    
    /* include memdebug.h last */
    #include "memdebug.h"
    
    
    #if !defined(CURL_SWS_FORK_ENABLED) && defined(HAVE_FORK)
    
    /*
     * The normal sws build for the plain standard curl test suite has no use for
     * fork(), but if you feel wild and crazy and want to setup some more exotic
     * tests. Define this and run...
     */
    
    #define REQBUFSIZ 150000
    #define REQBUFSIZ_TXT "149999"
    
    
    long prevtestno=-1; /* previous test number we served */
    long prevpartno=-1; /* previous part number we served */
    bool prevbounce;    /* instructs the server to increase the part number for
                           a test in case the identical testno+partno request
                           shows up again */
    
    
    #define RCMD_NORMALREQ 0 /* default request, use the tests file normally */
    #define RCMD_IDLE      1 /* told to sit idle */
    #define RCMD_STREAM    2 /* told to stream */
    
    
    struct httprequest {
      char reqbuf[REQBUFSIZ]; /* buffer area for the incoming request */
      int offset;     /* size of the incoming request */
    
      long testno;     /* test number found in the request */
    
      long partno;     /* part number found in the request */
    
      int open;       /* keep connection open info, as found in the request */
      bool auth_req;  /* authentication required, don't wait for body unless
                         there's an Authorization header */
      bool auth;      /* Authorization header present in the incoming request */
      size_t cl;      /* Content-Length of the incoming request */
      bool digest;    /* Authorization digest header found */
      bool ntlm;      /* Authorization ntlm header found */
    
    
      int rcmd;       /* doing a special command, see defines above */
    
    };
    
    int ProcessRequest(struct httprequest *req);
    
    void storerequest(char *reqbuf);
    
    
    #ifndef DEFAULT_LOGFILE
    
    #define DEFAULT_LOGFILE "log/sws.log"
    
    const char *serverlogfile = DEFAULT_LOGFILE;
    
    
    #define SWSVERSION "cURL test suite HTTP server/0.1"
    
    #define REQUEST_DUMP  "log/server.input"
    #define RESPONSE_DUMP "log/server.response"
    
    /* very-big-path support */
    #define MAXDOCNAMELEN 140000
    #define MAXDOCNAMELEN_TXT "139999"
    
    #define REQUEST_KEYWORD_SIZE 256
    
    #define CMD_AUTH_REQUIRED "auth_required"
    
    
    /* 'idle' means that it will accept the request fine but never respond
       any data. Just keep the connection alive. */
    #define CMD_IDLE "idle"
    
    /* 'stream' means to send a never-ending stream of data */
    #define CMD_STREAM "stream"
    
    
      DOCNUMBER_BADCONNECT = -5,
      DOCNUMBER_INTERNAL= -4,
      DOCNUMBER_CONNECT = -3,
      DOCNUMBER_WERULEZ = -2,
      DOCNUMBER_404     = -1
    };
    
    
    
    /* sent as reply to a QUIT */
    static const char *docquit =
    
    "HTTP/1.1 200 Goodbye" END_OF_HEADERS;
    
    /* sent as reply to a CONNECT */
    static const char *docconnect =
    
    "HTTP/1.1 200 Mighty fine indeed" END_OF_HEADERS;
    
    
    /* sent as reply to a "bad" CONNECT */
    static const char *docbadconnect =
    
    "HTTP/1.1 501 Forbidden you fool" END_OF_HEADERS;
    
    
    /* send back this on 404 file not found */
    
    static const char *doc404 = "HTTP/1.1 404 Not Found\r\n"
        "Server: " SWSVERSION "\r\n"
        "Connection: close\r\n"
        "Content-Type: text/html"
        END_OF_HEADERS
    
        "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
        "<HTML><HEAD>\n"
        "<TITLE>404 Not Found</TITLE>\n"
        "</HEAD><BODY>\n"
        "<H1>Not Found</H1>\n"
        "The requested URL was not found on this server.\n"
    
        "<P><HR><ADDRESS>" SWSVERSION "</ADDRESS>\n" "</BODY></HTML>\n";
    
    static volatile int sigpipe;  /* Why? It's not used */
    
    #endif
    
    static void sigpipe_handler(int sig)
    {
    
      (void)sig; /* prevent warning */
      sigpipe = 1;
    
    #endif
    
    int ProcessRequest(struct httprequest *req)
    {
      char *line=req->reqbuf;
      char chunked=FALSE;
      static char request[REQUEST_KEYWORD_SIZE];
      static char doc[MAXDOCNAMELEN];
      char logbuf[256];
      int prot_major, prot_minor;
    
      end = strstr(req->reqbuf, END_OF_HEADERS);
    
      /* try to figure out the request characteristics as soon as possible, but
         only once! */
      if((req->testno == DOCNUMBER_NOTHING) &&
         sscanf(line, "%" REQBUFSIZ_TXT"s %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
                request,
                doc,
                &prot_major,
                &prot_minor) == 4) {
        char *ptr;
    
        /* find the last slash */
        ptr = strrchr(doc, '/');
    
        /* get the number after it */
        if(ptr) {
          FILE *stream;
          char *filename;
    
          if((strlen(doc) + strlen(request)) < 200)
            sprintf(logbuf, "Got request: %s %s HTTP/%d.%d",
                    request, doc, prot_major, prot_minor);
          else
            sprintf(logbuf, "Got a *HUGE* request HTTP/%d.%d",
                    prot_major, prot_minor);
    
          if(!strncmp("/verifiedserver", ptr, 15)) {
            logmsg("Are-we-friendly question received");
            req->testno = DOCNUMBER_WERULEZ;
            return 1; /* done */
          }
    
          if(!strncmp("/quit", ptr, 15)) {
            logmsg("Request-to-quit received");
            req->testno = DOCNUMBER_QUIT;
            return 1; /* done */
          }
    
          ptr++; /* skip the slash */
    
    
          /* skip all non-numericals following the slash */
    
          while(*ptr && !isdigit((int)*ptr))
    
          req->testno = strtol(ptr, &ptr, 10);
    
          if(req->testno > 10000) {
            req->partno = req->testno % 10000;
            req->testno /= 10000;
          }
          else
            req->partno = 0;
    
    
          sprintf(logbuf, "Requested test number %ld part %ld",
    
    
          filename = test2file(req->testno);
    
          stream=fopen(filename, "rb");
          if(!stream) {
            logmsg("Couldn't open test file %d", req->testno);
    
            req->open = FALSE; /* closes connection */
    
          else {
    
            /* get the custom server control "commands" */
            cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize);
            fclose(stream);
    
            if(cmdsize) {
              logmsg("Found a reply-servercmd section!");
    
              if(!strncmp(CMD_AUTH_REQUIRED, cmd, strlen(CMD_AUTH_REQUIRED))) {
                logmsg("instructed to require authorization header");
                req->auth_req = TRUE;
              }
    
              else if(!strncmp(CMD_IDLE, cmd, strlen(CMD_IDLE))) {
                logmsg("instructed to idle");
                req->rcmd = RCMD_IDLE;
                req->open = TRUE;
              }
              else if(!strncmp(CMD_STREAM, cmd, strlen(CMD_STREAM))) {
                logmsg("instructed to stream");
                req->rcmd = RCMD_STREAM;
              }
    
            }
          }
        }
        else {
          if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
                    doc, &prot_major, &prot_minor) == 3) {
    
    Gisle Vanem's avatar
    Gisle Vanem committed
            sprintf(logbuf, "Received a CONNECT %s HTTP/%d.%d request",
    
    
            if(prot_major*10+prot_minor == 10)
              req->open = FALSE; /* HTTP 1.0 closes connection by default */
    
            if(!strncmp(doc, "bad", 3))
              /* if the host name starts with bad, we fake an error here */
              req->testno = DOCNUMBER_BADCONNECT;
            else if(!strncmp(doc, "test", 4)) {
    
              /* if the host name starts with test, the port number used in the
                 CONNECT line will be used as test number! */
    
              char *portp = strchr(doc, ':');
              if(portp)
                req->testno = atoi(portp+1);
    
              else
                req->testno = DOCNUMBER_CONNECT;
            }
            else
              req->testno = DOCNUMBER_CONNECT;
          }
          else {
            logmsg("Did not find test number in PATH");
            req->testno = DOCNUMBER_404;
          }
        }
      }
    
    
      if(!end)
        /* we don't have a complete request yet! */
        return 0;
    
    
      /* **** Persistancy ****
       *
       * If the request is a HTTP/1.0 one, we close the connection unconditionally
       * when we're done.
       *
       * If the request is a HTTP/1.1 one, we MUST check for a "Connection:"
       * header that might say "close". If it does, we close a connection when
       * this request is processed. Otherwise, we keep the connection alive for X
       * seconds.
       */
    
    
        if(!req->cl && curlx_strnequal("Content-Length:", line, 15)) {
    
          /* If we don't ignore content-length, we read it and we read the whole
             request including the body before we return. If we've been told to
             ignore the content-length, we will return as soon as all headers
             have been received */
          req->cl = strtol(line+15, &line, 10);
    
    
          logmsg("Found Content-Length: %d in the request", req->cl);
    
        else if(curlx_strnequal("Transfer-Encoding: chunked", line,
                                strlen("Transfer-Encoding: chunked"))) {
    
          /* chunked data coming in */
          chunked = TRUE;
        }
    
        if(chunked) {
    
          if(strstr(req->reqbuf, "\r\n0\r\n\r\n"))
    
            /* end of chunks reached */
            return 1; /* done */
          else
            return 0; /* not done */
        }
    
    
        line = strchr(line, '\n');
        if(line)
          line++;
      } while(line);
    
    
      if(!req->auth && strstr(req->reqbuf, "Authorization:")) {
        req->auth = TRUE; /* Authorization: header present! */
        if(req->auth_req)
          logmsg("Authorization header found, as required");
      }
    
      if(!req->digest && strstr(req->reqbuf, "Authorization: Digest")) {
        /* If the client is passing this Digest-header, we set the part number
           to 1000. Not only to spice up the complexity of this, but to make
           Digest stuff to work in the test suite. */
        req->partno += 1000;
        req->digest = TRUE; /* header found */
        logmsg("Received Digest request, sending back data %d", req->partno);
      }
      else if(!req->ntlm &&
              strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAD")) {
        /* If the client is passing this type-3 NTLM header */
        req->partno += 1002;
        req->ntlm = TRUE; /* NTLM found */
    
        logmsg("Received NTLM type-3, sending back data %d", req->partno);
    
        if(req->cl) {
          logmsg("  Expecting %d POSTed bytes", req->cl);
        }
    
      }
      else if(!req->ntlm &&
              strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAB")) {
        /* If the client is passing this type-1 NTLM header */
        req->partno += 1001;
        req->ntlm = TRUE; /* NTLM found */
        logmsg("Received NTLM type-1, sending back data %d", req->partno);
      }
      if(strstr(req->reqbuf, "Connection: close"))
        req->open = FALSE; /* close connection after this request */
    
      /* If authentication is required and no auth was provided, end now. This
         makes the server NOT wait for PUT/POST data and you can then make the
         test case send a rejection before any such data has been sent. Test case
         154 uses this.*/
      if(req->auth_req && !req->auth)
        return 1;
    
      if(req->cl) {
    
        if(req->cl <= strlen(end+strlen(END_OF_HEADERS)))
    
          return 1; /* done */
        else
          return 0; /* not complete yet */
      }
      return 1; /* done */
    }
    
    
    /* store the entire request in a file */
    void storerequest(char *reqbuf)
    {
      FILE *dump;
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      dump = fopen(REQUEST_DUMP, "ab"); /* b is for windows-preparing */
    
        size_t len = strlen(reqbuf);
        fwrite(reqbuf, 1, len, dump);
    
        logmsg("Wrote request (%d bytes) input to " REQUEST_DUMP,
               (int)len);
    
      }
      else {
        logmsg("Failed to write request input to " REQUEST_DUMP);
    
    /* return 0 on success, non-zero on failure */
    
    static int get_request(curl_socket_t sock, struct httprequest *req)
    
    {
      int fail= FALSE;
      char *reqbuf = req->reqbuf;
    
      /*** Init the httpreqest structure properly for the upcoming request ***/
      memset(req, 0, sizeof(struct httprequest));
    
      /* here's what should not be 0 from the start */
      req->testno = DOCNUMBER_NOTHING; /* safe default */
      req->open = TRUE; /* connection should remain open and wait for more
                           commands */
    
        ssize_t got = sread(sock, reqbuf + req->offset, REQBUFSIZ - req->offset);
    
        if (got <= 0) {
          if (got < 0) {
    
            logmsg("recv() returned error: %d", errno);
    
            return DOCNUMBER_INTERNAL;
    
          }
          logmsg("Connection closed by client");
    
          reqbuf[req->offset]=0;
    
          /* dump the request receivied so far to the external file */
          storerequest(reqbuf);
    
          return DOCNUMBER_INTERNAL;
    
        logmsg("Request buffer overflow, closing connection");
    
        fail = TRUE;
        /* dump the request to an external file anyway */
    
      /* dump the request to an external file */
      storerequest(reqbuf);
    
    static int send_doc(curl_socket_t sock, struct httprequest *req)
    
      size_t count;
    
      const char *buffer;
    
      char *ptr;
      FILE *stream;
    
      char *cmd=NULL;
    
      size_t cmdsize=0;
    
      static char weare[256];
    
    
      char partbuf[80]="data";
    
      logmsg("Send response number %d part %d", req->testno, req->partno);
    
      switch(req->rcmd) {
      default:
      case RCMD_NORMALREQ:
        break; /* continue with business as usual */
      case RCMD_STREAM:
    #define STREAMTHIS "a string to stream 01234567890\n"
        count = strlen(STREAMTHIS);
        while(1) {
          written = swrite(sock, STREAMTHIS, count);
    
          if(written != (ssize_t)count) {
    
            logmsg("Stopped streaming");
    
      case RCMD_IDLE:
        /* Do nothing. Sit idle. Pretend it rains. */
        return 0;
      }
    
      req->open = FALSE;
    
    
      if(req->testno < 0) {
        switch(req->testno) {
    
        case DOCNUMBER_QUIT:
          logmsg("Replying to QUIT");
          buffer = docquit;
          break;
    
        case DOCNUMBER_WERULEZ:
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          /* we got a "friends?" question, reply back that we sure are */
    
          logmsg("Identifying ourselves as friends");
    
          sprintf(weare, "HTTP/1.1 200 OK\r\n\r\nWE ROOLZ: %d\r\n",
    
          buffer = weare;
    
          break;
        case DOCNUMBER_INTERNAL:
          logmsg("Bailing out due to internal error");
          return -1;
        case DOCNUMBER_CONNECT:
          logmsg("Replying to CONNECT");
          buffer = docconnect;
          break;
        case DOCNUMBER_BADCONNECT:
          logmsg("Replying to a bad CONNECT");
          buffer = docbadconnect;
          break;
        case DOCNUMBER_404:
        default:
          logmsg("Replying to with a 404");
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          buffer = doc404;
    
        ptr = NULL;
        stream=NULL;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
        count = strlen(buffer);
    
        char *filename = test2file(req->testno);
    
          sprintf(partbuf, "data%ld", req->partno);
    
    
        stream=fopen(filename, "rb");
        if(!stream) {
          logmsg("Couldn't open test file");
          return 0;
        }
    
          buffer = spitout(stream, "reply", partbuf, &count);
          ptr = (char *)buffer;
    
          fclose(stream);
    
        /* re-open the same file again */
        stream=fopen(filename, "rb");
        if(!stream) {
          logmsg("Couldn't open test file");
          return 0;
        }
    
        else {
    
          /* get the custom server control "commands" */
    
          cmd = (char *)spitout(stream, "reply", "postcmd", &cmdsize);
    
          fclose(stream);
        }
    
      dump = fopen(RESPONSE_DUMP, "ab"); /* b is for windows-preparing */
      if(!dump) {
        logmsg("couldn't create logfile: " RESPONSE_DUMP);
        return -1;
      }
    
    
      /* If the word 'swsclose' is present anywhere in the reply chunk, the
         connection will be closed after the data has been sent to the requesting
         client... */
      if(strstr(buffer, "swsclose") || !count) {
        persistant = FALSE;
    
        logmsg("connection close instruction \"swsclose\" found in response");
    
      if(strstr(buffer, "swsbounce")) {
        prevbounce = TRUE;
        logmsg("enable \"swsbounce\" in the next request");
      }
      else
        prevbounce = FALSE;
    
        written = swrite(sock, buffer, count);
    
          logmsg("Sending response failed and we bailed out!");
    
        /* write to file as well */
        fwrite(buffer, 1, written, dump);
    
    
        count -= written;
        buffer += written;
      } while(count>0);
    
    
      logmsg("Response sent (%d bytes) and written to " RESPONSE_DUMP,
             responsesize);
    
    
      if(cmdsize > 0 ) {
        char command[32];
        int num;
    
        do {
          if(2 == sscanf(ptr, "%31s %d", command, &num)) {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
            if(!strcmp("wait", command)) {
              logmsg("Told to sleep for %d seconds", num);
    
              sleep(num); /* wait this many seconds */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
            }
    
              logmsg("Unknown command in reply command section");
          }
          ptr = strchr(ptr, '\n');
          if(ptr)
            ptr++;
          else
            ptr = NULL;
        } while(ptr && *ptr);
      }
      if(cmd)
        free(cmd);
    
      prevtestno = req->testno;
      prevpartno = req->partno;
    
    
    char use_ipv6=FALSE;
    
    
    int main(int argc, char *argv[])
    {
    
      struct sockaddr_in me;
    
    #ifdef ENABLE_IPV6
      struct sockaddr_in6 me6;
    #endif /* ENABLE_IPV6 */
    
      curl_socket_t sock, msgsock;
      int flag;
    
      unsigned short port = DEFAULT_PORT;
    
      char *pidname= (char *)".http.pid";
    
    #ifdef CURL_SWS_FORK_ENABLED
      bool use_fork = FALSE;
    #endif
    
        if(!strcmp("--version", argv[arg])) {
    
          printf("sws IPv4%s"
    #ifdef CURL_SWS_FORK_ENABLED
                 " FORK"
    #endif
                 "\n"
                 ,
    
    #ifdef ENABLE_IPV6
                 "/IPv6"
    #else
                 ""
    #endif
                 );
          return 0;
        }
    
        else if(!strcmp("--pidfile", argv[arg])) {
          arg++;
          if(argc>arg)
            pidname = argv[arg++];
        }
        else if(!strcmp("--ipv6", argv[arg])) {
    
    #ifdef ENABLE_IPV6
          use_ipv6=TRUE;
    #endif
          arg++;
        }
    
    #ifdef CURL_SWS_FORK_ENABLED
        else if(!strcmp("--fork", argv[arg])) {
          use_fork=TRUE;
          arg++;
        }
    #endif
    
    
          if(atoi(argv[arg]))
            port = (unsigned short)atoi(argv[arg++]);
    
          if(argc>arg)
    
    #if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
      win32_init();
    
    #ifdef HAVE_SIGNAL
    
      signal(SIGPIPE, sigpipe_handler);
    
      siginterrupt(SIGPIPE, 1);
    
    #ifdef ENABLE_IPV6
      if(!use_ipv6)
    #endif
        sock = socket(AF_INET, SOCK_STREAM, 0);
    #ifdef ENABLE_IPV6
      else
        sock = socket(AF_INET6, SOCK_STREAM, 0);
    #endif
    
    
      if (CURL_SOCKET_BAD == sock) {
    
        logmsg("Error opening socket: %d", errno);
    
      if (0 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 
                (void *) &flag, sizeof(flag))) {
        logmsg("setsockopt(SO_REUSEADDR) failed: %d", errno);
        sclose(sock);
        exit(1);
    
    #ifdef ENABLE_IPV6
      if(!use_ipv6) {
    #endif
    
        memset(&me, 0, sizeof(me));
    
        me.sin_family = AF_INET;
        me.sin_addr.s_addr = INADDR_ANY;
        me.sin_port = htons(port);
        rc = bind(sock, (struct sockaddr *) &me, sizeof(me));
    #ifdef ENABLE_IPV6
      }
      else {
    
        memset(&me6, 0, sizeof(me6));
    
        me6.sin6_family = AF_INET6;
        me6.sin6_addr = in6addr_any;
        me6.sin6_port = htons(port);
        rc = bind(sock, (struct sockaddr *) &me6, sizeof(me6));
      }
    #endif /* ENABLE_IPV6 */
    
      if(0 != rc) {
    
        logmsg("Error binding socket: %d", errno);
    
        sclose(sock);
    
      if(pidfile) {
        fprintf(pidfile, "%d\n", (int)getpid());
        fclose(pidfile);
      }
    
      else {
    
        fprintf(stderr, "Couldn't write pid file\n");
    
      logmsg("Running IPv%d version on port %d",
    
    #ifdef ENABLE_IPV6
             (use_ipv6?6:4)
    #else
             4
    #endif
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
             , port );
    
      /* start accepting connections */
    
      rc = listen(sock, 5);
      if(0 != rc) {
        logmsg("listen() failed with error: %d", errno);
        sclose(sock);
        exit(1);
      }
    
      while (1) {
    
        msgsock = accept(sock, NULL, NULL);
    
        if (CURL_SOCKET_BAD == msgsock) {
          printf("MAJOR ERROR: accept() failed with error: &d\n", errno);
    
        if(use_fork) {
          /* The fork enabled version just forks off the child and don't care
             about it anymore, so don't assume otherwise. Beware and don't do
             this at home. */
          rc = fork();
          if(-1 == rc) {
            printf("MAJOR ERROR: fork() failed!\n");
            break;
          }
    
        else
          /* not a fork, just set rc so the following proceeds nicely */
          rc = 0;
    
        /* 0 is returned to the child */
        if(0 == rc) {
    #endif
    
        logmsg("====> Client connect");
    
          if(get_request(msgsock, &req))
            /* non-zero means error, break out of loop */
            break;
    
          if(prevbounce) {
            /* bounce treatment requested */
            if((req.testno == prevtestno) &&
               (req.partno == prevpartno)) {
              req.partno++;
              logmsg("BOUNCE part number to %ld", req.partno);
            }
          }
    
    
          if((req.testno < 0) && (req.testno != DOCNUMBER_CONNECT)) {
    
            logmsg("special request received, no persistancy");
            break;
          }
    
            logmsg("instructed to close connection after server-reply");
            break;
          }
    
            logmsg("=> persistant connection request ended, awaits new request");
    
          /* if we got a CONNECT, loop and get another request as well! */
    
        } while(req.open || (req.testno == DOCNUMBER_CONNECT));
    
        logmsg("====> Client disconnect");
    
    #ifdef CURL_SWS_FORK_ENABLED
        }
    #endif