/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2006, 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: 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 #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 #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 "util.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 CURL_SWS_FORK_ENABLED #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 */ #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 checkindex; /* where to start checking of the 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 pipe; /* if non-zero, expect this many requests to do a "piped" request/response */ int rcmd; /* doing a special command, see defines above */ }; int ProcessRequest(struct httprequest *req); void storerequest(char *reqbuf); #define DEFAULT_PORT 8999 #ifndef DEFAULT_LOGFILE #define DEFAULT_LOGFILE "log/sws.log" #endif 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" #define END_OF_HEADERS "\r\n\r\n" 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 #ifdef SIGPIPE static void sigpipe_handler(int sig) { (void)sig; /* prevent warning */ sigpipe = 1; } #endif int ProcessRequest(struct httprequest *req) { char *line=&req->reqbuf[req->checkindex]; 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(line, END_OF_HEADERS); logmsg("ProcessRequest() called"); /* 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("%s", 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 */ /* skip all non-numericals following the slash */ while(*ptr && !isdigit((int)*ptr)) 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", req->testno, req->partno); logmsg("%s", logbuf); filename = test2file(req->testno); stream=fopen(filename, "rb"); if(!stream) { logmsg("Couldn't open test file %d", req->testno); req->open = FALSE; /* closes connection */ return 1; /* done */ } else { char *cmd = NULL; size_t cmdsize = 0; int num=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; } 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(1 == sscanf(cmd, "pipe: %d", &num)) { logmsg("instructed to allow a pipe size %d", num); req->pipe = num-1; /* decrease by one since we don't count the first request in this number */ } free(cmd); } } } else { if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d", doc, &prot_major, &prot_minor) == 3) { sprintf(logbuf, "Received a CONNECT %s HTTP/%d.%d request", doc, prot_major, prot_minor); logmsg("%s", 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)) { /* 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! */ logmsg("ProcessRequest returned without a complete request"); return 0; } logmsg("ProcessRequest found a complete request"); if(req->pipe) /* we do have a full set, advance the checkindex to after the end of the headers, for the pipelining case mostly */ req->checkindex += (end - line) + strlen(END_OF_HEADERS); /* **** 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-Length: %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\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 */ while(req->pipe) { /* scan for more header ends within this chunk */ line = &req->reqbuf[req->checkindex]; end = strstr(line, END_OF_HEADERS); if(!end) break; req->checkindex += (end - line) + strlen(END_OF_HEADERS); req->pipe--; } /* 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; 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(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 */ req->pipe = 0; /*** end of httprequest init ***/ while (req->offset < REQBUFSIZ) { 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("Read %d bytes", got); req->offset += got; reqbuf[req->offset] = 0; if(ProcessRequest(req)) { if(req->pipe--) { logmsg("Waiting for another piped request"); continue; } 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(curl_socket_t sock, struct httprequest *req) { ssize_t 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"; 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"); break; } } return -1; 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: /* 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; 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; } 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; FILE *pidfile; char *pidname= (char *)".http.pid"; struct httprequest req; int rc; int arg=1; #ifdef CURL_SWS_FORK_ENABLED bool use_fork = FALSE; #endif while(argc>arg) { 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 else if(argc>arg) { if(atoi(argv[arg])) port = (unsigned short)atoi(argv[arg++]); if(argc>arg) path = argv[arg++]; } } #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 #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); return 1; } flag = 1; if (0 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &flag, sizeof(flag))) { logmsg("setsockopt(SO_REUSEADDR) failed: %d", errno); sclose(sock); return 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); return 1; } pidfile = fopen(pidname, "w"); if(pidfile) { fprintf(pidfile, "%d\n", (int)getpid()); fclose(pidfile); } else { fprintf(stderr, "Couldn't write pid file\n"); sclose(sock); return 1; } logmsg("Running IPv%d version on port %d", #ifdef ENABLE_IPV6 (use_ipv6?6:4) #else 4 #endif , port ); /* start accepting connections */ rc = listen(sock, 5); if(0 != rc) { logmsg("listen() failed with error: %d", errno); sclose(sock); return 1; } while (1) { msgsock = accept(sock, NULL, NULL); if (CURL_SOCKET_BAD == msgsock) { printf("MAJOR ERROR: accept() failed with error: &d\n", errno); break; } #ifdef CURL_SWS_FORK_ENABLED 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"); 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 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"); sclose(msgsock); if (req.testno == DOCNUMBER_QUIT) break; #ifdef CURL_SWS_FORK_ENABLED } #endif } sclose(sock); return 0; }