1
0
mirror of https://github.com/moparisthebest/socat synced 2024-11-16 05:55:11 -05:00
socat/socat.c
2008-02-17 14:59:16 +01:00

1355 lines
38 KiB
C

/* $Id: socat.c,v 1.111 2007/03/06 21:03:28 gerhard Exp $ */
/* Copyright Gerhard Rieger 2001-2007 */
/* Published under the GNU General Public License V.2, see file COPYING */
/* this is the main source, including command line option parsing, general
control, and the data shuffler */
#include "config.h"
#include "xioconfig.h" /* what features are enabled */
#include "sysincludes.h"
#include "mytypes.h"
#include "compat.h"
#include "error.h"
#include "sycls.h"
#include "sysutils.h"
#include "dalan.h"
#include "filan.h"
#include "xio.h"
#include "xioopts.h"
#include "xiosigchld.h"
#include "xiolockfile.h"
#include "xioopen.h"
/* command line options */
struct {
bool strictopts; /* stop on errors in address options */
xiolock_t lock; /* a lock file */
} socat_opts = {
0, /* strictopts */
{ NULL, 0 }, /* lock */
};
void socat_usage(FILE *fd);
void socat_version(FILE *fd);
int socat(int argc, const char *address1, const char *address2);
int _socat(xiofile_t *xfd1, xiofile_t *xfd2);
void socat_signal(int sig);
void lftocrlf(char **in, ssize_t *len, size_t bufsiz);
void crlftolf(char **in, ssize_t *len, size_t bufsiz);
static int socat_lock(void);
static void socat_unlock(void);
static int socat_newchild(void);
static const char socatversion[] =
#include "./VERSION"
;
static const char timestamp[] = __DATE__" "__TIME__;
const char copyright_socat[] = "socat by Gerhard Rieger - see www.dest-unreach.org";
#if WITH_OPENSSL
const char copyright_openssl[] = "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)";
const char copyright_ssleay[] = "This product includes software written by Tim Hudson (tjh@cryptsoft.com)";
#endif
bool havelock;
int main(int argc, const char *argv[]) {
const char **arg1, *a;
char buff[10];
double rto;
int i, argc0, result;
struct utsname ubuf;
int lockrc;
diag_set('p', strchr(argv[0], '/') ? strrchr(argv[0], '/')+1 : argv[0]);
/* we must init before applying options because env settings have lower
priority and are to be overridden by options */
if (xioinitialize(XIO_MAYALL) != 0) {
Exit(1);
}
xiosetopt('p', "%");
xiosetopt('o', ":");
argc0 = argc; /* save for later use */
arg1 = argv+1; --argc;
while (arg1[0] && (arg1[0][0] == '-')) {
switch (arg1[0][1]) {
case 'V': socat_version(stdout); Exit(0);
#if WITH_HELP
case '?':
case 'h':
socat_usage(stdout);
xioopenhelp(stdout, (arg1[0][2]=='?'||arg1[0][2]=='h') ? (arg1[0][3]=='?'||arg1[0][3]=='h') ? 2 : 1 : 0);
Exit(0);
#endif /* WITH_HELP */
case 'd': diag_set('d', NULL); break;
#if WITH_FILAN
case 'D': xioparams->debug = true; break;
#endif
case 'l':
switch (arg1[0][2]) {
case 'm': /* mixed mode: stderr, then switch to syslog; + facility */
diag_set('s', NULL);
xiosetopt('l', "m");
xioparams->logopt = arg1[0][2];
xiosetopt('y', &arg1[0][3]);
break;
case 'y': /* syslog + facility */
diag_set(arg1[0][2], &arg1[0][3]);
break;
case 'f': /* to file, +filename */
case 'p': /* artificial program name */
if (arg1[0][3]) {
diag_set(arg1[0][2], &arg1[0][3]);
} else if (arg1[1]) {
diag_set(arg1[0][2], arg1[1]);
++arg1, --argc;
} else {
Error1("option -l%c requires an argument; use option \"-h\" for help", arg1[0][2]);
}
break;
case 's': /* stderr */
diag_set(arg1[0][2], NULL);
break;
case 'u':
diag_set('u', NULL);
break;
case 'h':
diag_set_int('h', true);
break;
default:
Error1("unknown log option \"%s\"; use option \"-h\" for help", arg1[0]);
break;
}
break;
case 'v': xioparams->verbose = true; break;
case 'x': xioparams->verbhex = true; break;
case 'b': if (arg1[0][2]) {
a = *arg1+2;
} else {
++arg1, --argc;
if ((a = *arg1) == NULL) {
Error("option -b requires an argument; use option \"-h\" for help");
Exit(1);
}
}
xioparams->bufsiz = strtoul(a, (char **)&a, 0);
break;
case 's':
diag_set_int('e', E_FATAL); break;
case 't': if (arg1[0][2]) {
a = *arg1+2;
} else {
++arg1, --argc;
if ((a = *arg1) == NULL) {
Error("option -t requires an argument; use option \"-h\" for help");
Exit(1);
}
}
rto = strtod(a, (char **)&a);
xioparams->closwait.tv_sec = rto;
xioparams->closwait.tv_usec =
(rto-xioparams->closwait.tv_sec) * 1000000;
break;
case 'T': if (arg1[0][2]) {
a = *arg1+2;
} else {
++arg1, --argc;
if ((a = *arg1) == NULL) {
Error("option -T requires an argument; use option \"-h\" for help");
Exit(1);
}
}
rto = strtod(a, (char **)&a);
xioparams->total_timeout.tv_sec = rto;
xioparams->total_timeout.tv_usec =
(rto-xioparams->total_timeout.tv_sec) * 1000000;
break;
case 'u': xioparams->lefttoright = true; break;
case 'U': xioparams->righttoleft = true; break;
case 'g': xioopts_ignoregroups = true; break;
case 'L': if (socat_opts.lock.lockfile)
Error("only one -L and -W option allowed");
if (arg1[0][2]) {
socat_opts.lock.lockfile = *arg1+2;
} else {
++arg1, --argc;
if ((socat_opts.lock.lockfile = *arg1) == NULL) {
Error("option -L requires an argument; use option \"-h\" for help");
Exit(1);
}
}
break;
case 'W': if (socat_opts.lock.lockfile)
Error("only one -L and -W option allowed");
if (arg1[0][2]) {
socat_opts.lock.lockfile = *arg1+2;
} else {
++arg1, --argc;
if ((socat_opts.lock.lockfile = *arg1) == NULL) {
Error("option -W requires an argument; use option \"-h\" for help");
Exit(1);
}
}
socat_opts.lock.waitlock = true;
socat_opts.lock.intervall.tv_sec = 1;
socat_opts.lock.intervall.tv_nsec = 0;
break;
#if WITH_IP4 || WITH_IP6
#if WITH_IP4
case '4':
#endif
#if WITH_IP6
case '6':
#endif
xioopts.default_ip = arg1[0][1];
xioopts.preferred_ip = arg1[0][1];
break;
#endif /* WITH_IP4 || WITH_IP6 */
case '\0':
case '-': /*! this is hardcoded "--" */
case ',':
case ':': break; /* this "-" is a variation of STDIO or -- */
default:
xioinqopt('p', buff, sizeof(buff));
if (arg1[0][1] == buff[0]) {
break;
}
Error1("unknown option \"%s\"; use option \"-h\" for help", arg1[0]);
Exit(1);
}
/* the leading "-" might be a form of the first address */
xioinqopt('p', buff, sizeof(buff));
if (arg1[0][0] == '-' &&
(arg1[0][1] == '\0' || arg1[0][1] == ':' ||
arg1[0][1] == ',' || arg1[0][1] == '-'/*!*/ ||
arg1[0][1] == buff[0]))
break;
++arg1; --argc;
}
#if 0
Info1("%d address arguments", argc);
#else
if (argc != 2) {
Error1("exactly 2 addresses required (there are %d); use option \"-h\" for help", argc);
Exit(1);
}
#endif
if (xioparams->lefttoright && xioparams->righttoleft) {
Error("-U and -u must not be combined");
}
Info(copyright_socat);
#if WITH_OPENSSL
Info(copyright_openssl);
Info(copyright_ssleay);
#endif
Debug2("socat version %s on %s", socatversion, timestamp);
uname(&ubuf); /* ! here we circumvent internal tracing (Uname) */
Debug4("running on %s version %s, release %s, machine %s\n",
ubuf.sysname, ubuf.version, ubuf.release, ubuf.machine);
#if WITH_MSGLEVEL <= E_DEBUG
for (i = 0; i < argc0; ++i) {
Debug2("argv[%d]: \"%s\"", i, argv[i]);
}
#endif /* WITH_MSGLEVEL <= E_DEBUG */
/* not sure what signal should print a message */
Signal(SIGHUP, socat_signal);
Signal(SIGINT, socat_signal);
Signal(SIGQUIT, socat_signal);
Signal(SIGILL, socat_signal);
/* SIGABRT for assert; catching caused endless recursion on assert in libc
(tzfile.c:498: __tzfile_compute: Assertion 'num_types == 1' failed.) */
/*Signal(SIGABRT, socat_signal);*/
Signal(SIGBUS, socat_signal);
Signal(SIGFPE, socat_signal);
Signal(SIGSEGV, socat_signal);
Signal(SIGTERM, socat_signal);
#if HAVE_SIGACTION
{
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
act.sa_flags = SA_NOCLDSTOP|SA_RESTART|SA_SIGINFO
#ifdef SA_NOMASK
|SA_NOMASK
#endif
;
act.sa_sigaction = xiosigaction_subaddr_ok;
if (Sigaction(SIGUSR1, &act, NULL) < 0) {
/*! Linux man does not explicitely say that errno is defined */
Warn1("sigaction(SIGUSR1, {&xiosigaction_subaddr_ok}, NULL): %s", strerror(errno));
}
}
#else /* !HAVE_SIGACTION */
if (Signal(SIGUSR1, xiosigaction_subaddr_ok) == SIG_ERR) {
Warn1("signal(SIGCHLD, xiosigaction_subaddr_ok): %s", strerror(errno));
}
#endif /* !HAVE_SIGACTION */
/* set xio hooks */
xiohook_newchild = &socat_newchild;
if (lockrc = socat_lock()) {
/* =0: goon; >0: locked; <0: error, printed in sub */
if (lockrc > 0)
Error1("could not obtain lock \"%s\"", socat_opts.lock.lockfile);
Exit(1);
}
Atexit(socat_unlock);
result = socat(argc, arg1[0], arg1[1]);
Notice1("exiting with status %d", result);
Exit(result);
return 0; /* not reached, just for gcc -Wall */
}
void socat_usage(FILE *fd) {
fputs(copyright_socat, fd); fputc('\n', fd);
fputs("Usage:\n", fd);
fputs("socat [options] <bi-address> <bi-address>\n", fd);
fputs(" options:\n", fd);
fputs(" -V print version and feature information to stdout, and exit\n", fd);
#if WITH_HELP
fputs(" -h|-? print a help text describing command line options and addresses\n", fd);
fputs(" -hh like -h, plus a list of all common address option names\n", fd);
fputs(" -hhh like -hh, plus a list of all available address option names\n", fd);
#endif /* WITH_HELP */
fputs(" -d increase verbosity (use up to 4 times; 2 are recommended)\n", fd);
#if WITH_FILAN
fputs(" -D analyze file descriptors before loop\n", fd);
#endif
fputs(" -ly[facility] log to syslog, using facility (default is daemon)\n", fd);
fputs(" -lf<logfile> log to file\n", fd);
fputs(" -ls log to stderr (default if no other log)\n", fd);
fputs(" -lm[facility] mixed log mode (stderr during initialization, then syslog)\n", fd);
fputs(" -lp<progname> set the program name used for logging\n", fd);
fputs(" -lu use microseconds for logging timestamps\n", fd);
fputs(" -lh add hostname to log messages\n", fd);
fputs(" -v verbose data traffic, text\n", fd);
fputs(" -x verbose data traffic, hexadecimal\n", fd);
fputs(" -b<size_t> set data buffer size (8192)\n", fd);
fputs(" -s sloppy (continue on error)\n", fd);
fputs(" -t<timeout> wait seconds before closing second channel\n", fd);
fputs(" -T<timeout> total inactivity timeout in seconds\n", fd);
fputs(" -u unidirectional mode (left to right)\n", fd);
fputs(" -U unidirectional mode (right to left)\n", fd);
fputs(" -g do not check option groups\n", fd);
fputs(" -L <lockfile> try to obtain lock, or fail\n", fd);
fputs(" -W <lockfile> try to obtain lock, or wait\n", fd);
#if WITH_IP4
fputs(" -4 prefer IPv4 if version is not explicitly specified\n", fd);
#endif
#if WITH_IP6
fputs(" -6 prefer IPv6 if version is not explicitly specified\n", fd);
#endif
}
void socat_version(FILE *fd) {
struct utsname ubuf;
fputs(copyright_socat, fd); fputc('\n', fd);
fprintf(fd, "socat version %s on %s\n", socatversion, timestamp);
Uname(&ubuf);
fprintf(fd, " running on %s version %s, release %s, machine %s\n",
ubuf.sysname, ubuf.version, ubuf.release, ubuf.machine);
fputs("features:\n", fd);
#ifdef WITH_STDIO
fprintf(fd, " #define WITH_STDIO %d\n", WITH_STDIO);
#else
fputs(" #undef WITH_STDIO\n", fd);
#endif
#ifdef WITH_FDNUM
fprintf(fd, " #define WITH_FDNUM %d\n", WITH_FDNUM);
#else
fputs(" #undef WITH_FDNUM\n", fd);
#endif
#ifdef WITH_FILE
fprintf(fd, " #define WITH_FILE %d\n", WITH_FILE);
#else
fputs(" #undef WITH_FILE\n", fd);
#endif
#ifdef WITH_CREAT
fprintf(fd, " #define WITH_CREAT %d\n", WITH_CREAT);
#else
fputs(" #undef WITH_CREAT\n", fd);
#endif
#ifdef WITH_GOPEN
fprintf(fd, " #define WITH_GOPEN %d\n", WITH_GOPEN);
#else
fputs(" #undef WITH_GOPEN\n", fd);
#endif
#ifdef WITH_TERMIOS
fprintf(fd, " #define WITH_TERMIOS %d\n", WITH_TERMIOS);
#else
fputs(" #undef WITH_TERMIOS\n", fd);
#endif
#ifdef WITH_PIPE
fprintf(fd, " #define WITH_PIPE %d\n", WITH_PIPE);
#else
fputs(" #undef WITH_PIPE\n", fd);
#endif
#ifdef WITH_UNIX
fprintf(fd, " #define WITH_UNIX %d\n", WITH_UNIX);
#else
fputs(" #undef WITH_UNIX\n", fd);
#endif /* WITH_UNIX */
#ifdef WITH_ABSTRACT_UNIXSOCKET
fprintf(fd, " #define WITH_ABSTRACT_UNIXSOCKET %d\n", WITH_ABSTRACT_UNIXSOCKET);
#else
fputs(" #undef WITH_ABSTRACT_UNIXSOCKET\n", fd);
#endif /* WITH_ABSTRACT_UNIXSOCKET */
#ifdef WITH_IP4
fprintf(fd, " #define WITH_IP4 %d\n", WITH_IP4);
#else
fputs(" #undef WITH_IP4\n", fd);
#endif
#ifdef WITH_IP6
fprintf(fd, " #define WITH_IP6 %d\n", WITH_IP6);
#else
fputs(" #undef WITH_IP6\n", fd);
#endif
#ifdef WITH_RAWIP
fprintf(fd, " #define WITH_RAWIP %d\n", WITH_RAWIP);
#else
fputs(" #undef WITH_RAWIP\n", fd);
#endif
#ifdef WITH_TCP
fprintf(fd, " #define WITH_TCP %d\n", WITH_TCP);
#else
fputs(" #undef WITH_TCP\n", fd);
#endif
#ifdef WITH_UDP
fprintf(fd, " #define WITH_UDP %d\n", WITH_UDP);
#else
fputs(" #undef WITH_UDP\n", fd);
#endif
#ifdef WITH_LISTEN
fprintf(fd, " #define WITH_LISTEN %d\n", WITH_LISTEN);
#else
fputs(" #undef WITH_LISTEN\n", fd);
#endif
#ifdef WITH_SOCKS4
fprintf(fd, " #define WITH_SOCKS4 %d\n", WITH_SOCKS4);
#else
fputs(" #undef WITH_SOCKS4\n", fd);
#endif
#ifdef WITH_SOCKS4A
fprintf(fd, " #define WITH_SOCKS4A %d\n", WITH_SOCKS4A);
#else
fputs(" #undef WITH_SOCKS4A\n", fd);
#endif
#ifdef WITH_PROXY
fprintf(fd, " #define WITH_PROXY %d\n", WITH_PROXY);
#else
fputs(" #undef WITH_PROXY\n", fd);
#endif
#ifdef WITH_SYSTEM
fprintf(fd, " #define WITH_SYSTEM %d\n", WITH_SYSTEM);
#else
fputs(" #undef WITH_SYSTEM\n", fd);
#endif
#ifdef WITH_EXEC
fprintf(fd, " #define WITH_EXEC %d\n", WITH_EXEC);
#else
fputs(" #undef WITH_EXEC\n", fd);
#endif
#ifdef WITH_READLINE
fprintf(fd, " #define WITH_READLINE %d\n", WITH_READLINE);
#else
fputs(" #undef WITH_READLINE\n", fd);
#endif
#ifdef WITH_TUN
fprintf(fd, " #define WITH_TUN %d\n", WITH_TUN);
#else
fputs(" #undef WITH_TUN\n", fd);
#endif
#ifdef WITH_PTY
fprintf(fd, " #define WITH_PTY %d\n", WITH_PTY);
#else
fputs(" #undef WITH_PTY\n", fd);
#endif
#ifdef WITH_OPENSSL
fprintf(fd, " #define WITH_OPENSSL %d\n", WITH_OPENSSL);
#else
fputs(" #undef WITH_OPENSSL\n", fd);
#endif
#ifdef WITH_FIPS
fprintf(fd, " #define WITH_FIPS %d\n", WITH_FIPS);
#else
fputs(" #undef WITH_FIPS\n", fd);
#endif
#ifdef WITH_LIBWRAP
fprintf(fd, " #define WITH_LIBWRAP %d\n", WITH_LIBWRAP);
#else
fputs(" #undef WITH_LIBWRAP\n", fd);
#endif
#ifdef WITH_SYCLS
fprintf(fd, " #define WITH_SYCLS %d\n", WITH_SYCLS);
#else
fputs(" #undef WITH_SYCLS\n", fd);
#endif
#ifdef WITH_FILAN
fprintf(fd, " #define WITH_FILAN %d\n", WITH_FILAN);
#else
fputs(" #undef WITH_FILAN\n", fd);
#endif
#ifdef WITH_RETRY
fprintf(fd, " #define WITH_RETRY %d\n", WITH_RETRY);
#else
fputs(" #undef WITH_RETRY\n", fd);
#endif
#ifdef WITH_MSGLEVEL
fprintf(fd, " #define WITH_MSGLEVEL %d /*%s*/\n", WITH_MSGLEVEL,
&"debug\0\0\0info\0\0\0\0notice\0\0warn\0\0\0\0error\0\0\0fatal\0\0\0"[WITH_MSGLEVEL<<3]);
#else
fputs(" #undef WITH_MSGLEVEL\n", fd);
#endif
}
/* call this function when the common command line options are parsed, and the
addresses are extracted (but not resolved). */
int socat(int argc, const char *address1, const char *address2) {
xiofile_t *xfd1, *xfd2;
xioinitialize(XIO_MAYALL);
#if 1
if (Signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
Warn1("signal(SIGPIPE, SIG_IGN): %s", strerror(errno));
}
#endif
/* open the first (left most) address */
if (xioparams->lefttoright) {
if ((xfd1 = socat_open(address1, XIO_RDONLY, XIO_MAYFORK|XIO_MAYCHILD|XIO_MAYCONVERT)) == NULL) {
return -1;
}
} else if (xioparams->righttoleft) {
if ((xfd1 = socat_open(address1, XIO_WRONLY, XIO_MAYFORK|XIO_MAYCHILD|XIO_MAYCONVERT)) == NULL) {
return -1;
}
} else {
if ((xfd1 = socat_open(address1, XIO_RDWR, XIO_MAYFORK|XIO_MAYCHILD|XIO_MAYCONVERT)) == NULL) {
return -1;
}
}
xiosetsigchild(xfd1, socat_sigchild);
#if 1 /*! */
if (XIO_RDSTREAM(xfd1)->subaddrstat < 0) {
Info1("child "F_pid" has already died",
XIO_RDSTREAM(xfd1)->child.pid);
XIO_RDSTREAM(xfd1)->child.pid = 0;
/* return STAT_RETRYLATER; */
}
#endif
/* second (right) addresses chain */
if (XIO_WRITABLE(xfd1)) {
if (XIO_READABLE(xfd1)) {
if ((xfd2 = socat_open(address2, XIO_RDWR, XIO_MAYFORK|XIO_MAYCHILD|XIO_MAYEXEC|XIO_MAYCONVERT)) == NULL) {
return -1;
}
} else {
if ((xfd2 = socat_open(address2, XIO_RDONLY, XIO_MAYFORK|XIO_MAYCHILD|XIO_MAYEXEC|XIO_MAYCONVERT)) == NULL) {
return -1;
}
}
} else { /* assuming sock1 is readable */
if ((xfd2 = socat_open(address2, XIO_WRONLY, XIO_MAYFORK|XIO_MAYCHILD|XIO_MAYEXEC|XIO_MAYCONVERT)) == NULL) {
return -1;
}
}
xiosetsigchild(xfd2, socat_sigchild);
#if 1 /*! */
if (XIO_RDSTREAM(xfd2)->subaddrstat < 0) {
Info1("child "F_pid" has already died",
XIO_RDSTREAM(xfd2)->child.pid);
XIO_RDSTREAM(xfd2)->child.pid = 0;
/* return STAT_RETRYLATER; */
}
#endif
Info("resolved and opened all sock addresses");
return
_socat(xfd1, xfd2);
}
#if 0
/* checks if this is a connection to a child process, and if so, sees if the
child already died, leaving some data for us.
returns <0 if an error occurred;
returns 0 if no child or not yet died or died without data (sets eof);
returns >0 if child died and left data
*/
int childleftdata(xiofile_t *xfd) {
fd_set in, out, expt;
int retval;
/* have to check if a child process died before, but left read data */
if (XIO_READABLE(xfd) &&
(/*0 XIO_RDSTREAM(xfd)->howtoclose == END_KILL ||*/
XIO_RDSTREAM(xfd)->howtoclose == END_CLOSE_KILL ||
XIO_RDSTREAM(xfd)->howtoclose == END_SHUTDOWN_KILL) &&
XIO_RDSTREAM(xfd)->child.pid == 0) {
struct timeval time0 = { 0,0 };
FD_ZERO(&in); FD_ZERO(&out); FD_ZERO(&expt);
if (XIO_READABLE(xfd) && !(XIO_RDSTREAM(xfd)->eof >= 2 && !XIO_RDSTREAM(xfd)->ignoreeof)) {
FD_SET(XIO_GETRDFD(xfd), &in);
/*0 FD_SET(XIO_GETRDFD(xfd), &expt);*/
}
do {
retval = Select(FD_SETSIZE, &in, &out, &expt, &time0);
} while (retval < 0 && errno == EINTR);
if (retval < 0) {
#if HAVE_FDS_BITS
Error5("select(%d, &0x%lx, &0x%lx, &0x%lx, {0}): %s",
FD_SETSIZE, in.fds_bits[0], out.fds_bits[0],
expt.fds_bits[0], strerror(errno));
#else
Error5("select(%d, &0x%lx, &0x%lx, &0x%lx, {0}): %s",
FD_SETSIZE, in.__fds_bits[0], out.__fds_bits[0],
expt.__fds_bits[0], strerror(errno));
#endif
return -1;
} else if (retval == 0) {
Info("terminated child did not leave data for us");
XIO_RDSTREAM(xfd)->eof = 2;
xfd->stream.eof = 2;
closing = MAX(closing, 1);
}
}
return 0;
}
#endif /* 0 */
#if 0
bool mayrd1; /* sock1 has read data or eof, according to select() */
bool mayrd2; /* sock2 has read data or eof, according to select() */
bool maywr1; /* sock1 can be written to, according to select() */
bool maywr2; /* sock2 can be written to, according to select() */
/* here we come when the sockets are opened (in the meaning of C language),
and their options are set/applied
returns -1 on error or 0 on success */
int _socat(xiofile_t *xfd1, xiofile_t *xfd2) {
fd_set in, out, expt;
int retval;
unsigned char *buff;
ssize_t bytes1, bytes2;
int polling = 0; /* handling ignoreeof */
int wasaction = 1; /* last select was active, do NOT sleep before next */
struct timeval total_timeout; /* the actual total timeout timer */
sock1 = xfd1;
sock2 = xfd2;
#if WITH_FILAN
if (socat_opts.debug) {
int fdi, fdo;
int msglevel, exitlevel;
msglevel = diag_get_int('D'); /* save current message level */
diag_set_int('D', E_ERROR); /* only print errors and fatals in filan */
exitlevel = diag_get_int('e'); /* save current exit level */
diag_set_int('e', E_FATAL); /* only exit on fatals */
fdi = XIO_GETRDFD(sock1);
fdo = XIO_GETWRFD(sock1);
filan_fd(fdi, stderr);
if (fdo != fdi) {
filan_fd(fdo, stderr);
}
fdi = XIO_GETRDFD(sock2);
fdo = XIO_GETWRFD(sock2);
filan_fd(fdi, stderr);
if (fdo != fdi) {
filan_fd(fdo, stderr);
}
diag_set_int('e', exitlevel); /* restore old exit level */
diag_set_int('D', msglevel); /* restore old message level */
}
#endif /* WITH_FILAN */
/* when converting nl to crnl, size might double */
buff = Malloc(2*xioparams->bufsiz+1);
if (buff == NULL) return -1;
if (socat_opts.logopt == 'm' && xioinqopt('l', NULL, 0) == 'm') {
Info("switching to syslog");
diag_set('y', xioopts.syslogfac);
xiosetopt('l', "\0");
}
total_timeout = socat_opts.total_timeout;
Notice4("starting data transfer loop with FDs [%d,%d] and [%d,%d]",
XIO_GETRDFD(sock1), XIO_GETWRFD(sock1),
XIO_GETRDFD(sock2), XIO_GETWRFD(sock2));
while (XIO_RDSTREAM(sock1)->eof <= 1 ||
XIO_RDSTREAM(sock2)->eof <= 1) {
struct timeval timeout, *to = NULL;
Debug6("data loop: sock1->eof=%d, sock2->eof=%d, closing=%d, wasaction=%d, total_to={"F_tv_sec"."F_tv_usec"}",
XIO_RDSTREAM(sock1)->eof, XIO_RDSTREAM(sock2)->eof,
closing, wasaction,
total_timeout.tv_sec, total_timeout.tv_usec);
/* for ignoreeof */
if (polling) {
if (!wasaction) {
/* yes we could do it with select but I like readable trace output */
if (socat_opts.pollintv.tv_sec) Sleep(socat_opts.pollintv.tv_sec);
if (socat_opts.pollintv.tv_usec) Usleep(socat_opts.pollintv.tv_usec);
if (socat_opts.total_timeout.tv_sec != 0 ||
socat_opts.total_timeout.tv_usec != 0) {
if (total_timeout.tv_usec < socat_opts.pollintv.tv_usec) {
total_timeout.tv_usec += 1000000;
total_timeout.tv_sec -= 1;
}
total_timeout.tv_sec -= socat_opts.pollintv.tv_sec;
total_timeout.tv_usec -= socat_opts.pollintv.tv_usec;
if (total_timeout.tv_sec < 0 ||
total_timeout.tv_sec == 0 && total_timeout.tv_usec < 0) {
Notice("inactivity timeout triggered");
return 0;
}
}
} else {
wasaction = 0;
}
}
if (polling) {
/* there is a ignoreeof poll timeout, use it */
timeout = socat_opts.pollintv;
to = &timeout;
} else if (socat_opts.total_timeout.tv_sec != 0 ||
socat_opts.total_timeout.tv_usec != 0) {
/* there might occur a total inactivity timeout */
timeout = socat_opts.total_timeout;
to = &timeout;
} else {
to = NULL;
}
if (closing>=1) {
/* first eof already occurred, start end timer */
timeout = socat_opts.closwait;
to = &timeout;
closing = 2;
}
do {
int _errno;
FD_ZERO(&in); FD_ZERO(&out); FD_ZERO(&expt);
childleftdata(sock1);
childleftdata(sock2);
if (closing>=1) {
/* first eof already occurred, start end timer */
timeout = socat_opts.closwait;
to = &timeout;
closing = 2;
}
if (XIO_READABLE(sock1) &&
!(XIO_RDSTREAM(sock1)->eof > 1 && !XIO_RDSTREAM(sock1)->ignoreeof) &&
!socat_opts.righttoleft) {
Debug3("*** sock1: %p [%d,%d]", sock1, XIO_GETRDFD(sock1), XIO_GETWRFD(sock1));
if (!mayrd1) {
FD_SET(XIO_GETRDFD(sock1), &in);
}
if (!maywr2) {
FD_SET(XIO_GETWRFD(sock2), &out);
}
}
if (XIO_READABLE(sock2) &&
!(XIO_RDSTREAM(sock2)->eof > 1 && !XIO_RDSTREAM(sock2)->ignoreeof) &&
!socat_opts.lefttoright) {
Debug3("*** sock2: %p [%d,%d]", sock2, XIO_GETRDFD(sock2), XIO_GETWRFD(sock2));
if (!mayrd2) {
FD_SET(XIO_GETRDFD(sock2), &in);
}
if (!maywr1) {
FD_SET(XIO_GETWRFD(sock1), &out);
}
}
retval = Select(FD_SETSIZE, &in, &out, &expt, to);
_errno = errno;
if (retval < 0 && errno == EINTR) {
Info1("select(): %s", strerror(errno));
}
errno = _errno;
} while (retval < 0 && errno == EINTR);
/* attention:
when an exec'd process sends data and terminates, it is unpredictable
whether the data or the sigchild arrives first.
*/
if (retval < 0) {
#if HAVE_FDS_BITS
Error7("select(%d, &0x%lx, &0x%lx, &0x%lx, %s%lu): %s",
FD_SETSIZE, in.fds_bits[0], out.fds_bits[0],
expt.fds_bits[0], to?"&":"NULL/", to?to->tv_sec:0,
strerror(errno));
#else
Error7("select(%d, &0x%lx, &0x%lx, &0x%lx, %s%lu): %s",
FD_SETSIZE, in.__fds_bits[0], out.__fds_bits[0],
expt.__fds_bits[0], to?"&":"NULL/", to?to->tv_sec:0,
strerror(errno));
#endif
return -1;
} else if (retval == 0) {
Info2("select timed out (no data within %ld.%06ld seconds)",
closing>=1?socat_opts.closwait.tv_sec:socat_opts.total_timeout.tv_sec,
closing>=1?socat_opts.closwait.tv_usec:socat_opts.total_timeout.tv_usec);
if (polling && !wasaction) {
/* there was a ignoreeof poll timeout, use it */
;
} else if (socat_opts.total_timeout.tv_sec != 0 ||
socat_opts.total_timeout.tv_usec != 0) {
/* there was a total inactivity timeout */
Notice("inactivity timeout triggered");
return 0;
}
if (closing) {
break;
}
/* one possibility to come here is ignoreeof on some fd, but no EOF
and no data on any descriptor - this is no indication for end! */
continue;
}
if (XIO_READABLE(sock1) && XIO_GETRDFD(sock1) >= 0 &&
FD_ISSET(XIO_GETRDFD(sock1), &in)) {
mayrd1 = true;
}
if (XIO_READABLE(sock2) && XIO_GETRDFD(sock2) >= 0 &&
FD_ISSET(XIO_GETRDFD(sock2), &in)) {
mayrd2 = true;
}
if (XIO_GETWRFD(sock1) >= 0 && FD_ISSET(XIO_GETWRFD(sock1), &out)) {
maywr1 = true;
}
if (XIO_GETWRFD(sock2) >= 0 && FD_ISSET(XIO_GETWRFD(sock2), &out)) {
maywr2 = true;
}
if (mayrd1 && maywr2) {
mayrd1 = false;
if ((bytes1 = xiotransfer(sock1, sock2, &buff, xioparams->bufsiz, false))
< 0) {
if (errno != EAGAIN) {
closing = MAX(closing, 1);
Notice("socket 1 to socket 2 is in error");
if (socat_opts.lefttoright) {
break;
}
}
} else if (bytes1 > 0) {
maywr2 = false;
total_timeout = socat_opts.total_timeout;
wasaction = 1;
/* is more data available that has already passed select()? */
mayrd1 = (xiopending(sock1) > 0);
if (XIO_RDSTREAM(sock1)->readbytes != 0 &&
XIO_RDSTREAM(sock1)->actbytes == 0) {
/* avoid idle when all readbytes already there */
mayrd1 = true;
}
} else { /* bytes2 == 0 */
if (XIO_RDSTREAM(sock1)->ignoreeof && !closing) {
;
} else {
XIO_RDSTREAM(sock1)->eof = 2;
closing = MAX(closing, 1);
}
/* (bytes1 == 0) handled later */
}
} else {
bytes1 = -1;
}
if (mayrd2 && maywr1) {
mayrd2 = false;
if ((bytes2 = xiotransfer(sock2, sock1, &buff, xioparams->bufsiz, true))
< 0) {
if (errno != EAGAIN) {
closing = MAX(closing, 1);
Notice("socket 2 to socket 1 is in error");
if (socat_opts.righttoleft) {
break;
}
}
} else if (bytes2 > 0) {
maywr1 = false;
total_timeout = socat_opts.total_timeout;
wasaction = 1;
/* is more data available that has already passed select()? */
mayrd2 = (xiopending(sock2) > 0);
if (XIO_RDSTREAM(sock2)->readbytes != 0 &&
XIO_RDSTREAM(sock2)->actbytes == 0) {
/* avoid idle when all readbytes already there */
mayrd2 = true;
}
} else { /* bytes == 0 */
if (XIO_RDSTREAM(sock2)->ignoreeof && !closing) {
;
} else {
XIO_RDSTREAM(sock2)->eof = 2;
closing = MAX(closing, 1);
}
/* (bytes2 == 0) handled later */
}
} else {
bytes2 = -1;
}
/* NOW handle EOFs */
if (bytes1 == 0 || XIO_RDSTREAM(sock1)->eof >= 2) {
if (XIO_RDSTREAM(sock1)->ignoreeof && !closing) {
Debug1("socket 1 (fd %d) is at EOF, ignoring",
XIO_RDSTREAM(sock1)->fd1); /*! */
polling = 1;
} else {
Notice1("socket 1 (fd %d) is at EOF", XIO_GETRDFD(sock1));
xioshutdown(sock2, SHUT_WR);
if (socat_opts.lefttoright) {
break;
}
}
}
if (bytes2 == 0 || XIO_RDSTREAM(sock2)->eof >= 2) {
if (XIO_RDSTREAM(sock2)->ignoreeof && !closing) {
Debug1("socket 2 (fd %d) is at EOF, ignoring",
XIO_RDSTREAM(sock2)->fd1);
polling = 1;
} else {
Notice1("socket 2 (fd %d) is at EOF", XIO_GETRDFD(sock2));
xioshutdown(sock1, SHUT_WR);
if (socat_opts.righttoleft) {
break;
}
}
}
}
/* close everything that's still open */
xioclose(sock1);
xioclose(sock2);
return 0;
}
#endif /* 0 */
#if 0
#define MAXTIMESTAMPLEN 128
/* prints the timestamp to the buffer and terminates it with '\0'. This buffer
should be at least MAXTIMESTAMPLEN bytes long.
returns 0 on success or -1 if an error occurred */
int gettimestamp(char *timestamp) {
size_t bytes;
#if HAVE_GETTIMEOFDAY || 1
struct timeval now;
int result;
time_t nowt;
#else /* !HAVE_GETTIMEOFDAY */
time_t now;
#endif /* !HAVE_GETTIMEOFDAY */
#if HAVE_GETTIMEOFDAY || 1
result = gettimeofday(&now, NULL);
if (result < 0) {
return result;
} else {
nowt = now.tv_sec;
#if HAVE_STRFTIME
bytes = strftime(timestamp, 20, "%Y/%m/%d %H:%M:%S", localtime(&nowt));
bytes += sprintf(timestamp+19, "."F_tv_usec" ", now.tv_usec);
#else
strcpy(timestamp, ctime(&nowt));
bytes = strlen(timestamp);
#endif
}
#else /* !HAVE_GETTIMEOFDAY */
now = time(NULL); if (now == (time_t)-1) {
return -1;
} else {
#if HAVE_STRFTIME
bytes = strftime(timestamp, 21, "%Y/%m/%d %H:%M:%S ", localtime(&now));
#else
strcpy(timestamp, ctime(&now));
bytes = strlen(timestamp);
#endif
}
#endif /* !HAVE_GETTIMEOFDAY */
return 0;
}
#endif
#if 0
static const char *prefixltor = "> ";
static const char *prefixrtol = "< ";
static unsigned long numltor;
static unsigned long numrtol;
/* print block header (during verbose or hex dump)
returns 0 on success or -1 if an error occurred */
static int
xioprintblockheader(FILE *file, size_t bytes, bool righttoleft) {
char timestamp[MAXTIMESTAMPLEN];
char buff[128+MAXTIMESTAMPLEN];
if (gettimestamp(timestamp) < 0) {
return -1;
}
if (righttoleft) {
sprintf(buff, "%s%s length="F_Zu" from=%lu to=%lu\n",
prefixrtol, timestamp, bytes, numrtol, numrtol+bytes-1);
numrtol+=bytes;
} else {
sprintf(buff, "%s%s length="F_Zu" from=%lu to=%lu\n",
prefixltor, timestamp, bytes, numltor, numltor+bytes-1);
numltor+=bytes;
}
fputs(buff, file);
return 0;
}
#endif /* 0 */
#if 0
/* inpipe is suspected to have read data available; read at most bufsiz bytes
and transfer them to outpipe. Perform required data conversions.
buff should be at least twice as large as bufsiz, to allow all standard
conversions. Returns the number of bytes written, or 0 on EOF or <0 if an
error occurred or when data was read but none written due to conversions
(with EAGAIN). EAGAIN also occurs when reading from a nonblocking FD where
the file has a mandatory lock.
If 0 bytes were read (EOF), it does NOT shutdown or close a channel, and it
does NOT write a zero bytes block.
*/
/* inpipe, outpipe must be single descriptors (not dual!) */
int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
unsigned char **buff, size_t bufsiz, bool righttoleft) {
ssize_t bytes, writt;
bytes = xioread(inpipe, *buff, xioparams->bufsiz);
if (bytes < 0) {
if (errno != EAGAIN)
XIO_RDSTREAM(inpipe)->eof = 2;
/*xioshutdown(inpipe, SHUT_RD);*/
return -1;
}
if (bytes == 0 && XIO_RDSTREAM(inpipe)->ignoreeof && !closing) {
writt = 0;
} else if (bytes == 0) {
XIO_RDSTREAM(inpipe)->eof = 2;
closing = MAX(closing, 1);
writt = 0;
}
else /* if (bytes > 0)*/ {
if (XIO_RDSTREAM(inpipe)->lineterm !=
XIO_WRSTREAM(outpipe)->lineterm) {
cv_newline(buff, &bytes,
XIO_RDSTREAM(inpipe)->lineterm,
XIO_WRSTREAM(outpipe)->lineterm);
}
if (bytes == 0) {
errno = EAGAIN; return -1;
}
if (xioparams->verbose && xioparams->verbhex) {
/* Hack-o-rama */
size_t i = 0;
size_t j;
size_t N = 16;
const unsigned char *end, *s, *t;
s = *buff;
end = (*buff)+bytes;
xioprintblockheader(stderr, bytes, righttoleft);
while (s < end) {
/*! prefix? */
j = Min(N, (size_t)(end-s));
/* print hex */
t = s;
i = 0;
while (i < j) {
int c = *t++;
fprintf(stderr, " %02x", c);
++i;
if (c == '\n') break;
}
/* fill hex column */
while (i < N) {
fputs(" ", stderr);
++i;
}
fputs(" ", stderr);
/* print acsii */
t = s;
i = 0;
while (i < j) {
int c = *t++;
if (c == '\n') {
fputc('.', stderr);
break;
}
if (!isprint(c))
c = '.';
fputc(c, stderr);
++i;
}
fputc('\n', stderr);
s = t;
}
fputs("--\n", stderr);
} else if (socat_opts.verbose) {
size_t i = 0;
xioprintblockheader(stderr, bytes, righttoleft);
while (i < (size_t)bytes) {
int c = (*buff)[i];
if (i > 0 && (*buff)[i-1] == '\n')
/*! prefix? */;
switch (c) {
case '\a' : fputs("\\a", stderr); break;
case '\b' : fputs("\\b", stderr); break;
case '\t' : fputs("\t", stderr); break;
case '\n' : fputs("\n", stderr); break;
case '\v' : fputs("\\v", stderr); break;
case '\f' : fputs("\\f", stderr); break;
case '\r' : fputs("\\r", stderr); break;
case '\\' : fputs("\\\\", stderr); break;
default:
if (!isprint(c))
c = '.';
fputc(c, stderr);
break;
}
++i;
}
} else if (xioparams->verbhex) {
int i;
/*! prefix? */
for (i = 0; i < bytes; ++i) {
fprintf(stderr, " %02x", (*buff)[i]);
}
fputc('\n', stderr);
}
writt = xiowrite(outpipe, *buff, bytes);
if (writt < 0) {
/* EAGAIN when nonblocking but a mandatory lock is on file.
the problem with EAGAIN is that the read cannot be repeated,
so we need to buffer the data and try to write it later
again. not yet implemented, sorry. */
#if 0
if (errno == EPIPE) {
return 0; /* can no longer write; handle like EOF */
}
#endif
return -1;
} else {
Info3("transferred "F_Zu" bytes from %d to %d",
writt, XIO_GETRDFD(inpipe), XIO_GETWRFD(outpipe));
}
}
return writt;
}
#endif /* 0 */
#if 0
#define CR '\r'
#define LF '\n'
int cv_newline(unsigned char **buff, ssize_t *bytes,
int lineterm1, int lineterm2) {
/* must perform newline changes */
if (lineterm1 <= LINETERM_CR && lineterm2 <= LINETERM_CR) {
/* no change in data length */
unsigned char from, to, *p, *z;
if (lineterm1 == LINETERM_RAW) {
from = '\n'; to = '\r';
} else {
from = '\r'; to = '\n';
}
z = *buff + *bytes;
p = *buff;
while (p < z) {
if (*p == from) *p = to;
++p;
}
} else if (lineterm1 == LINETERM_CRNL) {
/* buffer becomes shorter */
unsigned char to, *s, *t, *z;
if (lineterm2 == LINETERM_RAW) {
to = '\n';
} else {
to = '\r';
}
z = *buff + *bytes;
s = t = *buff;
while (s < z) {
if (*s == '\r') {
++s;
continue;
}
if (*s == '\n') {
*t++ = to; ++s;
} else {
*t++ = *s++;
}
}
*bytes = t - *buff;
} else {
/* buffer becomes longer, must alloc another space */
unsigned char *buf2;
unsigned char from; unsigned char *s, *t, *z;
if (lineterm1 == LINETERM_RAW) {
from = '\n';
} else {
from = '\r';
}
if ((buf2 = Malloc(2*xioparams->bufsiz+1)) == NULL) {
return -1;
}
s = *buff; t = buf2; z = *buff + *bytes;
while (s < z) {
if (*s == from) {
*t++ = '\r'; *t++ = '\n';
++s;
continue;
} else {
*t++ = *s++;
}
}
free(*buff);
*buff = buf2;
*bytes = t - buf2;;
}
return 0;
}
#endif /* 0 */
void socat_signal(int signum) {
switch (signum) {
case SIGQUIT:
case SIGILL:
case SIGABRT:
case SIGBUS:
case SIGFPE:
case SIGSEGV:
case SIGPIPE:
diag_set_int('x', 128+signum); /* in case Error exits for us */
Error1("exiting on signal %d", signum);
diag_set_int('x', 0); /* in case Error did not exit */
break;
case SIGTERM:
Warn1("exiting on signal %d", signum); break;
case SIGHUP:
case SIGINT:
Notice1("exiting on signal %d", signum); break;
}
Exit(128+signum);
}
#if 0
/* this is the callback when the child of an address died */
static int socat_sigchild(struct single *file) {
if (file->ignoreeof && !closing) {
;
} else {
file->eof = MAX(file->eof, 1);
closing = 1;
}
return 0;
}
#endif
static int socat_lock(void) {
int lockrc;
#if 1
if ((lockrc = xiolock(&socat_opts.lock)) < 0) {
return -1;
}
if (lockrc == 0) {
havelock = true;
}
return lockrc;
#else
if (socat_opts.lock.lockfile) {
if ((lockrc = xiolock(socat_opts.lock.lockfile)) < 0) {
/*Error1("error with lockfile \"%s\"", socat_opts.lock.lockfile);*/
return -1;
}
if (lockrc) {
return 1;
}
havelock = true;
/*0 Info1("obtained lock \"%s\"", socat_opts.lock.lockfile);*/
}
if (socat_opts.lock.waitlock) {
if (xiowaitlock(socat_opts.lock.waitlock, socat_opts.lock.intervall)) {
/*Error1("error with lockfile \"%s\"", socat_opts.lock.lockfile);*/
return -1;
} else {
havelock = true;
/*0 Info1("obtained lock \"%s\"", socat_opts.lock.waitlock);*/
}
}
return 0;
#endif
}
static void socat_unlock(void) {
if (!havelock) return;
if (socat_opts.lock.lockfile) {
if (Unlink(socat_opts.lock.lockfile) < 0) {
Warn2("unlink(\"%s\"): %s",
socat_opts.lock.lockfile, strerror(errno));
} else {
Info1("released lock \"%s\"", socat_opts.lock.lockfile);
}
}
}
/* this is a callback function that may be called by the newchild hook of xio
*/
static int socat_newchild(void) {
havelock = false;
return 0;
}