/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2004, Daniel Stenberg, , 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: sws.c,v 1.57 2004/10/07 22:57:24 bagder Exp $ ***************************************************************************/ /* sws.c: simple (silly?) web server This code was originally graciously donated to the project by Juergen Wilke. Thanks a bunch! */ #include "setup.h" /* portability help from the lib directory */ #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #ifdef _XOPEN_SOURCE_EXTENDED /* This define is "almost" required to build on HPUX 11 */ #include #endif #ifdef HAVE_NETDB_H #include #endif #include "curlx.h" /* from the private lib dir */ #include "getpart.h" #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif #if defined( WIN32 ) && !defined( __CYGWIN__ ) #include #include #include #define sleep( sec ) Sleep( ( sec ) * 1000 ) #define EINPROGRESS WSAEINPROGRESS #define EWOULDBLOCK WSAEWOULDBLOCK #define EISCONN WSAEISCONN #define ENOTSOCK WSAENOTSOCK #define ECONNREFUSED WSAECONNREFUSED static void win32_cleanup( void ); #endif #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 */ 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 ProcessRequest( struct httprequest *req ); void storerequest( char *reqbuf ); #define DEFAULT_PORT 8999 #ifndef DEFAULT_LOGFILE #define DEFAULT_LOGFILE "log/sws.log" #endif #define SWSVERSION "cURL test suite HTTP server/0.1" #define REQUEST_DUMP "log/server.input" #define RESPONSE_DUMP "log/server.response" #define TEST_DATA_PATH "%s/data/test%ld" /* very-big-path support */ #define MAXDOCNAMELEN 140000 #define MAXDOCNAMELEN_TXT "139999" #define REQUEST_KEYWORD_SIZE 256 #define CMD_AUTH_REQUIRED "auth_required" #define END_OF_HEADERS "\r\n\r\n" /* global variable, where to find the 'data' dir */ const char *path = "."; enum { DOCNUMBER_NOTHING = -7, DOCNUMBER_QUIT = -6, 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 "\n" "\n" "404 Not Found\n" "\n" "

Not Found

\n" "The requested URL was not found on this server.\n" "


