/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* See "man perlpod" to make man or web pages.

=head1 NAME
  proxy - connect something to something else

=head1 SYNOPSIS
  proxy [-/+options] host port

=head1 DESCRIPTION
  This is a little program that makes a connection to the given  host
  and  then acts as a pipe to send data from stdin to the connection,
  and from the connection to stdout. We can also produce a transcript
  of the session in a logfile if desired.  Thus, this program is just
  a variant of the tee(1) command, but with a TCP  connection  rather
  than a local pipe.

  You'd think that the telnet(1) command would do  the  job,  but  it
  doesn't.   The  problem  is  that  with  telnet  you get everything
  (including passwords) echoed by someone.  If the  other  end  is  a
  login  session,  the result is double echoing of input, which users
  find confusing and exasperating.  And the echoing of passwords is a
  showstopper, of course.  There doesn't seem to be any fix for this,
  so I wrote my own program.

=head1 OPTIONS
  Options may start with '+' or '-' to enable/disable features;  with
  the  options for which this doesn't make sense, either may be used.
  An option letter may be upper or lower case.

  The options recognized are:

  -a<A>
    Accept only connections from address <A>.  This allows us to  set
    up  a  proxy listener on a port that will only accept connections
    from a single controlling client host.

  -l
  -l<P>
  -l<A:P>
    Listen for connections on port <P>.  When we receive an  incoming
    connection,  we  will  then  make  the  second  connection to the
    host/port on the command line.  You can specify an IP address  as
    well  as  a  port  in the usual web notation:  -l16.32.25.14:2007
    would mean to bind only to port 2007 on 16.32.25.14,  which  must
    be  an  IP  address  of  one  of  our interfaces.  If the port is
    omitted, we will bind to a random port, and write its  number  to
    stdout so that our parent can find it.

  -t<T>
    Timeout at time <T>.  The value of <T> is a  large  integer,  the
    Unix  timestamp  for when the timeout should occur.  At that time
    this program will exit.

=head CONFIG
  This program has no config files at all.  Everything is  configured
  from the command line or the environment.

=head1 AUTHOR:
  John Chambers <jc@trillian.mit.edu>

=cut
*/

#include "V.h"
#include "memchunk.h"
#include "sys_socket.h"
#include "sys_in.h"
#include "sys_netdb.h"
#include "sys_types.h"
#include "sys_stat.h"
#include "sys_signal.h"

#ifdef OSF1
#include <sys/mode.h>
#endif
#include <unistd.h>

#define BUF 1000		/* Enough to hold longest message */

char   buf[BUF];
char*  dfl_host = "localhost";
char   ifmt[] = "%d";
byte   FTPcmd = 0x0FF;	/* FTP command byte */
byte   FTPwont = 0x0FC;	/* FTP "I won't do that" response */
time_t timeout = 0;		/* Timeout after this many seconds */
TIMV   tmout = {1};		/* How long to wait in select() */
TIMV*  tmop = 0;		/* Null for no timeout */
char*  portname = 0;
U32    now, then;		/* Timestamps */
int    qlen = 1;		/* Length of listening queue */
U32    rmask = 0;		/* Select read mask */
STat   stat0;			/* Status for file 0 */
Flag   raw0 = 0;		/* File 0 has been set to raw mode */
char*  myname = 0;		/* For long version of program name */

/*
* We have three different addresses to deal with:
*   "client" is the controlling connection, stdin/out by default
*   "my"     is our local listening port, if needed.
*   "server" is the remote system that we connect to.
*/
char* cladst = 0;			/* Client's  IP address string */
char* myadst = 0;			/* Listening IP address string */
char* svadst = 0;			/* Server's  IP address string */
char* svhost = 0;
int   svport = 0;
int   myport = 0;
int   clport = 0;
SKin  clad = {0};
SKin  myad = {0};
SKin  svad = {0};
int   cliens;

Flag  randport = 0;		/* If true, generate a random listening port */
int   tryports = 20;	/* Number of ports to try */

