Newer
Older
Daniel Stenberg
committed
/***************************************************************************
* _ _ ____ _
* 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), or connect
* to a given (localhost) port.
Daniel Stenberg
committed
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
*
* 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
#define ENABLE_CURLX_PRINTF
/* make the curlx header define all printf() functions to use the curlx_*
versions instead */
Daniel Stenberg
committed
#include "curlx.h" /* from the private lib dir */
#include "getpart.h"
#include "inet_pton.h"
#include "util.h"
Daniel Stenberg
committed
/* 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
const char *serverlogfile = (char *)DEFAULT_LOGFILE;
Daniel Stenberg
committed
static void lograw(unsigned char *buffer, ssize_t len)
Daniel Stenberg
committed
{
char data[120];
ssize_t i;
Daniel Stenberg
committed
unsigned char *ptr = buffer;
char *optr = data;
ssize_t width=0;
Daniel Stenberg
committed
for(i=0; i<len; i++) {
switch(ptr[i]) {
case '\n':
sprintf(optr, "\\n");
width += 2;
optr += 2;
break;
case '\r':
sprintf(optr, "\\r");
width += 2;
optr += 2;
break;
default:
sprintf(optr, "%c", (isgraph(ptr[i]) || ptr[i]==0x20) ?ptr[i]:'.');
width++;
optr++;
break;
}
Daniel Stenberg
committed
if(width>60) {
logmsg("'%s'", data);
Daniel Stenberg
committed
width = 0;
optr = data;
}
}
if(width)
logmsg("'%s'", data);
Daniel Stenberg
committed
}
#ifdef SIGPIPE
static void sigpipe_handler(int sig)
{
(void)sig; /* prevent warning */
sigpipe = 1;
}
#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 */
ACTIVE_DISCONNECT /* as a client, disconnected from server */
Daniel Stenberg
committed
};
/*
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;
Daniel Stenberg
committed
curl_socket_t maxfd;
ssize_t rc;
ssize_t bytes_written;
Daniel Stenberg
committed
unsigned char buffer[256]; /* FIX: bigger buffer */
char data[256];
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) {
Daniel Stenberg
committed
case PASSIVE_LISTEN:
Daniel Stenberg
committed
/* server mode */
sockfd = listenfd;
/* there's always a socket to wait for */
FD_SET(sockfd, &fds_read);
maxfd = sockfd;
break;
case PASSIVE_CONNECT:
Daniel Stenberg
committed
sockfd = *sockfdp;
/* eeek, we are supposedly connected and then this cannot be -1 ! */
logmsg("socket is -1! on %s:%d", __FILE__, __LINE__);
}
else {
/* there's always a socket to wait for */
FD_SET(sockfd, &fds_read);
maxfd = sockfd;
}
Daniel Stenberg
committed
break;
case ACTIVE:
Daniel Stenberg
committed
/* sockfd turns CURL_SOCKET_BAD when our connection has been closed */
Daniel Stenberg
committed
FD_SET(sockfd, &fds_read);
maxfd = sockfd;
}
else {
logmsg("No socket to read on");
maxfd = 0;
}
break;
case ACTIVE_DISCONNECT:
logmsg("disconnected, no socket to read on");
maxfd = 0;
sockfd = CURL_SOCKET_BAD;
break;
Daniel Stenberg
committed
do {
rc = select(maxfd + 1, &fds_read, &fds_write, &fds_err, &timeout);
} while((rc == -1) && (ourerrno() == EINTR));
Daniel Stenberg
committed
switch(rc) {
Daniel Stenberg
committed
case -1:
return FALSE;
case 0: /* timeout! */
return TRUE;
}
if(FD_ISSET(fileno(stdin), &fds_read)) {
/* 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_stdin = read(fileno(stdin), buffer, 5);
if(5 == nread_stdin) {
Daniel Stenberg
committed
logmsg("Received %c%c%c%c (on stdin)",
Daniel Stenberg
committed
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 to PORT with "IPv[num]/[port]" */
sprintf((char *)buffer, "IPv%d/%d\n", use_ipv6?6:4, (int)port);
buffer_len = (ssize_t)strlen((char *)buffer);
sprintf(data, "PORT\n%04x\n", buffer_len);
Daniel Stenberg
committed
write(fileno(stdout), data, 10);
Daniel Stenberg
committed
}
else if(!memcmp("QUIT", buffer, 4)) {
/* just die */
logmsg("quits");
return FALSE;
Daniel Stenberg
committed
}
else if(!memcmp("DATA", buffer, 4)) {
/* data IN => data OUT */
if(5 != read(fileno(stdin), buffer, 5))
return FALSE;
Daniel Stenberg
committed
buffer_len = (ssize_t)strtol((char *)buffer, NULL, 16);
if (buffer_len > (ssize_t)sizeof(buffer)) {
logmsg("Buffer size %d too small for data size %d",
(int)sizeof(buffer), buffer_len);
return FALSE;
}
nread_stdin = read(fileno(stdin), buffer, buffer_len);
if(nread_stdin != buffer_len)
Daniel Stenberg
committed
return FALSE;
logmsg("> %d bytes data, server => client", buffer_len);
lograw(buffer, buffer_len);
Daniel Stenberg
committed
if(*mode == PASSIVE_LISTEN) {
logmsg("*** We are disconnected!");
Daniel Stenberg
committed
write(fileno(stdout), "DISC\n", 5);
}
Daniel Stenberg
committed
/* send away on the socket */
bytes_written = swrite(sockfd, buffer, buffer_len);
if(bytes_written != buffer_len) {
logmsg("Not all data was sent. Bytes to send: %d sent: %d",
Daniel Stenberg
committed
}
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;
if(*mode == PASSIVE_CONNECT)
*mode = PASSIVE_LISTEN;
else
*mode = ACTIVE_DISCONNECT;
Daniel Stenberg
committed
}
else
logmsg("attempt to close already dead connection");
return TRUE;
}
}
else if(-1 == nread_stdin) {
logmsg("read %d from stdin, exiting", nread_stdin);
return FALSE;
Daniel Stenberg
committed
}
}
Daniel Stenberg
committed
if((sockfd != CURL_SOCKET_BAD) && (FD_ISSET(sockfd, &fds_read)) ) {
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);
Daniel Stenberg
committed
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 */
Daniel Stenberg
committed
Daniel Stenberg
committed
logmsg("====> Client disconnect");
write(fileno(stdout), "DISC\n", 5);
sclose(sockfd);
*sockfdp = CURL_SOCKET_BAD;
if(*mode == PASSIVE_CONNECT)
*mode = PASSIVE_LISTEN;
else
*mode = ACTIVE_DISCONNECT;
Daniel Stenberg
committed
return TRUE;
}
Daniel Stenberg
committed
write(fileno(stdout), data, 10);
Daniel Stenberg
committed
logmsg("< %d bytes data, client => server", nread_socket);
lograw(buffer, nread_socket);
Daniel Stenberg
committed
}
return TRUE;
}
static curl_socket_t sockdaemon(curl_socket_t sock,
unsigned short *port)
{
/* passive daemon style */
struct sockaddr_in me;
#ifdef ENABLE_IPV6
struct sockaddr_in6 me6;
#endif /* ENABLE_IPV6 */
int flag = 1;
int rc;
if (setsockopt
(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag,
sizeof(flag)) < 0) {
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
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");
return CURL_SOCKET_BAD;
}
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 CURL_SOCKET_BAD;
}
*port = ntohs(add.sin_port);
}
/* start accepting connections */
listen(sock, 4);
return sock;
}
static curl_socket_t mksock(bool use_ipv6)
{
curl_socket_t sock;
#ifdef ENABLE_IPV6
if(!use_ipv6)
#ifdef ENABLE_IPV6
else
sock = socket(AF_INET6, SOCK_STREAM, 0);
#endif
if (sock < 0) {
perror("opening stream socket");
logmsg("Error opening socket");
return CURL_SOCKET_BAD;
}
return sock;
}
Daniel Stenberg
committed
int main(int argc, char *argv[])
{
struct sockaddr_in me;
#ifdef ENABLE_IPV6
struct sockaddr_in6 me6;
#endif /* ENABLE_IPV6 */
curl_socket_t sock;
curl_socket_t msgsock;
Daniel Stenberg
committed
FILE *pidfile;
char *pidname= (char *)".sockfilt.pid";
int rc;
int arg=1;
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)
serverlogfile = argv[arg++];
Daniel Stenberg
committed
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
}
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]");
return 0;
Daniel Stenberg
committed
}
}
#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 = mksock(use_ipv6);
Daniel Stenberg
committed
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_INET6, "::1", &me6.sin6_addr);
Daniel Stenberg
committed
rc = connect(sock, (struct sockaddr *) &me6, sizeof(me6));
}
#endif /* ENABLE_IPV6 */
if(rc) {
perror("connecting stream socket");
logmsg("Error connecting to port %d", port);
return 1;
Daniel Stenberg
committed
}
logmsg("====> Client connect");
msgsock = sock; /* use this as stream */
}
else {
/* passive daemon style */
sock = sockdaemon(sock, &port);
if(CURL_SOCKET_BAD == sock)
return 1;
msgsock = CURL_SOCKET_BAD; /* no stream socket yet */
Daniel Stenberg
committed
}
logmsg("Running IPv%d version",
(use_ipv6?6:4));
if(connectport)
logmsg("Connected to port %d", connectport);
else
logmsg("Listening on port %d", port);
pidfile = fopen(pidname, "w");
if(pidfile) {
int pid = (int)getpid();
fprintf(pidfile, "%d\n", pid);
fclose(pidfile);
logmsg("Wrote pid %d to %s", pid, pidname);
}
else
fprintf(stderr, "Couldn't write pid file\n");
while(juggle(&msgsock, sock, &mode));
Daniel Stenberg
committed
sclose(sock);
return 0;
}