" SWSVERSION "
\n" "\n"; #ifdef SIGPIPE static volatile int sigpipe; /* Why? It's not used */ #endif static void logmsg( const char *msg, ... ) { time_t t = time( NULL ); va_list ap; struct tm *curr_time = localtime( &t ); char buffer[256]; /* possible overflow if you pass in a huge string */ FILE *logfp; va_start( ap, msg ); vsprintf( buffer, msg, ap ); va_end( ap ); logfp = fopen( DEFAULT_LOGFILE, "a" ); fprintf( logfp ? logfp : stderr, /* write to stderr if the logfile doesn't open */ "%02d:%02d:%02d %s\n", curr_time->tm_hour, curr_time->tm_min, curr_time->tm_sec, buffer ); if ( logfp ) { fclose( logfp ); } } #ifdef SIGPIPE static void sigpipe_handler( int sig ) { (void)sig; /* prevent warning */ sigpipe = 1; } #endif #if defined( WIN32 ) && !defined( __CYGWIN__ ) #undef perror #define perror( m ) win32_perror( m ) static void win32_perror( const char *msg ) { char buf[256]; DWORD err = WSAGetLastError(); if ( !FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, LANG_NEUTRAL, buf, sizeof( buf ), NULL ) ) { snprintf( buf, sizeof( buf ), "Unknown error %lu (%#lx)", err, err ); } if ( msg ) { fprintf( stderr, "%s: ", msg ); } fprintf( stderr, "%s\n", buf ); } #endif static char *test2file( long testno ) { static char filename[256]; sprintf( filename, TEST_DATA_PATH, path, testno ); return filename; } 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; char *end; 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 ); } logmsg( logbuf ); 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 */ 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", req->testno, req->partno ); logmsg( logbuf ); filename = test2file( req->testno ); stream = fopen( filename, "rb" ); if ( !stream ) { logmsg( "Couldn't open test file %d", req->testno ); return 0; } else { char *cmd = NULL; size_t cmdsize = 0; /* 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; } free( cmd ); } } } else { if ( sscanf( req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d", doc, &prot_major, &prot_minor ) == 3 ) { sprintf( logbuf, "Receiced a CONNECT %s HTTP/%d.%d request", doc, prot_major, prot_minor ); logmsg( logbuf ); 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 ) ) { char *ptr = strchr( doc, ':' ); if ( ptr ) { req->testno = atoi( ptr + 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. */ do { 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-Legth: %d in the request", req->cl ); break; } 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" ) ) { /* 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 ); } 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 ( req->cl && ( req->auth || !req->auth_req ) ) { 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; dump = fopen( REQUEST_DUMP, "ab" ); /* b is for windows-preparing */ if ( dump ) { size_t len = strlen( reqbuf ); fwrite( reqbuf, 1, len, dump ); fclose( 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( int 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 */ /*** end of httprequest init ***/ while ( req->offset < REQBUFSIZ ) { int got = sread( sock, reqbuf + req->offset, REQBUFSIZ - req->offset ); if ( got <= 0 ) { if ( got < 0 ) { perror( "recv" ); logmsg( "recv() returned error" ); 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; } req->offset += got; reqbuf[req->offset] = 0; if ( ProcessRequest( req ) ) { break; } } if ( req->offset >= REQBUFSIZ ) { logmsg( "Request buffer overflow, closing connection" ); reqbuf[REQBUFSIZ - 1] = 0; fail = TRUE; /* dump the request to an external file anyway */ } else { reqbuf[req->offset] = 0; } /* dump the request to an external file */ storerequest( reqbuf ); return fail; /* success */ } /* returns -1 on failure */ static int send_doc( int sock, struct httprequest *req ) { int written; size_t count; const char *buffer; char *ptr; FILE *stream; char *cmd = NULL; size_t cmdsize = 0; FILE *dump; int persistant = TRUE; size_t responsesize; static char weare[256]; char partbuf[80] = "data"; req->open = FALSE; logmsg( "Send response number %d part %d", req->testno, req->partno ); if ( req->testno < 0 ) { switch ( req->testno ) { case DOCNUMBER_QUIT: logmsg( "Replying to QUIT" ); buffer = docquit; break; case DOCNUMBER_WERULEZ: /* 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", (int)getpid() ); 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" ); buffer = doc404; break; } ptr = NULL; stream = NULL; count = strlen( buffer ); } else { char *filename = test2file( req->testno ); if ( 0 != req->partno ) { sprintf( partbuf, "data%ld", req->partno ); } stream = fopen( filename, "rb" ); if ( !stream ) { logmsg( "Couldn't open test file" ); return 0; } else { 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; } responsesize = count; do { written = swrite( sock, buffer, count ); if ( written < 0 ) { logmsg( "Sending response failed and we bailed out!" ); return -1; } /* write to file as well */ fwrite( buffer, 1, written, dump ); count -= written; buffer += written; } while ( count > 0 ); fclose( dump ); logmsg( "Response sent (%d bytes) and written to " RESPONSE_DUMP, responsesize ); if ( ptr ) { free( ptr ); } if ( cmdsize > 0 ) { char command[32]; int num; char *ptr = cmd; do { if ( 2 == sscanf( ptr, "%31s %d", command, &num ) ) { if ( !strcmp( "wait", command ) ) { logmsg( "Told to sleep for %d seconds", num ); sleep( num ); /* wait this many seconds */ } else { logmsg( "Unknown command in reply command section" ); } } ptr = strchr( ptr, '\n' ); if ( ptr ) { ptr++; } else { ptr = NULL; } } while ( ptr && *ptr ); } if ( cmd ) { free( cmd ); } req->open = persistant; prevtestno = req->testno; prevpartno = req->partno; return 0; } #if defined( WIN32 ) && !defined( __CYGWIN__ ) static void win32_init( void ) { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 0 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { perror( "Winsock init failed" ); logmsg( "Error initialising winsock -- aborting\n" ); exit( 1 ); } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 0 ) { WSACleanup(); perror( "Winsock init failed" ); logmsg( "No suitable winsock.dll found -- aborting\n" ); exit( 1 ); } } static void win32_cleanup( void ) { WSACleanup(); } #endif int main( int argc, char *argv[] ) { struct sockaddr_in me; int sock, msgsock, flag; unsigned short port = DEFAULT_PORT; FILE *pidfile; struct httprequest req; if ( argc > 1 ) { port = (unsigned short)atoi( argv[1] ); if ( argc > 2 ) { path = argv[2]; } } #if defined( WIN32 ) && !defined( __GNUC__ ) || defined( __MINGW32__ ) win32_init(); atexit( win32_cleanup ); #else #ifdef SIGPIPE #ifdef HAVE_SIGNAL signal( SIGPIPE, sigpipe_handler ); #endif #ifdef HAVE_SIGINTERRUPT siginterrupt( SIGPIPE, 1 ); #endif #endif #endif sock = socket( AF_INET, SOCK_STREAM, 0 ); if ( sock < 0 ) { perror( "opening stream socket" ); logmsg( "Error opening socket -- aborting\n" ); exit( 1 ); } flag = 1; if ( setsockopt ( sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &flag, sizeof( int ) ) < 0 ) { perror( "setsockopt(SO_REUSEADDR)" ); } me.sin_family = AF_INET; me.sin_addr.s_addr = INADDR_ANY; me.sin_port = htons( port ); if ( bind( sock, (struct sockaddr *) &me, sizeof me ) < 0 ) { perror( "binding stream socket" ); logmsg( "Error binding socket -- aborting\n" ); exit( 1 ); } pidfile = fopen( ".http.pid", "w" ); if ( pidfile ) { fprintf( pidfile, "%d\n", (int)getpid() ); fclose( pidfile ); } else { fprintf( stderr, "Couldn't write pid file\n" ); } /* start accepting connections */ listen( sock, 5 ); while ( 1 ) { msgsock = accept( sock, NULL, NULL ); if ( msgsock == -1 ) { continue; } logmsg( "====> Client connect" ); do { 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 ); } } send_doc( msgsock, &req ); if ( ( req.testno < 0 ) && ( req.testno != DOCNUMBER_CONNECT ) ) { logmsg( "special request received, no persistancy" ); break; } if ( !req.open ) { logmsg( "instructed to close connection after server-reply" ); break; } if ( req.open ) { logmsg( "persistant connection, 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" ); sclose( msgsock ); if ( req.testno == DOCNUMBER_QUIT ) { break; } } sclose( sock ); return 0; }