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, int len)
{
char data[120];
int i;
unsigned char *ptr = buffer;
char *optr = data;
int width=0;
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;
curl_socket_t maxfd;
Daniel Stenberg
committed
unsigned char buffer[256]; /* FIX: bigger buffer */
char data[256];
curl_socket_t sockfd;
ssize_t bytes_written;
Daniel Stenberg
committed
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;
/* there's always a socket to wait for */
FD_SET(sockfd, &fds_read);
maxfd = sockfd;
break;
case PASSIVE_CONNECT:
sockfd = *sockfdp;
if(-1 == sockfd) {
/* 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:
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;
}
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 {
r = select(maxfd + 1, &fds_read, &fds_write, &fds_err, &timeout);
} while((r == -1) && (ourerrno() == EINTR));
switch(r) {
case -1:
return FALSE;
case 0: /* timeout! */
return TRUE;
}
if(FD_ISSET(fileno(stdin), &fds_read)) {
Daniel Stenberg
committed
/* 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 %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 with PORT with "IPv[num]/[port]" */
sprintf((char *)buffer, "IPv%d/%d\n", use_ipv6?6:4, port);
Daniel Stenberg
committed
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");
return FALSE;
Daniel Stenberg
committed
}
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);
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, len);
if(bytes_written != (ssize_t)len) {
logmsg("====> Not all data was sent. Bytes to send: %d Bytes sent: %d",
len, bytes_written);
}
}
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(nread == -1){
Daniel Stenberg
committed
logmsg("read %d from stdin, exiting", (int)nread);
return FALSE;
Daniel Stenberg
committed
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
}
}
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);
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;
else
*mode = ACTIVE_DISCONNECT;
Daniel Stenberg
committed
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;
}
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
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
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, (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");
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
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
}
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;
}