aboutsummaryrefslogtreecommitdiff
path: root/tests/server
diff options
context:
space:
mode:
Diffstat (limited to 'tests/server')
-rw-r--r--tests/server/Makefile.am16
-rw-r--r--tests/server/getpart.c16
-rw-r--r--tests/server/sockfilt.c668
-rw-r--r--tests/server/testpart.c52
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;
+}
+