/*
* Here are assorted sleep intervals, not all of which may be used  at
* this time:
*/
int   bsleep =  1;	/* Seconds to wait in bindloop */
int   esleep = 10;	/* Seconds to wait before exiting */
int   lsleep =  1;	/* Seconds to wait in listen loop */
int   rsleep = -1;	/* Seconds to wait for supplies */
int   wsleep = -1;	/* Seconds between writes */

/*
* Here are some messages for handling telnet handshaking.  We  handle
* most of this by simply refusing or denying everything.
*/
byte  TNwont[]    = {0xFF,0xFC,0};	/* Generic telnet refusal */
byte  TNwontx18[] = {0xFF,0xFC,0x18};	/* Refusal to do 0x18 */
byte  TNwontx01[] = {0xFF,0xFC,0x01};	/* Refusal to do 0x01 */
byte  TNwontx03[] = {0xFF,0xFC,0x03};	/* Refusal to do 0x03 */
byte  TNwontx22[] = {0xFF,0xFC,0x22};	/* Refusal to do 0x22=LINEMODE */
byte  TNdontx22[] = {0xFF,0xFC,0x22};	/* Command to not do 0x22=LINEMODE */

/*
* Here are  our  file  handles  for  input  and  output  in  the  two
* directions.   If  no  "near"  end  is specified, ifd1 and ofd1 will
* default to stdin and stdout.
*/
int   ifd1 = -1;	/* Near  input end of our connection */
int   ifd2 = -1;	/* Far   input end of our connection */
int   ofd1 = -1;	/* Near output end of our connection */
int   ofd2 = -1;	/* Far  output end of our connection */
int   lsock = -1;	/* Listening socket */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
void quit(st) {
	if (raw0) MakeSane(0,"stdin");
	P2 "%s\t%s Exiting with status %d in %d seconds ...",Vtime(),pname,st,esleep D;
	Fprintf(stdout,"\r%s\tProcess %d exiting with status %d in %d seconds ...\r\n",
		pname,mypid,st,esleep);
	fflush(stdout);
	Sleep(esleep);
	P2 "%s\t%s exiting with status %d.",Vtime(),pname,st D;
	Fprintf(stdout,"\r%s\tProcess %d exiting with status %d.\r\n",pname,mypid,st);
	fflush(stdout);
	Exit(st);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
void sigALRM() {
	V2 "ALRM",pname D;
	V2 "Timed out." D;
	Fprintf(stdout,"\r\n%s\tTimed out.\r\n",pname);

	quit(ETIMEDOUT);
}

void sigHUP() {
	V3 "sigHUP" D;
	Fprintf(stderr,"\r\n%s\tSignal: HUP.\r\n",pname);
	quit(1);
}

void sigQUIT() {
	V3 "sigQUIT" D;
	Fprintf(stderr,"\r\n%s\tSignal: QUIT.\r\n",pname);
	quit(1);
}

void sigTERM() {
	V3 "sigTERM" D;
	Fprintf(stderr,"\r\n%s\tSignal: TERM.\r\n",pname);
	quit(1);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* This routine has the job of making sure that all our standard  files
* are attached to the specified socket, and no std file is left with a
* connection to the original standard files.  The std files are highly
* likely to be a TCP connection to a remote client. If it's a browser,
* we'll cause it to hang, so we must close them all down.
*/
dupsock(sock)
	int sock;
{	int i, fdcount;
	V2 "Fclose(stdin)" D;
	Fclose(stdin);
	V2 "Fclose(stderr)" D;
	Fclose(stderr);
	for (i=0; i<3; i++) {
		V2 "Close(%d)",i D;
		Close(i);
	}
	for (i=0; i<3; i++) {
		V2 "Dup2(%d,%d)",sock,i D;
	    if (Dup2(sock,i) < 0) {
		    V1 "Can't dup2(%d,%d) [Err %d=%s=%s]",sock,i D;
		    quit(errno);
	    }
	}
	fdcount = Vdmpft();
	V2 "We have %d open files ...",fdcount D;
	for (i=4; i<= fdcount; i++) {
		if (Fstat(i,&stat0) < 0) {
			V2 "File %d is not open.",i D;
			;
		} elsif (i == Fileno(Vout)) {
			V2 "File %d is Vout.",i D;
			;
		} elsif (i == sock) {
			V2 "File %d is listening socket.",i D;
			;
		} else {
			V2 "File %d is unknown; close it.",i D;
			Close(i);
		}
	}
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
main(ac,av)
	int   ac;
	char**av;
{	int   r=0;	/* Result (exit status) */
	char* p;
	int   c0, c1;
	long  a, childpid, gcpid, i, fdcount, m, n, sockmask;
	HOST* hp;
	int   x0,x1,x2,x3;
/*
* Initialize the debug package:
*/
	ac = Vinit(ac,av);
	mypid = Getpid();
	BZero(&stat0,sizeof(STat));
	newpname();
/*
* Process the command-line args:
*/
	for (a=1; a<ac; a++) {
		V2 "Arg %2d= %s Y",a,Dspp(av[a]) D;
		switch (c0 = av[a][0]) {
		case 0:
			continue;
		case '-':
		case '+':
			V3 "Opt %2d: \"%s\"",a,av[a] D;
			switch (c1 = av[a][1]) {
			case 'a':
			case 'A':
				cladst = av[a]+2;	/* Allowed IP address */
			V2 "Client address: %s",cladst D;
				break;
			case 'd':
			case 'D':
				Vopt(av[a]+2);
				break;
			case 'l':
			case 'L':
				myadst = av[a]+2;	/* Listen IP address and port */
				V2 "My address: %s",cladst D;
				break;
			case 'r':
			case 'R':
				if (sscanf(av[a]+2,ifmt,&rsleep) < 1) ++n;
				V2 "Wait %d seconds for replies.",rsleep D;
				break;
			case 's':
			case 'S':
				if (sscanf(av[a]+2,ifmt,&wsleep) < 1) ++n;
				V2 "Wait %d seconds between messages.",wsleep D;
				break;
			case 't':
			case 'T':
				if (sscanf(av[a]+2,ifmt,&timeout) < 1) ++n;
				V2 "Timeout at %d.",timeout D;
				break;
			default:
				V1 "Arg %2d: \"%s\" ignored.",a,av[a] D;
				break;
			}
			break;
		default:
			if (!svhost) {
				svhost = av[a];
				V2 "svhost=\"%s\" mypid=%d ***.",svhost,mypid D;
			} else
			if (!portname && (sscanf(av[a],ifmt,&svport) > 0)) {
				portname = av[a];
				V2 "svPort: \"%s\"",portname D;
				V3 "svport: %d.",svport D;
			} else {
				V1 "Arg %2d: \"%s\" ignored.",a,av[a] D;
			}
			break;
		}
	}
	if (!svport) svport = 23;	/* Default is telnet port */
	SignalM(SIGALRM, sigALRM,"sigALRM");
/*
* Here's our first attempt to handle a timeout exit:
*/
	if (timeout) {
		if ((i = timeout - (now = UnixTime(0))) <= 0) {
			Fprintf(stdout,"\r\nTimeout %d is before our start time %d.\r\n",timeout,now);
			Fprintf(stderr,"\r\n[SE]\tTimeout %d is before our start time %d.\r\n",timeout,now);
			quit(ETIMEDOUT);
		}
		Alarm(i);
		V2 "Timeout alarm set for %d seconds from now.",i D;
	}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* If we have a listening socket, set it up.
*/
if (myadst) {
	V3 "My listening address: \"%s\"",myadst D;
	if (timeout) {
		if ((i = timeout - (now = UnixTime(0))) <= 0) {
			Fprintf(stdout,"\r\nTimeout %d reached at %d [listen].\r\n",timeout,now);
			Fprintf(stderr,"\r\n[SE]\tTimeout %d reached at %d [listen].\r\n",timeout,now);
			quit(ETIMEDOUT);
		}
		Alarm(i);
		V2 "Timeout alarm set for %d seconds from now.",i D;
	}
	if (*myadst == 0) {
		V2 "Listen at 0.0.0.0 random port." D;
		BZero(&(myad.sin_addr.s_addr),4);
		randport = 1;
	} elsif (sscanf(myadst,"%d.%d.%d.%d:%d",&x0,&x1,&x2,&x3,&myport) > 3) {
		byte p[4];
		V2 "Listen at %d.%d.%d.%d:%d",x0,x1,x2,x3,myport D;
		p[0] = x0;
		p[1] = x1;
		p[2] = x2;
		p[3] = x3;
		H5(p,4,"h-myad");
		V6 "Before BCopy()" D;
		BCopy(p,&(myad.sin_addr.s_addr),4);
		V6 "After  BCopy()" D;
		myad.sin_port = htons(myport);
	} elsif (sscanf(myadst,"%d.%d.%d.%d",&x0,&x1,&x2,&x3) > 3) {
		byte p[4];
		randport = 1;
		V2 "Listen at %d.%d.%d.%d random port",x0,x1,x2,x3 D;
		p[0] = x0;
		p[1] = x1;
		p[2] = x2;
		p[3] = x3;
		H5(p,4,"h-myad");
		V6 "Before BCopy()" D;
		BCopy(p,&(myad.sin_addr.s_addr),4);
		V6 "After  BCopy()" D;
	} elsif (sscanf(myadst,":%d",&myport) > 0) {
		V2 "Listen at *.*.*.*:%d",myport D;
		myad.sin_port = htons(myport);
	} else {
		V1 "Can't decipher listening address \"%s\"",myadst D;
		quit(EFAULT);
	}
	lsock = Socket(AF_INET,SOCK_STREAM,0);
	if (lsock < 0) {
		V1 "### Can't get datagram socket [Err %d=%s=%s]",Errinfo D;
		quit(errno);
	}
	V3 "Got TCP stream socket %d.",lsock D;
	if (randport) {
		myport = (mypid + now) & 0x07FFF;
		V3 "Seeding port search with %d.",myport D;
	}
bindloop:
	if (randport && tryports--) {
		do {myport = (myport * 17 + 1) & 0x07FFF;} while (myport < 1024);
		V3 "Trying port %d.",myport D;
		myad.sin_port = htons(myport);
	}
	if (Bind(lsock,&myad,sizeof(myad)) < 0) {
		V1 "Can't bind to address %s.%d [Err %d=%s=%s]",
			ipdot(&myad.sin_addr),ntohs( myad.sin_port),Errinfo D;
		if ((now = UnixTime(0)) < timeout) {
			V2 "Trying to bind to %s.%d ...",ipdot(&myad.sin_addr),ntohs( myad.sin_port) D;
			Sleep(bsleep);
			goto bindloop;
		}
		quit(errno);
	}
	if (randport) {
		Fprintf(stdout,"Port %d\n",myport,mypid);
		fflush(stdout);
	}
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	* Now we try to solve a major problem for things run from CGI scripts: *
	* We  must  not  hang around with an open connection to the client, or *
	* the client will think we're still trying to send it stuff.  The main *
	* process exits here, and so does the intermediate "child" process, to *
	* leave behind an orphan grandchild.  The parent and  child  exit,  so *
	* they  don't leave an open connection.  The grandchild calls dupsock, *
	* to dup all its open files to the listening socket. This should sever *
	* all ties with the client.                                            *
	* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	if (childpid = Fork()) {	/* CGI seems to require this */
		V2 "Parent of child %d.",childpid D;
		exit(0);
	} elsif (gcpid = Fork()) {
		V2 "Child." D;
		Fprintf(stdout,"Pid %d\n",gcpid);
		fflush(stdout);
		exit(0);
	} else {
		V2 "Grandchild." D;
		dupsock(lsock);
		newpname();
	}
	if (Listen(lsock,qlen) < 0) {
		V1 "Listen(%d,%d) failed [Err %d=%s=%s]",lsock,qlen,Errinfo D;
		quit(errno);
	}
	V2 "Bind to %s:%d succeeded.",ipdot(&myad.sin_addr),ntohs(myad.sin_port) D;
	while (1) {			/* Listen for client connections */
		V2 "Accept ..." D;
		cliens = sizeof(clad);
		if (timeout) {
			if ((i = timeout - (now = UnixTime(0))) <= 0) {
				Fprintf(stdout,"\r\nTimeout %d at %d [conn].\r\n",timeout,now);
				Fprintf(stderr,"\r\n[SE]\tTimeout %d at %d [conn].\r\n",timeout,now);
				quit(ETIMEDOUT);
			}
			Alarm(i);
			V2 "Timeout alarm set for %d seconds from now.",i D;
		}
		if ((ifd1 = ifd2 = Accept(lsock,&clad,&cliens)) < 0) {
			V1 "Accept(%d,%08X,%08X) failed [Err %d=%s=%s]",lsock,&clad,&cliens,Errinfo D;
			Fprintf(stdout,"\r\nAccept(%d,%08X,%08X) failed [out %d=%s=%s]",lsock,&clad,&cliens,Errinfo);  fflush(stdout);
			Fprintf(stderr,"\r\n[SE]\tAccept(%d,%08X,%08X) failed [Err %d=%s=%s]",lsock,&clad,&cliens,Errinfo);  fflush(stderr);
			quit(errno);
		}
		V2 "Accept returned socket ifd1=ifd2=%d.",ifd1 D;
		if (Fork()) {
			Sleep(lsleep);
			Close(ifd1);	/* Parent closes accept socket */
			V5 "Listen loop in parent." D;
		} else {
			newpname();		/* Calculate pname.pid for log messages */
			V5 "Listen loop in child process %d.",mypid D;
			if (ifd1 != 0) Dup2(ifd1,0);	/* Dup new socket to stdin */
			if (ifd1 != 1) Dup2(ifd1,1);	/* Dup new socket to stdout */
			if (ifd1 >= 2) Close(ifd1);	/* Close excess fild handle */
			ifd1 = 0;
			ofd1 = 1;
			Close(lsock);	/* Child closes listening socket */
			goto conn;		/* Break out of listen loop */
		}
	}
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Create socket for connecting to the remote address:port.             *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
conn:
	if (timeout) {
		if ((i = timeout - (now = UnixTime(0))) <= 0) {
			Fprintf(stdout,"\r\nTimeout %d at %d [conn].\r\n",timeout,now);
			Fprintf(stderr,"\r\n[SE]\tTimeout %d at %d [conn].\r\n",timeout,now);
			quit(ETIMEDOUT);
		}
		Alarm(i);
		V2 "Timeout alarm set for %d seconds from now.",i D;
	}
	V6 "before socket(AF_INET=%d, SOCK_STREAM=%d, 0)",AF_INET,SOCK_STREAM D;
	ifd2 = ofd2 = Socket(AF_INET, SOCK_STREAM, 0);
	if (ifd2 < 0) {
		V1 "### Can't get datagram socket [Err %d=%s=%s]",Errinfo D;
		quit(errno);
	}
	V3 "Got TCP stream socket %d.",ifd2 D;

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Construct address of socket to send to.   Gethostbyname()  retuns  a *
* structure  including the network address of the specified host.  The *
* port number is taken from the command line.                          *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	if (!svhost) {
		svhost = dfl_host;
		V3 "No svhost; using \"%s\"",svhost D;
	}
	if (sscanf(svhost,"%d.%d.%d.%d:%d",&x0,&x1,&x2,&x3,&svport) > 3) {
		byte p[4];
		V2 "%s is IP address %d.%d.%d.%d:%d",svhost,x0,x1,x2,x3,svport D;
		p[0] = x0;
		p[1] = x1;
		p[2] = x2;
		p[3] = x3;
		H5(p,4,"h-svad");
		V6 "Before BCopy()" D;
		BCopy(p,&(svad.sin_addr.s_addr),4);
		V6 "After  BCopy()" D;
		svad.sin_port = htons(svport);
	} elsif (sscanf(svhost,"%d.%d.%d.%d",&x0,&x1,&x2,&x3) > 3) {
		byte p[4];
		V2 "%s is IP address %d.%d.%d.%d:%d",svhost,x0,x1,x2,x3,svport D;
		p[0] = x0;
		p[1] = x1;
		p[2] = x2;
		p[3] = x3;
		H5(p,4,"h-svad");
		V6 "Before BCopy()" D;
		BCopy(p,&(svad.sin_addr.s_addr),4);
		V6 "After  BCopy()" D;
	} else {
		V2 "\"%s\" is host name to look up.",svhost D;
		hp = Gethostbyn(svhost);
		BCopy(hp->h_addr,&(svad.sin_addr.s_addr),hp->h_length);
		svad.sin_port = htons(svport);
		H6(&svad,sizeof(svad),"svad");
	}
	svad.sin_family = AF_INET;

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Try to make a connection to the server.                              *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	P1 "Connecting to %s:%u ...",
		ipdot(&svad.sin_addr),
		ntohs( svad.sin_port) D;
	if (Connect(ifd2,&svad,sizeof(svad)) < 0) {
		Fprintf(stdout,"\r\n%s\tCan't connect to %s:%u [Err %d=%s=%s]\r\n",
			pname,ipdot(&svad.sin_addr),ntohs( svad.sin_port),Errinfo);


		V1 "Can't connect to %s:%u [Err %d=%s=%s]",
			ipdot(&svad.sin_addr),ntohs( svad.sin_port),Errinfo D;
		Fail;
	}
	if (Vlvl>1) Fprintf(stdout,"\r%s\tConnected to %s:%u.\n",pname,ipdot(&svad.sin_addr),ntohs( svad.sin_port));
	P1 "%s\tConnected to %s:%u.",pname,ipdot(&svad.sin_addr),ntohs( svad.sin_port) D;
#ifdef NOTNOW
	if (svport == 23) {
		V3 "Send TNwont messages ..." D;
		V3 "Send TNwontx18 ..." D;
		Write(ifd2,TNwontx18,3);
		V3 "Send TNwontx01 ..." D;
		Write(ifd2,TNwontx01,3);
		V3 "Send TNwontx03 ..." D;
		Write(ifd2,TNwontx03,3);
	}
#endif
	SignalM(SIGALRM, sigALRM,"sigALRM");
	SignalM(SIGHUP,  sigHUP, "sigHUP" );
	SignalM(SIGQUIT, sigQUIT,"sigQUIT");
	SignalM(SIGTERM, sigTERM,"sigTERM");

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Now go into a read/write loop.  We hang looking for input from stdin
* or the socket, and input from either is sent to the other.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	if (rsleep > 0) Fprintf(stderr,"Wait %d seconds for replies ...\n",rsleep);
	if (timeout) {
		char *p = ctime(&timeout);
		StrTrim(p);
		V2 "[V2]\ttimeout=%d p=\"%s\"",timeout,p D;
		P3 "%s\t[P1]\tTimeout at %s.\n",pname,p D;


	}
	P1 "%s\tEscape character is '^]'.\n",pname D;
	if (Vlvl>1) Fprintf(stdout,"\r%s\tEscape character is '^]'.\r\n",pname); fflush(stdout);
	then = UnixTime(0);
	if (ifd1 < 0) ifd1 = 0;
	if (ofd1 < 0) ofd1 = 1;
	fdcount = Max(ifd1,ifd2) + 1;
	sockmask = (1 << ifd1) | (1 << ifd2);
	if (Fstat(0,&stat0)) {
		V1 "### Fstat(0,%lX) failed [Err %d=%s=%s]",&stat0,Errinfo D;
	} else {
		if (S_ISSOCK(stat0.st_mode)) {
			char* nowp = Vtime();
			V2 "Stdin is a socket." D;
			V2 "Send TNwontx22 to stdin ..." D;
			Write(0,TNwontx22,3);
			V2 "%s { %s [TNwontx22]",nowp,Dsps(TNwontx22,3) D;
#ifdef NOTNOW
			V2 "Send TNdontx22 to stdin ..." D;
			Write(0,TNdontx22,3);
			V2 "%s { %s [TNdontx22]",nowp,Dsps(TNdontx22,3) D;
#endif
		} elsif (isatty(0)) {
			V2 "Stdin is a tty." D;
			MakeRaw(0,"stdin");
			raw0 = 1;
		} else {
			V2 "Stdin is neither socket nor tty." D;
		}
	}
	nbio(0);	/* Make sure stdin is nonblocking */
loop:
	now = UnixTime(0);
	if (timeout > 0 && now > timeout) {
		V2 "Timeout" D;
		Fprintf(stdout,"Timeout.\n");
		Fprintf(stderr,"[SE]\tTimeout.\n");
		quit(ETIMEDOUT);
	}
	if (rsleep >= 0) {
		tmop = &tmout;
		if ((tmout.tv_sec = rsleep - (now - then)) < 0) {
			V2 "Out of time." D;
			Done;
		}
	} else {
		tmop = 0;
	}
	rmask = sockmask | 1 ;
	errno = 0;
	if ((i = Select(fdcount, &rmask,0,0,tmop)) > 0) {
		char* nowp = Vtime();
		V3 "Got input, rmask=%08X at %s",rmask,nowp D;
		if (rmask & 1) {
			n = Read(0, p=buf, BUF);
			if (n > 0) {
				V3 "<stdin %s",Dsps(buf,n) D;
				if ((n == 1) && (*p == 0x1D)) {
					if (raw0) MakeSane(0,"stdin");
					Write(1,"^]\n",3);
					quit(0);
				}
				V4 "%s > %s",nowp,Dsps(buf,n) D;
				Write(ofd2,buf,n);
			} elsif (n == 0) {
				V2 "EOF on stdin." D;
				quit(0);
			} else {
				V1 "Error on stdin: %d=%s=%s.",Errinfo D;
				Fprintf(stdout,"Error on stdin: %d=%s=%s.\n",Errinfo);
				Fprintf(stderr,"[SE]\tError on stdin: %d=%s=%s.\n",Errinfo);
				quit(errno);
			}
			Loop;
		} elsif (rmask & sockmask) {
			n = Read(ifd2, p=buf, BUF);
			V5 "Got from %s.%d %s\n",
				ipdot(&svad.sin_addr),
				ntohs( svad.sin_port),
				Dsps(buf,n) D;
			V4 "%s < %s",nowp,Dsps(buf,n) D;
			if (n > 0) {
				V3 "<sock: %s",Dsps(buf,n) D;
				while ((n >= 3) && (B8(*p) == FTPcmd)) {
					H4(p,3,"FTPcmd");
					p[1] = FTPwont;
					P2 "%s } %s",nowp,Dsps(p,3) D;
					Write(ofd2,p,3);
					p += 3;
					n -= 3;
				}
				Write(1,p,n);
			} elsif (n == 0) {
				V1 "EOF from %s.%d.\n",
					ipdot(&svad.sin_addr),
					ntohs( svad.sin_port) D;
				quit(0);
			} else {
				V1 "ERR %d=%s=%s from %s.%d.\n",Errinfo,
					ipdot(&svad.sin_addr),
					ntohs( svad.sin_port) D;
				quit(errno);
			}
			Loop;
		}
	}
	if (i == 0) {
		V2 "Timeout." D;
		Fprintf(stdout,"Timeout.\n"); fflush(stdout);
		Fprintf(stderr,"[SE]\tTimeerr.\n"); fflush(stderr);
		Done;
	}
	if (errno) {
		V2 "Can't read from socket [Err %d=%s=%s]",Errinfo D;
	} else {
		V2 "EOF from socket." D;
	}
fail:
done:
	Close(ifd2);
	Close(ofd2);
	quit(r);
}
usage()
{
	Fprintf(stdout,"Usage: %s [-/+options] [host] [port]\n", pname);
}

/*
* Build a new pname that includes our process id:
*/
newpname()
{	char *p, *q;
	Fenter("newpname");
	mypid = Getpid();
	for (p=q=progname; *q; q++) {if (*q == '/') {p = q + 1;}}
	if (!myname) {if (!(myname = GetChunk(strlen(p)+26,"myname"))) Fail;}
	Sprintf(myname,"%s.%d\0",p,mypid);
	V2 "myname=\"%s\"",myname D;
	pname = Vfctroot.name = myname;
fail:
	Freturn(mypid);
}
