#define CURL_NO_OLDIES
#include "setup.h"
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#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 HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#define ENABLE_CURLX_PRINTF
#include "curlx.h"
#include "getpart.h"
#include "inet_pton.h"
#include "util.h"
#include "server_sockaddr.h"
#include "memdebug.h"
#define DEFAULT_PORT 8999
#ifndef DEFAULT_LOGFILE
#define DEFAULT_LOGFILE "log/sockfilt.log"
#endif
const char *serverlogfile = DEFAULT_LOGFILE;
static bool verbose = FALSE;
static bool bind_only = FALSE;
#ifdef ENABLE_IPV6
static bool use_ipv6 = FALSE;
#endif
static const char *ipv_inuse = "IPv4";
static unsigned short port = DEFAULT_PORT;
static unsigned short connectport = 0;
enum sockmode {
PASSIVE_LISTEN,
PASSIVE_CONNECT,
ACTIVE,
ACTIVE_DISCONNECT
};
#ifndef HAVE_SIGINTERRUPT
#define siginterrupt(x,y) do {} while(0)
#endif
typedef RETSIGTYPE (*SIGHANDLER_T)(int);
#ifdef SIGHUP
static SIGHANDLER_T old_sighup_handler = SIG_ERR;
#endif
#ifdef SIGPIPE
static SIGHANDLER_T old_sigpipe_handler = SIG_ERR;
#endif
#ifdef SIGALRM
static SIGHANDLER_T old_sigalrm_handler = SIG_ERR;
#endif
#ifdef SIGINT
static SIGHANDLER_T old_sigint_handler = SIG_ERR;
#endif
#ifdef SIGTERM
static SIGHANDLER_T old_sigterm_handler = SIG_ERR;
#endif
SIG_ATOMIC_T got_exit_signal = 0;
static volatile int exit_signal = 0;
static RETSIGTYPE exit_signal_handler(int signum)
{
int old_errno = ERRNO;
if(got_exit_signal == 0) {
got_exit_signal = 1;
exit_signal = signum;
}
(void)signal(signum, exit_signal_handler);
SET_ERRNO(old_errno);
}
static void install_signal_handlers(void)
{
#ifdef SIGHUP
if((old_sighup_handler = signal(SIGHUP, SIG_IGN)) == SIG_ERR)
logmsg("cannot install SIGHUP handler: %s", strerror(ERRNO));
#endif
#ifdef SIGPIPE
if((old_sigpipe_handler = signal(SIGPIPE, SIG_IGN)) == SIG_ERR)
logmsg("cannot install SIGPIPE handler: %s", strerror(ERRNO));
#endif
#ifdef SIGALRM
if((old_sigalrm_handler = signal(SIGALRM, SIG_IGN)) == SIG_ERR)
logmsg("cannot install SIGALRM handler: %s", strerror(ERRNO));
#endif
#ifdef SIGINT
if((old_sigint_handler = signal(SIGINT, exit_signal_handler)) == SIG_ERR)
logmsg("cannot install SIGINT handler: %s", strerror(ERRNO));
else
siginterrupt(SIGINT, 1);
#endif
#ifdef SIGTERM
if((old_sigterm_handler = signal(SIGTERM, exit_signal_handler)) == SIG_ERR)
logmsg("cannot install SIGTERM handler: %s", strerror(ERRNO));
else
siginterrupt(SIGTERM, 1);
#endif
}
static void restore_signal_handlers(void)
{
#ifdef SIGHUP
if(SIG_ERR != old_sighup_handler)
(void)signal(SIGHUP, old_sighup_handler);
#endif
#ifdef SIGPIPE
if(SIG_ERR != old_sigpipe_handler)
(void)signal(SIGPIPE, old_sigpipe_handler);
#endif
#ifdef SIGALRM
if(SIG_ERR != old_sigalrm_handler)
(void)signal(SIGALRM, old_sigalrm_handler);
#endif
#ifdef SIGINT
if(SIG_ERR != old_sigint_handler)
(void)signal(SIGINT, old_sigint_handler);
#endif
#ifdef SIGTERM
if(SIG_ERR != old_sigterm_handler)
(void)signal(SIGTERM, old_sigterm_handler);
#endif
}
static ssize_t fullread(int filedes, void *buffer, size_t nbytes)
{
int error;
ssize_t rc;
ssize_t nread = 0;
do {
rc = read(filedes, (unsigned char *)buffer + nread, nbytes - nread);
if(got_exit_signal) {
logmsg("signalled to die");
return -1;
}
if(rc < 0) {
error = ERRNO;
if((error == EINTR) || (error == EAGAIN))
continue;
logmsg("unrecoverable read() failure: %s", strerror(error));
return -1;
}
if(rc == 0) {
logmsg("got 0 reading from stdin");
return 0;
}
nread += rc;
} while((size_t)nread < nbytes);
if(verbose)
logmsg("read %zd bytes", nread);
return nread;
}
static ssize_t fullwrite(int filedes, const void *buffer, size_t nbytes)
{
int error;
ssize_t wc;
ssize_t nwrite = 0;
do {
wc = write(filedes, (unsigned char *)buffer + nwrite, nbytes - nwrite);
if(got_exit_signal) {
logmsg("signalled to die");
return -1;
}
if(wc < 0) {
error = ERRNO;
if((error == EINTR) || (error == EAGAIN))
continue;
logmsg("unrecoverable write() failure: %s", strerror(error));
return -1;
}
if(wc == 0) {
logmsg("put 0 writing to stdout");
return 0;
}
nwrite += wc;
} while((size_t)nwrite < nbytes);
if(verbose)
logmsg("wrote %zd bytes", nwrite);
return nwrite;
}
static bool read_stdin(void *buffer, size_t nbytes)
{
ssize_t nread = fullread(fileno(stdin), buffer, nbytes);
if(nread != (ssize_t)nbytes) {
logmsg("exiting...");
return FALSE;
}
return TRUE;
}
static bool write_stdout(const void *buffer, size_t nbytes)
{
ssize_t nwrite = fullwrite(fileno(stdout), buffer, nbytes);
if(nwrite != (ssize_t)nbytes) {
logmsg("exiting...");
return FALSE;
}
return TRUE;
}
static void lograw(unsigned char *buffer, ssize_t len)
{
char data[120];
ssize_t i;
unsigned char *ptr = buffer;
char *optr = data;
ssize_t 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;
}
if(width>60) {
logmsg("'%s'", data);
width = 0;
optr = data;
}
}
if(width)
logmsg("'%s'", data);
}
static bool 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 sockfd = CURL_SOCKET_BAD;
curl_socket_t maxfd = CURL_SOCKET_BAD;
ssize_t rc;
ssize_t nread_socket;
ssize_t bytes_written;
ssize_t buffer_len;
int error = 0;
unsigned char buffer[17010];
char data[16];
if(got_exit_signal) {
logmsg("signalled to die, exiting...");
return FALSE;
}
#ifdef HAVE_GETPPID
if(getppid() <= 1) {
logmsg("process becomes orphan, exiting");
return FALSE;
}
#endif
timeout.tv_sec = 120;
timeout.tv_usec = 0;
FD_ZERO(&fds_read);
FD_ZERO(&fds_write);
FD_ZERO(&fds_err);
#ifdef USE_WINSOCK
#else
FD_SET(fileno(stdin), &fds_read);
#endif
switch(*mode) {
case PASSIVE_LISTEN:
sockfd = listenfd;
FD_SET(sockfd, &fds_read);
maxfd = sockfd;
break;
case PASSIVE_CONNECT:
sockfd = *sockfdp;
if(CURL_SOCKET_BAD == sockfd) {
logmsg("socket is -1! on %s:%d", __FILE__, __LINE__);
maxfd = 0;
}
else {
FD_SET(sockfd, &fds_read);
maxfd = sockfd;
}
break;
case ACTIVE:
sockfd = *sockfdp;
if(CURL_SOCKET_BAD != sockfd) {
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;
}
do {
rc = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, &timeout);
if(got_exit_signal) {
logmsg("signalled to die, exiting...");
return FALSE;
}
} while((rc == -1) && ((error = SOCKERRNO) == EINTR));
if(rc < 0) {
logmsg("select() failed with error: (%d) %s",
error, strerror(error));
return FALSE;
}
if(rc == 0)
return TRUE;
if(FD_ISSET(fileno(stdin), &fds_read)) {
if(!read_stdin(buffer, 5))
return FALSE;
logmsg("Received %c%c%c%c (on stdin)",
buffer[0], buffer[1], buffer[2], buffer[3] );
if(!memcmp("PING", buffer, 4)) {
if(!write_stdout("PONG\n", 5))
return FALSE;
}
else if(!memcmp("PORT", buffer, 4)) {
sprintf((char *)buffer, "%s/%hu\n", ipv_inuse, port);
buffer_len = (ssize_t)strlen((char *)buffer);
snprintf(data, sizeof(data), "PORT\n%04zx\n", buffer_len);
if(!write_stdout(data, 10))
return FALSE;
if(!write_stdout(buffer, buffer_len))
return FALSE;
}
else if(!memcmp("QUIT", buffer, 4)) {
logmsg("quits");
return FALSE;
}
else if(!memcmp("DATA", buffer, 4)) {
if(!read_stdin(buffer, 5))
return FALSE;
buffer[5] = '\0';
buffer_len = (ssize_t)strtol((char *)buffer, NULL, 16);
if (buffer_len > (ssize_t)sizeof(buffer)) {
logmsg("ERROR: Buffer size (%zu bytes) too small for data size "
"(%zd bytes)", sizeof(buffer), buffer_len);
return FALSE;
}
logmsg("> %zd bytes data, server => client", buffer_len);
if(!read_stdin(buffer, buffer_len))
return FALSE;
lograw(buffer, buffer_len);
if(*mode == PASSIVE_LISTEN) {
logmsg("*** We are disconnected!");
if(!write_stdout("DISC\n", 5))
return FALSE;
}
else {
bytes_written = swrite(sockfd, buffer, buffer_len);
if(bytes_written != buffer_len) {
logmsg("Not all data was sent. Bytes to send: %zd sent: %zd",
buffer_len, bytes_written);
}
}
}
else if(!memcmp("DISC", buffer, 4)) {
if(!write_stdout("DISC\n", 5))
return FALSE;
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;
}
else
logmsg("attempt to close already dead connection");
return TRUE;
}
}
if((sockfd != CURL_SOCKET_BAD) && (FD_ISSET(sockfd, &fds_read)) ) {
if(*mode == PASSIVE_LISTEN) {
sockfd = accept(sockfd, NULL, NULL);
if(CURL_SOCKET_BAD == sockfd)
logmsg("accept() failed");
else {
logmsg("====> Client connect");
if(!write_stdout("CNCT\n", 5))
return FALSE;
*sockfdp = sockfd;
*mode = PASSIVE_CONNECT;
}
return TRUE;
}
nread_socket = sread(sockfd, buffer, sizeof(buffer));
if(nread_socket <= 0) {
logmsg("====> Client disconnect");
if(!write_stdout("DISC\n", 5))
return FALSE;
sclose(sockfd);
*sockfdp = CURL_SOCKET_BAD;
if(*mode == PASSIVE_CONNECT)
*mode = PASSIVE_LISTEN;
else
*mode = ACTIVE_DISCONNECT;
return TRUE;
}
snprintf(data, sizeof(data), "DATA\n%04zx\n", nread_socket);
if(!write_stdout(data, 10))
return FALSE;
if(!write_stdout(buffer, nread_socket))
return FALSE;
logmsg("< %zd bytes data, client => server", nread_socket);
lograw(buffer, nread_socket);
}
return TRUE;
}
static curl_socket_t sockdaemon(curl_socket_t sock,
unsigned short *listenport)
{
srvr_sockaddr_union_t listener;
int flag;
int rc;
int totdelay = 0;
int maxretr = 10;
int delay= 20;
int attempt = 0;
int error = 0;
do {
attempt++;
flag = 1;
rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(void *)&flag, sizeof(flag));
if(rc) {
error = SOCKERRNO;
logmsg("setsockopt(SO_REUSEADDR) failed with error: (%d) %s",
error, strerror(error));
if(maxretr) {
rc = wait_ms(delay);
if(rc) {
error = SOCKERRNO;
logmsg("wait_ms() failed with error: (%d) %s",
error, strerror(error));
sclose(sock);
return CURL_SOCKET_BAD;
}
if(got_exit_signal) {
logmsg("signalled to die, exiting...");
sclose(sock);
return CURL_SOCKET_BAD;
}
totdelay += delay;
delay *= 2;
}
}
} while(rc && maxretr--);
if(rc) {
logmsg("setsockopt(SO_REUSEADDR) failed %d times in %d ms. Error: (%d) %s",
attempt, totdelay, error, strerror(error));
logmsg("Continuing anyway...");
}
#ifdef ENABLE_IPV6
if(!use_ipv6) {
#endif
memset(&listener.sa4, 0, sizeof(listener.sa4));
listener.sa4.sin_family = AF_INET;
listener.sa4.sin_addr.s_addr = INADDR_ANY;
listener.sa4.sin_port = htons(*listenport);
rc = bind(sock, &listener.sa, sizeof(listener.sa4));
#ifdef ENABLE_IPV6
}
else {
memset(&listener.sa6, 0, sizeof(listener.sa6));
listener.sa6.sin6_family = AF_INET6;
listener.sa6.sin6_addr = in6addr_any;
listener.sa6.sin6_port = htons(*listenport);
rc = bind(sock, &listener.sa, sizeof(listener.sa6));
}
#endif
if(rc) {
error = SOCKERRNO;
logmsg("Error binding socket on port %hu: (%d) %s",
*listenport, error, strerror(error));
sclose(sock);
return CURL_SOCKET_BAD;
}
if(!*listenport) {
curl_socklen_t la_size;
srvr_sockaddr_union_t localaddr;
#ifdef ENABLE_IPV6
if(!use_ipv6)
#endif
la_size = sizeof(localaddr.sa4);
#ifdef ENABLE_IPV6
else
la_size = sizeof(localaddr.sa6);
#endif
memset(&localaddr.sa, 0, (size_t)la_size);
if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
error = SOCKERRNO;
logmsg("getsockname() failed with error: (%d) %s",
error, strerror(error));
sclose(sock);
return CURL_SOCKET_BAD;
}
switch (localaddr.sa.sa_family) {
case AF_INET:
*listenport = ntohs(localaddr.sa4.sin_port);
break;
#ifdef ENABLE_IPV6
case AF_INET6:
*listenport = ntohs(localaddr.sa6.sin6_port);
break;
#endif
default:
break;
}
if(!*listenport) {
logmsg("Apparently getsockname() succeeded, with listener port zero.");
logmsg("A valid reason for this failure is a binary built without");
logmsg("proper network library linkage. This might not be the only");
logmsg("reason, but double check it before anything else.");
sclose(sock);
return CURL_SOCKET_BAD;
}
}
if(bind_only) {
logmsg("instructed to bind port without listening");
return sock;
}
rc = listen(sock, 5);
if(0 != rc) {
error = SOCKERRNO;
logmsg("listen() failed with error: (%d) %s",
error, strerror(error));
sclose(sock);
return CURL_SOCKET_BAD;
}
return sock;
}
int main(int argc, char *argv[])
{
srvr_sockaddr_union_t me;
curl_socket_t sock = CURL_SOCKET_BAD;
curl_socket_t msgsock = CURL_SOCKET_BAD;
int wrotepidfile = 0;
char *pidname= (char *)".sockfilt.pid";
bool juggle_again;
int rc;
int error;
int arg=1;
enum sockmode mode = PASSIVE_LISTEN;
const char *addr = NULL;
while(argc>arg) {
if(!strcmp("--version", argv[arg])) {
printf("sockfilt IPv4%s\n",
#ifdef ENABLE_IPV6
"/IPv6"
#else
""
#endif
);
return 0;
}
else if(!strcmp("--verbose", argv[arg])) {
verbose = TRUE;
arg++;
}
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++];
}
else if(!strcmp("--ipv6", argv[arg])) {
#ifdef ENABLE_IPV6
ipv_inuse = "IPv6";
use_ipv6 = TRUE;
#endif
arg++;
}
else if(!strcmp("--ipv4", argv[arg])) {
#ifdef ENABLE_IPV6
ipv_inuse = "IPv4";
use_ipv6 = FALSE;
#endif
arg++;
}
else if(!strcmp("--bindonly", argv[arg])) {
bind_only = TRUE;
arg++;
}
else if(!strcmp("--port", argv[arg])) {
arg++;
if(argc>arg) {
char *endptr;
unsigned long ulnum = strtoul(argv[arg], &endptr, 10);
if((endptr != argv[arg] + strlen(argv[arg])) ||
((ulnum != 0UL) && ((ulnum < 1025UL) || (ulnum > 65535UL)))) {
fprintf(stderr, "sockfilt: invalid --port argument (%s)\n",
argv[arg]);
return 0;
}
port = curlx_ultous(ulnum);
arg++;
}
}
else if(!strcmp("--connect", argv[arg])) {
arg++;
if(argc>arg) {
char *endptr;
unsigned long ulnum = strtoul(argv[arg], &endptr, 10);
if((endptr != argv[arg] + strlen(argv[arg])) ||
(ulnum < 1025UL) || (ulnum > 65535UL)) {
fprintf(stderr, "sockfilt: invalid --connect argument (%s)\n",
argv[arg]);
return 0;
}
connectport = curlx_ultous(ulnum);
arg++;
}
}
else if(!strcmp("--addr", argv[arg])) {
arg++;
if(argc>arg) {
addr = argv[arg];
arg++;
}
}
else {
puts("Usage: sockfilt [option]\n"
" --version\n"
" --verbose\n"
" --logfile [file]\n"
" --pidfile [file]\n"
" --ipv4\n"
" --ipv6\n"
" --bindonly\n"
" --port [port]\n"
" --connect [port]\n"
" --addr [address]");
return 0;
}
}
#ifdef WIN32
win32_init();
atexit(win32_cleanup);
#endif
install_signal_handlers();
#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) {
error = SOCKERRNO;
logmsg("Error creating socket: (%d) %s",
error, strerror(error));
write_stdout("FAIL\n", 5);
goto sockfilt_cleanup;
}
if(connectport) {
mode = ACTIVE;
#ifdef ENABLE_IPV6
if(!use_ipv6) {
#endif
memset(&me.sa4, 0, sizeof(me.sa4));
me.sa4.sin_family = AF_INET;
me.sa4.sin_port = htons(connectport);
me.sa4.sin_addr.s_addr = INADDR_ANY;
if (!addr)
addr = "127.0.0.1";
Curl_inet_pton(AF_INET, addr, &me.sa4.sin_addr);
rc = connect(sock, &me.sa, sizeof(me.sa4));
#ifdef ENABLE_IPV6
}
else {
memset(&me.sa6, 0, sizeof(me.sa6));
me.sa6.sin6_family = AF_INET6;
me.sa6.sin6_port = htons(connectport);
if (!addr)
addr = "::1";
Curl_inet_pton(AF_INET6, addr, &me.sa6.sin6_addr);
rc = connect(sock, &me.sa, sizeof(me.sa6));
}
#endif
if(rc) {
error = SOCKERRNO;
logmsg("Error connecting to port %hu: (%d) %s",
connectport, error, strerror(error));
write_stdout("FAIL\n", 5);
goto sockfilt_cleanup;
}
logmsg("====> Client connect");
msgsock = sock;
}
else {
sock = sockdaemon(sock, &port);
if(CURL_SOCKET_BAD == sock) {
write_stdout("FAIL\n", 5);
goto sockfilt_cleanup;
}
msgsock = CURL_SOCKET_BAD;
}
logmsg("Running %s version", ipv_inuse);
if(connectport)
logmsg("Connected to port %hu", connectport);
else if(bind_only)
logmsg("Bound without listening on port %hu", port);
else
logmsg("Listening on port %hu", port);
wrotepidfile = write_pidfile(pidname);
if(!wrotepidfile) {
write_stdout("FAIL\n", 5);
goto sockfilt_cleanup;
}
do {
juggle_again = juggle(&msgsock, sock, &mode);
} while(juggle_again);
sockfilt_cleanup:
if((msgsock != sock) && (msgsock != CURL_SOCKET_BAD))
sclose(msgsock);
if(sock != CURL_SOCKET_BAD)
sclose(sock);
if(wrotepidfile)
unlink(pidname);
restore_signal_handlers();
if(got_exit_signal) {
logmsg("============> sockfilt exits with signal (%d)", exit_signal);
raise(exit_signal);
}
logmsg("============> sockfilt quits");
return 0;
}