diff options
author | Daniel Stenberg <daniel@haxx.se> | 2005-04-18 06:57:44 +0000 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2005-04-18 06:57:44 +0000 |
commit | 14424f7058cd6c2a5aa31dc179db3d4b7bc564a0 (patch) | |
tree | 96893b10e2c340f26f7b662a3b9db342191807eb /tests/server | |
parent | 6063dff8d02adfba758d9f252c5704535d0d2faa (diff) |
Modified the FTP server to use the new 'sockfilt' program to do all the socket
level stuff. The FTP server communicates with sockfilt using perl's open2().
This enables easier IPv6 support and hopefully FTP-SSL support in the future.
Added four test cases for FTP-ipv6.
Diffstat (limited to 'tests/server')
-rw-r--r-- | tests/server/Makefile.am | 16 | ||||
-rw-r--r-- | tests/server/getpart.c | 16 | ||||
-rw-r--r-- | tests/server/sockfilt.c | 668 | ||||
-rw-r--r-- | tests/server/testpart.c | 52 |
4 files changed, 727 insertions, 25 deletions
diff --git a/tests/server/Makefile.am b/tests/server/Makefile.am index 565441caa..07727101a 100644 --- a/tests/server/Makefile.am +++ b/tests/server/Makefile.am @@ -25,16 +25,14 @@ AUTOMAKE_OPTIONS = foreign INCLUDES = -I$(top_srcdir)/lib -I$(top_srcdir)/include -noinst_PROGRAMS = sws getpart +noinst_PROGRAMS = sws getpart sockfilt -sws_SOURCES= sws.c getpart.c getpart.h $(top_srcdir)/lib/strequal.c \ - $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c \ +useful = getpart.c getpart.h $(top_srcdir)/lib/strequal.c \ + $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c \ $(top_srcdir)/lib/memdebug.c -extra_DIST = base64.pl - -getpart_CPPFLAGS = -DGETPART_TEST +sws_SOURCES= sws.c $(useful) +sockfilt_SOURCES = sockfilt.c $(useful) $(top_srcdir)/lib/inet_pton.c +getpart_SOURCES= testpart.c $(useful) -getpart_SOURCES= getpart.c getpart.h $(top_srcdir)/lib/strequal.c \ - $(top_srcdir)/lib/base64.c $(top_srcdir)/lib/mprintf.c \ - $(top_srcdir)/lib/memdebug.c +extra_DIST = base64.pl diff --git a/tests/server/getpart.c b/tests/server/getpart.c index c493f252e..85bf3ed7b 100644 --- a/tests/server/getpart.c +++ b/tests/server/getpart.c @@ -218,19 +218,3 @@ const char *spitout(FILE *stream, return string; } -#ifdef GETPART_TEST -int main(int argc, char **argv) -{ - if(argc< 3) { - printf("./moo main sub\n"); - } - else { - size_t size; - unsigned int i; - const char *buffer = spitout(stdin, argv[1], argv[2], &size); - for(i=0; i< size; i++) - printf("%c", buffer[i]); - } - return 0; -} -#endif diff --git a/tests/server/sockfilt.c b/tests/server/sockfilt.c new file mode 100644 index 000000000..07c81b11d --- /dev/null +++ b/tests/server/sockfilt.c @@ -0,0 +1,668 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2005, 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$ + ***************************************************************************/ + +/* Purpose + * + * 1. Accept a TCP connection on a custom port (ipv4 or ipv6). + * + * 2. Get commands on STDIN. Pass data on to the TCP stream. + * Get data from TCP stream and pass on to STDOUT. + * + * This program is made to perform all the socket/stream/connection stuff for + * the test suite's (perl) FTP server. Previously the perl code did all of + * this by its own, but I decided to let this program do the socket layer + * because of several things: + * + * o We want the perl code to work with rather old perl installations, thus + * we cannot use recent perl modules or features. + * + * o We want IPv6 support for systems that provide it, and doing optional IPv6 + * support in perl seems if not impossible so at least awkward. + * + * o We want FTP-SSL support, which means that a connection that starts with + * plain sockets needs to be able to "go SSL" in the midst. This would also + * require some nasty perl stuff I'd rather avoid. + * + * (Source originally based on sws.c) + */ +#include "setup.h" /* portability help from the lib directory */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <signal.h> +#include <time.h> +#include <ctype.h> +#include <sys/time.h> +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef _XOPEN_SOURCE_EXTENDED +/* This define is "almost" required to build on HPUX 11 */ +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#include "curlx.h" /* from the private lib dir */ +#include "getpart.h" +#include "inet_pton.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +#if defined(WIN32) && !defined(__CYGWIN__) +#include <windows.h> +#include <winsock2.h> +#include <process.h> + +#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); + +#if defined(ENABLE_IPV6) && defined(__MINGW32__) +const struct in6_addr in6addr_any = {{ IN6ADDR_ANY_INIT }}; +#endif +#endif + +/* include memdebug.h last */ +#include "memdebug.h" + +#define DEFAULT_PORT 8999 + +#ifndef DEFAULT_LOGFILE +#define DEFAULT_LOGFILE "log/sockfilt.log" +#endif + +#ifdef SIGPIPE +static volatile int sigpipe; /* Why? It's not used */ +#endif + +char *socklogfile = (char *)DEFAULT_LOGFILE; + +/* + * ourerrno() returns the errno (or equivalent) on this platform to + * hide platform specific for the function that calls this. + */ +static int ourerrno(void) +{ +#ifdef WIN32 + return (int)GetLastError(); +#else + return errno; +#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(socklogfile, "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); +} + +static void lograw(unsigned char *buffer, int len) +{ + char data[120]; + int i; + unsigned char *ptr = buffer; + char *optr = data; + int width=0; + + for(i=0; i<len; i++) { + sprintf(optr, "%c", + (isgraph(ptr[i]) || ptr[i]==0x20) ?ptr[i]:'.'); + optr += 1; + width += 1; + + if(width>60) { + logmsg("RAW: '%s'", data); + width = 0; + optr = data; + } + } + if(width) + logmsg("RAW: '%s'", data); +} + +#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 + +#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 + +char use_ipv6=FALSE; +unsigned short port = DEFAULT_PORT; +unsigned short connectport = 0; /* if non-zero, we activate this mode */ + +enum sockmode { + PASSIVE_LISTEN, /* as a server waiting for connections */ + PASSIVE_CONNECT, /* as a server, connected to a client */ + ACTIVE /* as a client, connected to a server */ +}; + +/* + sockfdp is a pointer to an established stream or CURL_SOCKET_BAD + + if sockfd is CURL_SOCKET_BAD, listendfd is a listening socket we must + accept() +*/ +static int juggle(curl_socket_t *sockfdp, + curl_socket_t listenfd, + enum sockmode *mode) +{ + struct timeval timeout; + fd_set fds_read; + fd_set fds_write; + fd_set fds_err; + curl_socket_t maxfd; + int r; + unsigned char buffer[256]; /* FIX: bigger buffer */ + char data[256]; + int sockfd; + + timeout.tv_sec = 120; + timeout.tv_usec = 0; + + FD_ZERO(&fds_read); + FD_ZERO(&fds_write); + FD_ZERO(&fds_err); + + FD_SET(fileno(stdin), &fds_read); + + switch(*mode) { + case PASSIVE_LISTEN: + /* server mode */ + sockfd = listenfd; + logmsg("waiting for a client to connect on socket %d", (int)sockfd); + /* there's always a socket to wait for */ + FD_SET(sockfd, &fds_read); + maxfd = sockfd; + break; + + case PASSIVE_CONNECT: + sockfd = *sockfdp; + logmsg("waiting for data from client on socket %d", (int)sockfd); + /* there's always a socket to wait for */ + FD_SET(sockfd, &fds_read); + maxfd = sockfd; + break; + + case ACTIVE: + sockfd = *sockfdp; + + /* sockfd turns CURL_SOCKET_BAD when our connection has been closed */ + if(sockfd != CURL_SOCKET_BAD) { + FD_SET(sockfd, &fds_read); + maxfd = sockfd; + logmsg("waiting for data from client on socket %d", (int)sockfd); + } + else { + logmsg("No socket to read on"); + maxfd = 0; + } + break; + } + + do { + r = select(maxfd + 1, &fds_read, &fds_write, &fds_err, &timeout); + } while((r == -1) && (ourerrno() == EINTR)); + + logmsg("select() returned %d", r); + + switch(r) { + case -1: + return FALSE; + + case 0: /* timeout! */ + return TRUE; + } + + + if(FD_ISSET(fileno(stdin), &fds_read)) { + size_t nread; + logmsg("data on stdin"); + /* read from stdin, commands/data to be dealt with and possibly passed on + to the socket + + protocol: + + 4 letter command + LF [mandatory] + + 4-digit hexadecimal data length + LF [if the command takes data] + data [the data being as long as set above] + + Commands: + + DATA - plain pass-thru data + */ + nread = read(fileno(stdin), buffer, 5); + if(5 == nread) { + + logmsg("Received command %c%c%c%c", + buffer[0], buffer[1], buffer[2], buffer[3] ); + + if(!memcmp("PING", buffer, 4)) { + /* send reply on stdout, just proving we are alive */ + write(fileno(stdout), "PONG\n", 5); + } + + else if(!memcmp("PORT", buffer, 4)) { + /* question asking us what PORT number we are listening to. + Replies with PORT with "IPv[num]/[port]" */ + sprintf((char *)buffer, "IPv%d/%d\n", use_ipv6?6:4, port); + r = strlen((char *)buffer); + sprintf(data, "PORT\n%04x\n", r); + write(fileno(stdout), data, 10); + write(fileno(stdout), buffer, r); + } + else if(!memcmp("QUIT", buffer, 4)) { + /* just die */ + logmsg("quits"); + exit(0); + } + else if(!memcmp("DATA", buffer, 4)) { + /* data IN => data OUT */ + long len; + + if(5 != read(fileno(stdin), buffer, 5)) + return FALSE; + + len = strtol((char *)buffer, NULL, 16); + if(len != read(fileno(stdin), buffer, len)) + return FALSE; + + logmsg("> %d bytes data, server => client", len); + lograw(buffer, len); + + if(*mode == PASSIVE_LISTEN) { + logmsg(".., but we are disconnected!"); + write(fileno(stdout), "DISC\n", 5); + } + else + /* send away on the socket */ + swrite(sockfd, buffer, len); + } + else if(!memcmp("DISC", buffer, 4)) { + /* disconnect! */ + write(fileno(stdout), "DISC\n", 5); + if(sockfd != CURL_SOCKET_BAD) { + logmsg("====> Client forcibly disconnected"); + sclose(sockfd); + *sockfdp = CURL_SOCKET_BAD; + } + else + logmsg("attempt to close already dead connection"); + return TRUE; + } + } + else { + logmsg("read %d from stdin, exiting", (int)nread); + exit(0); + } + } + + if((sockfd != CURL_SOCKET_BAD) && (FD_ISSET(sockfd, &fds_read)) ) { + logmsg("data on socket"); + + if(*mode == PASSIVE_LISTEN) { + /* there's no stream set up yet, this is an indication that there's a + client connecting. */ + sockfd = accept(sockfd, NULL, NULL); + if(-1 == sockfd) + logmsg("accept() failed\n"); + else { + logmsg("====> Client connect"); + write(fileno(stdout), "CNCT\n", 5); + *sockfdp = sockfd; /* store the new socket */ + *mode = PASSIVE_CONNECT; /* we have connected */ + } + return TRUE; + } + + /* read from socket, pass on data to stdout */ + r = sread(sockfd, buffer, sizeof(buffer)); + + if(r <= 0) { + logmsg("====> Client disconnect"); + write(fileno(stdout), "DISC\n", 5); + sclose(sockfd); + *sockfdp = CURL_SOCKET_BAD; + if(*mode == PASSIVE_CONNECT) + *mode = PASSIVE_LISTEN; + return TRUE; + } + + sprintf(data, "DATA\n%04x\n", r); + write(fileno(stdout), data, 10); + write(fileno(stdout), buffer, r); + + logmsg("< %d bytes data, client => server", r); + lograw(buffer, r); + } + + return TRUE; +} + +int main(int argc, char *argv[]) +{ + struct sockaddr_in me; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 me6; +#endif /* ENABLE_IPV6 */ + int sock; + int msgsock = CURL_SOCKET_BAD; /* no stream socket yet */ + int flag; + FILE *pidfile; + char *pidname= (char *)".sockfilt.pid"; + int rc; + int arg=1; + bool ok = FALSE; + enum sockmode mode = PASSIVE_LISTEN; /* default */ + + while(argc>arg) { + if(!strcmp("--version", argv[arg])) { + printf("sockfilt IPv4%s\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("--logfile", argv[arg])) { + arg++; + if(argc>arg) + socklogfile = argv[arg++]; + } + else if(!strcmp("--ipv6", argv[arg])) { +#ifdef ENABLE_IPV6 + use_ipv6=TRUE; +#endif + arg++; + } + else if(!strcmp("--ipv4", argv[arg])) { + /* for completeness, we support this option as well */ + use_ipv6=FALSE; + arg++; + } + else if(!strcmp("--port", argv[arg])) { + arg++; + if(argc>arg) { + port = (unsigned short)atoi(argv[arg]); + arg++; + } + } + else if(!strcmp("--connect", argv[arg])) { + /* Asked to actively connect to the specified local port instead of + doing a passive server-style listening. */ + arg++; + if(argc>arg) { + connectport = (unsigned short)atoi(argv[arg]); + arg++; + } + } + else { + puts("Usage: sockfilt [option]\n" + " --version\n" + " --logfile [file]\n" + " --pidfile [file]\n" + " --ipv4\n" + " --ipv6\n" + " --port [port]"); + exit(0); + } + } + +#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 (sock < 0) { + perror("opening stream socket"); + logmsg("Error opening socket"); + exit(1); + } + + if(connectport) { + /* Active mode, we should connect to the given port number */ + mode = ACTIVE; +#ifdef ENABLE_IPV6 + if(!use_ipv6) { +#endif + memset(&me, 0, sizeof(me)); + me.sin_family = AF_INET; + me.sin_port = htons(connectport); + me.sin_addr.s_addr = INADDR_ANY; + Curl_inet_pton(AF_INET, "127.0.0.1", &me.sin_addr); + + rc = connect(sock, (struct sockaddr *) &me, sizeof(me)); +#ifdef ENABLE_IPV6 + } + else { + memset(&me6, 0, sizeof(me6)); + me6.sin6_family = AF_INET6; + me6.sin6_port = htons(connectport); + Curl_inet_pton(AF_INET, "::1", &me6.sin6_addr); + + rc = connect(sock, (struct sockaddr *) &me6, sizeof(me6)); + } +#endif /* ENABLE_IPV6 */ + if(rc) { + perror("connecting stream socket"); + logmsg("Error connecting to port %d", port); + exit(1); + } + logmsg("====> Client connect"); + msgsock = sock; /* use this as stream */ + } + else { + /* passive daemon style */ + + flag = 1; + if (setsockopt + (sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &flag, + sizeof(int)) < 0) { + perror("setsockopt(SO_REUSEADDR)"); + } + +#ifdef ENABLE_IPV6 + if(!use_ipv6) { +#endif + 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(struct sockaddr_in6)); + 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(rc < 0) { + perror("binding stream socket"); + logmsg("Error binding socket"); + exit(1); + } + + if(!port) { + /* The system picked a port number, now figure out which port we actually + got */ + /* we succeeded to bind */ + struct sockaddr_in add; + socklen_t socksize = sizeof(add); + + if(getsockname(sock, (struct sockaddr *) &add, + &socksize)<0) { + fprintf(stderr, "getsockname() failed"); + return 1; + } + port = ntohs(add.sin_port); + } + + /* start accepting connections */ + listen(sock, 0); + + } + + pidfile = fopen(pidname, "w"); + if(pidfile) { + fprintf(pidfile, "%d\n", (int)getpid()); + fclose(pidfile); + } + else + fprintf(stderr, "Couldn't write pid file\n"); + + logmsg("Running IPv%d version", + (use_ipv6?6:4)); + + if(connectport) + logmsg("Connected to port %d", connectport); + else + logmsg("Listening on port %d", port); + + do { + ok = juggle(&msgsock, sock, &mode); + } while(ok); + + sclose(sock); + + return 0; +} + diff --git a/tests/server/testpart.c b/tests/server/testpart.c new file mode 100644 index 000000000..c1ebdd44f --- /dev/null +++ b/tests/server/testpart.c @@ -0,0 +1,52 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2005, 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$ + ***************************************************************************/ + +#include "setup.h" + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> +#include "getpart.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include <curl/mprintf.h> + +/* include memdebug.h last */ +#include "memdebug.h" + +int main(int argc, char **argv) +{ + if(argc< 3) { + printf("./testpart main sub\n"); + } + else { + size_t size; + unsigned int i; + const char *buffer = spitout(stdin, argv[1], argv[2], &size); + for(i=0; i< size; i++) + printf("%c", buffer[i]); + } + return 0; +} + |