+
+/* External protocol handler parameters from ckuus3.c */
+extern int exp_handler, exp_stderr, exp_timo;
+
+#ifdef SELECT
+#ifdef NETPTY
+
+/* The right size is 24576 */
+
+#ifndef PTY_PBUF_SIZE /* Size of buffer to read from pty */
+#define PTY_PBUF_SIZE 24576 /* and write to net. */
+#endif /* PTY_PBUF_SIZE */
+
+#ifndef PTY_TBUF_SIZE /* Size of buffer to read from net */
+#define PTY_TBUF_SIZE 24576 /* and write to pty. */
+#endif /* PTY_TBUF_SIZE */
+
+#ifdef O_NDELAY /* Whether to use nonblocking */
+#ifndef PTY_NO_NDELAY /* reads on the pseudoterminal */
+#ifndef PTY_USE_NDELAY
+#define PTY_USE_NDELAY
+#endif /* PTY_USE_NDELAY */
+#endif /* PTY_NO_NDELAY */
+#endif /* O_NDELAY */
+
+#ifndef HAVE_OPENPTY
+#ifndef USE_CKUPTY_C
+#define USE_CKUPTY_C
+#endif /* USE_CKUPTY_C */
+#endif /* HAVE_OPENPTY */
+
+VOID
+pty_make_raw(fd) int fd; {
+ int x = -23, i;
+
+#ifdef BSD44ORPOSIX /* POSIX */
+ struct termios tp;
+#else
+#ifdef ATTSV /* AT&T UNIX */
+#ifdef CK_ANSIC
+ struct termio tp = {0};
+#else
+ struct termio tp;
+#endif /* CK_ANSIC */
+#else
+ struct sgttyb tp; /* Traditional */
+#endif /* ATTSV */
+#endif /* BSD44ORPOSIX */
+
+ debug(F101,"pty_make_raw fd","",fd);
+ errno = 0;
+
+#ifdef BSD44ORPOSIX /* POSIX */
+ x = tcgetattr(fd,&tp);
+ debug(F101,"pty_make_raw tcgetattr","",x);
+#else
+#ifdef ATTSV /* AT&T UNIX */
+ x = ioctl(fd,TCGETA,&tp);
+ debug(F101,"pty_make_raw TCGETA ioctl","",x);
+#else
+ x = gtty(fd,&tp);
+ debug(F101,"pty_make_raw ttty","",x);
+#endif /* ATTSV */
+#endif /* BSD44ORPOSIX */
+ debug(F101,"pty_make_raw GET errno","",errno);
+
+#ifdef USE_CFMAKERAW
+ errno = 0;
+ cfmakeraw(&tp);
+ debug(F101,"pty_make_raw cfmakeraw errno","",errno);
+#else /* USE_CFMAKERAW */
+
+#ifdef COMMENT
+
+/* This very simple version recommended by Serg Iakolev doesn't work */
+
+ tp.c_lflag &= ~(ECHO|ICANON|IEXTEN|ISIG);
+ tp.c_iflag &= ~(BRKINT|ICRNL|INPCK|ISTRIP|IXON);
+ tp.c_cflag &= ~(CSIZE|PARENB);
+ tp.c_cflag |= CS8;
+ tp.c_oflag &= ~(OPOST);
+ tp.c_cc[VMIN] = 1;
+ tp.c_cc[VTIME] = 0;
+
+ debug(F101,"pty_make_raw 1 c_cc[] NCCS","",NCCS);
+ debug(F101,"pty_make_raw 1 iflags","",tp.c_iflag);
+ debug(F101,"pty_make_raw 1 oflags","",tp.c_oflag);
+ debug(F101,"pty_make_raw 1 lflags","",tp.c_lflag);
+ debug(F101,"pty_make_raw 1 cflags","",tp.c_cflag);
+
+#else
+#ifdef COMMENT
+/*
+ In this version we unset everything and then set only the
+ bits we know we need.
+*/
+ /* iflags */
+ tp.c_iflag = 0L;
+ tp.c_iflag |= IGNBRK;
+#ifdef IMAXBEL
+ tp.c_iflag |= IMAXBEL;
+#endif /* IMAXBEL */
+
+ /* oflags */
+ tp.c_oflag = 0L;
+
+ /* lflags */
+ tp.c_lflag = 0L;
+#ifdef NOKERNINFO
+ tp.c_lflag |= NOKERNINFO;
+#endif /* NOKERNINFO */
+
+ /* cflags */
+ tp.c_cflag = 0L;
+ tp.c_cflag |= CS8|CREAD;
+
+ for (i = 0; i < NCCS; i++) { /* No special characters */
+ tp.c_cc[i] = 0;
+ }
+#ifdef VMIN
+ tp.c_cc[VMIN] = 1; /* But always wait for input */
+#endif /* VMIN */
+ debug(F101,"pty_make_raw 2 c_cc[] NCCS","",NCCS);
+ debug(F101,"pty_make_raw 2 iflags","",tp.c_iflag);
+ debug(F101,"pty_make_raw 2 oflags","",tp.c_oflag);
+ debug(F101,"pty_make_raw 2 lflags","",tp.c_lflag);
+ debug(F101,"pty_make_raw 2 cflags","",tp.c_cflag);
+
+#else /* COMMENT */
+/*
+ In this version we set or unset every single flag explicitly. It works a
+ bit better than the simple version just above, but it's still far from
+ adequate.
+*/
+ /* iflags */
+ tp.c_iflag &= ~(PARMRK|ISTRIP|BRKINT|INLCR|IGNCR|ICRNL);
+ tp.c_iflag &= ~(INPCK|IGNPAR|IXANY|IXON|IXOFF);
+ tp.c_iflag |= IGNBRK;
+#ifdef IMAXBEL
+#ifdef COMMENT
+ tp.c_iflag |= IMAXBEL;
+#else
+ tp.c_iflag &= ~IMAXBEL;
+#endif /* COMMENT */
+#endif /* IMAXBEL */
+#ifdef IUCLC
+ tp.c_iflag &= ~IUCLC;
+#endif /* IUCLC */
+
+ /* oflags */
+#ifdef BSDLY
+ tp.c_oflag &= ~BSDLY;
+#endif /* BSDLY */
+#ifdef CRDLY
+ tp.c_oflag &= ~CRDLY;
+#endif /* CRDLY */
+#ifdef FFDLY
+ tp.c_oflag &= ~FFDLY;
+#endif /* FFDLY */
+#ifdef NLDLY
+ tp.c_oflag &= ~NLDLY;
+#endif /* NLDLY */
+#ifdef TABDLY
+ tp.c_oflag &= ~TABDLY;
+#endif /* TABDLY */
+#ifdef VTDLY
+ tp.c_oflag &= ~VTDLY;
+#endif /* VTDLY */
+#ifdef OFDEL
+ tp.c_oflag &= ~OFDEL;
+#endif /* OFDEL */
+#ifdef OFILL
+ tp.c_oflag &= ~OFILL;
+#endif /* OFILL */
+#ifdef OLCUC
+ tp.c_oflag &= ~OLCUC;
+#endif /* OLCUC */
+#ifdef CMSPAR
+ tp.c_oflag &= ~CMSPAR;
+#endif /* CMSPAR */
+ tp.c_oflag &= ~OPOST;
+#ifdef OXTABS
+ tp.c_oflag &= ~OXTABS;
+#endif /* OXTABS */
+#ifdef COMMENT
+#ifdef ONOCR
+ tp.c_oflag &= ~ONOCR; /* Maybe should be |=? */
+ tp.c_oflag |= ONOCR; /* makes no difference either way */
+#endif /* ONOCR */
+#endif /* COMMENT */
+#ifdef ONOEOT
+ tp.c_oflag &= ~ONOEOT;
+#endif /* ONOEOT */
+#ifdef ONLRET
+ tp.c_oflag &= ~ONLRET;
+#endif /* ONLRET */
+#ifdef ONLCR
+ tp.c_oflag &= ~ONLCR;
+#endif /* ONLCR */
+#ifdef OCRNL
+ tp.c_oflag &= ~OCRNL;
+#endif /* OCRNL */
+
+ /* lflags */
+ tp.c_lflag &= ~ECHO;
+#ifdef ECHOE
+ tp.c_lflag &= ~ECHOE;
+#endif /* ECHOE */
+#ifdef ECHONL
+ tp.c_lflag &= ~ECHONL;
+#endif /* ECHONL */
+#ifdef ECHOPRT
+ tp.c_lflag &= ~ECHOPRT;
+#endif /* ECHOPRT */
+#ifdef ECHOKE
+ tp.c_lflag &= ~ECHOKE;
+#endif /* ECHOKE */
+#ifdef ECHOCTL
+ tp.c_lflag &= ~ECHOCTL;
+#endif /* ECHOCTL */
+#ifdef XCASE
+ tp.c_lflag &= ~XCASE;
+#endif /* XCASE */
+#ifdef ALTWERASE
+ tp.c_lflag &= ~ALTWERASE;
+#endif /* ALTWERASE */
+#ifdef EXTPROC
+ tp.c_lflag &= ~(ICANON|ISIG|IEXTEN|TOSTOP|FLUSHO|PENDIN|EXTPROC);
+#else
+ tp.c_lflag &= ~(ICANON|ISIG|IEXTEN|TOSTOP|FLUSHO|PENDIN);
+#endif /* EXTPROC */
+#ifdef NOKERNINFO
+ tp.c_lflag |= NOKERNINFO;
+#endif /* NOKERNINFO */
+#ifndef COMMENT
+ tp.c_lflag &= ~NOFLSH; /* TRY IT THE OTHER WAY? */
+#else
+ tp.c_lflag |= NOFLSH; /* No, this way is worse */
+#endif /* COMMENT */
+
+ /* cflags */
+ tp.c_cflag &= ~(CSIZE|PARENB|PARODD);
+ tp.c_cflag |= CS8|CREAD;
+
+#ifdef MDMBUF
+ tp.c_cflag &= ~(MDMBUF);
+#else
+#ifdef CCAR_OFLOW
+ tp.c_cflag &= ~(CCAR_OFLOW); /* two names for the same thing */
+#endif /* CCAR_OFLOW */
+#endif /* MDMBUF */
+
+#ifdef CCTS_OFLOW
+ tp.c_cflag &= ~(CCTS_OFLOW);
+#endif /* CCTS_OFLOW */
+#ifdef CDSR_OFLOW
+ tp.c_cflag &= ~(CDSR_OFLOW);
+#endif /* CDSR_OFLOW */
+#ifdef CDTR_IFLOW
+ tp.c_cflag &= ~(CDTR_IFLOW);
+#endif /* CDTR_IFLOW */
+#ifdef CRTS_IFLOW
+ tp.c_cflag &= ~(CRTS_IFLOW);
+#endif /* CRTS_IFLOW */
+#ifdef CRTSXOFF
+ tp.c_cflag &= ~(CRTSXOFF);
+#endif /* CRTSXOFF */
+#ifdef CRTSCTS
+ tp.c_cflag &= ~(CRTSCTS);
+#endif /* CRTSCTS */
+#ifdef CLOCAL
+ tp.c_cflag &= ~(CLOCAL);
+#endif /* CLOCAL */
+#ifdef CSTOPB
+ tp.c_cflag &= ~(CSTOPB);
+#endif /* CSTOPB */
+#ifdef HUPCL
+ tp.c_cflag &= ~(HUPCL);
+#endif /* HUPCL */
+
+ for (i = 0; i < NCCS; i++) { /* No special characters */
+ tp.c_cc[i] = 0;
+ }
+#ifdef VMIN
+ tp.c_cc[VMIN] = 1; /* But always wait for input */
+#endif /* VMIN */
+ debug(F101,"pty_make_raw 3 c_cc[] NCCS","",NCCS);
+ debug(F101,"pty_make_raw 3 iflags","",tp.c_iflag);
+ debug(F101,"pty_make_raw 3 oflags","",tp.c_oflag);
+ debug(F101,"pty_make_raw 3 lflags","",tp.c_lflag);
+ debug(F101,"pty_make_raw 3 cflags","",tp.c_cflag);
+#endif /* COMMENT */
+#endif /* COMMENT */
+
+ errno = 0;
+#ifdef BSD44ORPOSIX /* POSIX */
+ x = tcsetattr(fd,TCSANOW,&tp);
+ debug(F101,"pty_make_raw tcsetattr","",x);
+#else
+#ifdef ATTSV /* AT&T UNIX */
+ x = ioctl(fd,TCSETA,&tp);
+ debug(F101,"pty_make_raw ioctl","",x);
+#else
+ x = stty(fd,&tp); /* Traditional */
+ debug(F101,"pty_make_raw stty","",x);
+#endif /* ATTSV */
+#endif /* BSD44ORPOSIX */
+ debug(F101,"pty_make_raw errno","",errno);
+
+#endif /* __NetBSD__ */
+}
+
+static int
+pty_chk(fd) int fd; {
+ int x, n = 0;
+ errno = 0;
+#ifdef FIONREAD
+ x = ioctl(fd, FIONREAD, &n); /* BSD and most others */
+ ckmakmsg(msgbuf,500,
+ "pty_chk ioctl FIONREAD errno=",
+ ckitoa(errno),
+ " count=",
+ ckitoa(n));
+ debug(F100,msgbuf,"",0);
+#else
+ n = rdchk(fd);
+ debug(F101,"pty_chk rdchk","",n);
+#ifdef RDCHK
+#endif /* RDCHK */
+#endif /* FIONREAD */
+ return((n > -1) ? n : 0);
+}
+
+static int
+pty_get_status(fd,pid) int fd; PID_T pid; {
+ int x, status = -1;
+ PID_T w;
+
+ debug(F101,"pty_get_status fd","",fd);
+ debug(F101,"pty_get_status pid","",pid);
+
+ if (pexitstat > -1)
+ return(pexitstat);
+
+#ifdef COMMENT
+ /* Not only unnecessary but harmful */
+ errno = 0;
+ x = kill(pty_fork_pid,0);
+ debug(F101,"pty_get_status kill value","",x);
+ debug(F101,"pty_get_status kill errno","",errno);
+ if (x > -1 && errno != ESRCH)
+ return(-1); /* Fork still there */
+ /* Fork seems to be gone */
+#endif /* COMMENT */
+
+ errno = 0;
+ x = waitpid(pty_fork_pid,&status,WNOHANG);
+ debug(F111,"pty_get_status waitpid",ckitoa(errno),x);
+ if (x <= 0 && errno == 0) {
+ debug(F101,"pty_get_status waitpid return","",-1);
+ return(-1);
+ }
+ if (x > 0) {
+ if (x != pty_fork_pid)
+ debug(F101,
+ "pty_get_status waitpid pid doesn't match","",pty_fork_pid);
+ debug(F101,"pty_get_status waitpid status","",status);
+ debug(F101,"pty_get_status waitpid errno","",errno);
+ if (WIFEXITED(status)) {
+ debug(F100,"pty_get_status WIFEXITED","",0);
+ status = WEXITSTATUS(status);
+ debug(F101,"pty_get_status fork exit status","",status);
+#ifdef COMMENT
+ end_pty();
+#endif /* COMMENT */
+ close(fd);
+ pexitstat = status;
+ } else {
+ debug(F100,"pty_get_status waitpid unexpected status","",0);
+ }
+ }
+ debug(F101,"pty_get_status return status","",status);
+ return(status);
+}
+
+/* t t p t y c m d -- Run command on pty and forward to net */
+
+/*
+ Needed for running external protocols on secure connections.
+ For example, if C-Kermit has made an SSL/TLS or Kerberos Telnet
+ connection, and then needs to transfer a file with Zmodem, which is
+ an external program, this routine reads Zmodem's output, encrypts it,
+ and then forwards it out the connection, and reads the encrypted data
+ stream coming in from the connection, decrypts it, and forwards it to
+ Zmodem.
+
+ Works like a TCP/IP port forwarder except one end is a pty rather
+ than a socket, which introduces some complications:
+
+ . On most platforms, select() always indicates the output side of
+ the pty has characters waiting to be read, even when it doesn't,
+ even when the pty process has already exited.
+
+ . Nonblocking reads must be used on the pty, because there is no
+ way on certain platforms (e.g. NetBSD) to find out how many characters
+ are available to be read (the FIONREAD ioctl always says 0). The code
+ also allows for blocking reads (if O_NDELAY and O_NONBLOCK are not
+ defined, or if PTY_NO_NDELAY is defined), but on some platforms this can
+ result in single-byte reads and writes (NetBSD again).
+
+ . Testing for "EOF" on the pty is problematic. select() never gives
+ any indication. After the pty process has exited and the fork has
+ disappeared, read() can still return with 0 bytes read but without an
+ error (NetBSD); no known test on the pty file descriptor will indicate
+ that it is no longer valid. The process ID of the pty fork can be
+ tested on some platforms (NetBSD, luckily) but not others (Solaris,
+ Linux).
+
+ On the network side, we use ttinc() and ttoc(), which, for network
+ connections, handle any active security methods.
+
+ Call with s = command.
+ Returns 0 on failure, 1 on success.
+ fdc - December 2006 - August 2007.
+
+ NOTE: This code defaults to nonblocking reads if O_NDELAY or O_NONBLOCK are
+ defined in the header files, which should be true of every recent Unix
+ platform. If this causes trouble somewhere, define PTY_NO_NDELAY, e.g. when
+ building C-Kermit:
+
+ touch ckutio.c
+ make platformname KFLAGS=-DPTY_NO_NODELAY
+*/
+static int have_pty = 0; /* Do we have a pty? */
+
+static SIGTYP (*save_sigchld)() = NULL; /* For catching SIGCHLD */
+
+static VOID
+sigchld_handler(sig) int sig; {
+ have_pty = 0; /* We don't have a pty */
+#ifdef DEBUG
+ if (save_sigchld) {
+ (VOID) signal(SIGCHLD,save_sigchld);
+ save_sigchld = NULL;
+ }
+ if (deblog) {
+ debug(F100,"**************","",0);
+ debug(F100,"SIGCHLD caught","",0);
+ debug(F100,"**************","",0);
+ }
+#endif /* DEBUG */
+}
+#define HAVE_IAC 1
+#define HAVE_CR 2
+
+int
+ttptycmd(s) char *s; {
+ CHAR tbuf[PTY_TBUF_SIZE]; /* Read from net, write to pty */
+ int tbuf_avail = 0; /* Pointers for tbuf */
+ int tbuf_written = 0;
+ static int in_state = 0; /* For TELNET IAC and NVT in */
+ static int out_prev = 0; /* Simpler scheme for out */
+
+ CHAR pbuf[PTY_PBUF_SIZE]; /* Read from pty, write to net */
+ CHAR dbuf[PTY_PBUF_SIZE + PTY_PBUF_SIZE + 1]; /* Double-size buffer */
+ int pbuf_avail = 0; /* Pointers for pbuf */
+ int pbuf_written = 0;
+
+ int ptyfd = -1; /* Pty file descriptor */
+ int have_net = 0; /* We have a network connection */
+ int pty_err = 0; /* Got error on pty */
+ int net_err = 0; /* Got error on net */
+ int status = -1; /* Pty process exit status */
+ int rc = 0; /* Our return code */
+
+ int x1 = 0, x2 = 0; /* Workers... */
+ int c, n, m, t, x; /* Workers */
+
+ long seconds_to_wait = 0L; /* select() timeout */
+ struct timeval tv, *tv2; /* For select() */
+#ifdef INTSELECT
+ int in, out, err; /* For select() */
+#else
+ fd_set in, out, err;
+#endif /* INTSELECT */
+ int nfds = 0; /* For select() */
+
+ int pset = 0, tset = 0, pnotset = 0, tnotset = 0; /* stats/debuggin only */
+ int read_net_bytes = 0; /* Stats */
+ int write_net_bytes = 0; /* Stats */
+ int read_pty_bytes = 0; /* Stats */
+ int write_pty_bytes = 0; /* Stats */
+ int is_tn = 0; /* TELNET protocol is active */
+
+ int masterfd = -1;
+ int slavefd = -1;
+#ifndef USE_CKUPTY_C
+ struct termios term;
+ struct winsize twin;
+ struct stringarray * q;
+ char ** args = NULL;
+#endif /* USE_CKUPTY_C */
+
+ in_state = 0; /* No previous character yet */
+
+ if (ttyfd == -1) {
+ printf("?Sorry, communication channel is not open\n");
+ return(0);
+ } else {
+ have_net = 1;
+ }
+ if (nopush) {
+ debug(F100,"ttptycmd fail: nopush","",0);
+ return(0);
+ }
+ if (!s) s = ""; /* Defense de bogus arguments */
+ if (!*s) return(0);
+ pexitstat = -1; /* Fork process exit status */
+
+#ifdef TNCODE
+ is_tn = (xlocal && netconn && IS_TELNET()) || /* Telnet protocol active */
+ (!xlocal && sstelnet);
+#endif /* TNCODE */
+
+ debug(F110,"ttptycmd command",s,0);
+ debug(F101,"ttptycmd ttyfd","",ttyfd);
+ debug(F101,"ttptycmd is_tn","",is_tn);
+ debug(F101,"ttptycmd ckermit pid","",getpid());
+
+#ifdef USE_CKUPTY_C
+ /* Call ckupty.c module to get and set up the pty fork */
+ /* fc 1 == "run an external protocol" */
+ debug(F100,"ttptycmd using ckupty.c","",0);
+ if (do_pty(&ptyfd,s,1) < 0) { /* Start the command on a pty */
+ debug(F100,"ttptycmd do_pty fails","",0);
+ return(0);
+ }
+ masterfd = ptyfd;
+ pty_master_fd = ptyfd;
+#ifdef COMMENT
+ slavefd = pty_slave_fd; /* This is not visible to us */
+#endif /* COMMENT */
+ debug(F111,"ttptycmd ptyfd","USE_CKUPTY_C",ptyfd);
+ debug(F111,"ttptycmd masterfd","USE_CKUPTY_C",masterfd);
+ debug(F111,"ttptycmd fork pid","USE_CKUPTY_C",pty_fork_pid);
+#ifndef SOLARIS
+ /* "ioctl inappropriate on device" for pty master */
+ pty_make_raw(masterfd);
+#endif /* SOLARIS */
+
+#else /* USE_CKUPTY_C */
+
+ debug(F100,"ttptycmd OPENPTY","",0);
+ if (tcgetattr(0, &term) == -1) { /* Get controlling terminal's modes */
+ perror("tcgetattr");
+ return(0);
+ }
+ if (ioctl(0, TIOCGWINSZ, (char *) &twin) == -1) { /* and window size */
+ perror("ioctl TIOCGWINSZ");
+ return(0);
+ }
+ if (openpty(&masterfd, &slavefd, NULL, NULL, NULL) == -1) {
+ debug(F101,"ttptycmd openpty failed errno","",errno);
+ perror("opentpy");
+ return(0);
+ }
+ debug(F101,"ttptycmd openpty masterfd","",masterfd);
+ debug(F101,"ttptycmd openpty slavefd","",slavefd);
+ pty_master_fd = masterfd;
+ pty_slave_fd = slavefd;
+ debug(F101,"ttptycmd openpty pty_master_fd","",pty_master_fd);
+
+ /* Put pty master in raw mode but let forked app control the slave */
+ pty_make_raw(masterfd);
+
+#ifdef COMMENT
+#ifdef TIOCREMOTE
+ /* TIOCREMOTE,0 = disable all termio processing */
+ x = ioctl(masterfd, TIOCREMOTE, 1);
+ debug(F111,"ttptycmd ioctl TIOCREMOTE",ckitoa(x),errno);
+#endif /* TIOCREMOTE */
+#ifdef TIOCTTY
+ /* TIOCTTY,0 = disable all termio processing */
+ x = ioctl(masterfd, TIOCTTY, 0);
+ debug(F111,"ttptycmd ioctl TIOCTTY",ckitoa(x),errno);
+#endif /* TIOCTTY */
+#endif /* COMMENT */
+
+ have_pty = 1; /* We have an open pty */
+ save_sigchld = signal(SIGCHLD, sigchld_handler); /* Catch fork quit */
+
+ pty_fork_pid = fork(); /* Make fork for external protocol */
+ debug(F101,"ttptycmd pty_fork_pid","",pty_fork_pid);
+ if (pty_fork_pid == -1) {
+ perror("fork");
+ return(0);
+ } else if (pty_fork_pid == 0) { /* In new fork */
+ int x;
+ debug(F101,"ttptycmd new fork pid","",getpid());
+ close(masterfd); /* Slave quarters no masters allowed */
+ x = setsid();
+ debug(F101,"ttptycmd new fork setsid","",x);
+ if (x == -1) {
+ perror("ttptycmd setsid");
+ exit(1);
+ }
+ signal(SIGINT,SIG_IGN); /* Let upper fork catch this */
+
+#ifdef COMMENT
+#ifdef TIOCSCTTY
+ /* Make pty the controlling terminal for the process */
+ /* THIS CAUSES AN INFINITE SIGWINCH INTERRUPT LOOP */
+ x = ioctl(slavefd, TIOCSCTTY, NULL);
+ debug(F101,"ttptycmd TIOCSCTTY","",x);
+#endif /* TIOCSCTTY */
+#endif /* COMMENT */
+
+ /* Initialize slave pty modes and size to those of our terminal */
+ if (tcsetattr(slavefd, TCSANOW, &term) == -1) {
+ perror("ttptycmd tcsetattr");
+ exit(1);
+ }
+ if (ioctl(slavefd, TIOCSWINSZ, &twin) == -1) {
+ perror("ttptycmd ioctl");
+ exit(1);
+ }
+#ifdef COMMENT
+#ifdef TIOCNOTTY
+ /* Disassociate this process from its terminal */
+ /* THIS HAS NO EFFECT */
+ x = ioctl(slavefd, TIOCNOTTY, NULL);
+ debug(F101,"ttptycmd TIOCNOTTY","",x);
+#endif /* TIOCNOTTY */
+#endif /* COMMENT */
+
+#ifdef COMMENT
+#ifdef SIGTTOU
+ /* Ignore terminal output interrupts */
+ /* THIS HAS NO EFFECT */
+ debug(F100,"ttptycmd ignoring SIGTTOU","",0);
+ signal(SIGTTOU, SIG_IGN);
+#endif /* SIGTTOU */
+#ifdef SIGTSTP
+ /* Ignore terminal output interrupts */
+ /* THIS HAS NO EFFECT */
+ debug(F100,"ttptycmd ignoring SIGTSTP","",0);
+ signal(SIGTSTP, SIG_IGN);
+#endif /* SIGTSTP */
+#endif /* COMMENT */
+
+ pty_make_raw(slavefd); /* Put it in rawmode */
+
+ errno = 0;
+ if (dup2(slavefd, STDIN_FILENO) != STDIN_FILENO ||
+ dup2(slavefd, STDOUT_FILENO) != STDOUT_FILENO) {
+ debug(F101,"ttptycmd new fork dup2 error","",errno);
+ perror("ttptycmd dup2");
+ exit(1);
+ }
+ debug(F100,"ttptycmd new fork dup2 ok","",0);
+
+ /* Parse external protocol command line */
+ q = cksplit(1,0,s,NULL,"\\%[]&$+-/=*^_@!{}/<>|.#~'`:;?",7,0,0);
+ if (!q) {
+ debug(F100,"ttptycmd cksplit failed","",0);
+ exit(1);
+ } else {
+ int i, n;
+ debug(F100,"ttptycmd cksplit ok","",0);
+ n = q->a_size;
+ args = q->a_head + 1;
+ for (i = 0; i <= n; i++) {
+ if (!args[i]) {
+ break;
+ } else {
+ /* sometimes cksplit() doesn't terminate the list */
+ if ((i == n) && args[i]) {
+ if ((int)strlen(args[i]) == 0)
+ makestr(&(args[i]),NULL);
+ }
+ }
+ }
+ }
+#ifdef COMMENT
+/*
+ Putting the slave pty in rawmode should not be necessary because the
+ external protocol program is supposed to do that itself. Yet doing this
+ here cuts down on Zmodem binary-file transmission errors by 30-50% but
+ still doesn't eliminate them.
+*/
+ pty_make_raw(STDIN_FILENO);
+ pty_make_raw(STDOUT_FILENO);
+#endif /* COMMENT */
+
+ debug(F100,"ttptycmd execvp'ing external protocol","",0);
+ execvp(args[0],args);
+ perror("execvp failed");
+ debug(F101,"ttptycmd execvp failed","",errno);
+ close(slavefd);
+ exit(1);
+ }
+ /* (there are better ways to do this...) */
+ msleep(1000); /* Make parent wait for child to be ready */
+ ptyfd = masterfd; /* We talk to the master */
+
+#endif /* USE_CKUPTY_C */
+
+ debug(F101,"ttptycmd ptyfd","",ptyfd);
+ if (ptyfd < 0) {
+ printf("?Failure to get pty\n");
+ return(-9);
+ }
+ have_pty = 1; /* We have an open pty or we wouldn't he here */
+
+ debug(F101,"ttptycmd PTY_PBUF_SIZE","",PTY_PBUF_SIZE);
+ debug(F101,"ttptycmd PTY_TBUF_SIZE","",PTY_TBUF_SIZE);
+
+#ifdef PTY_USE_NDELAY
+ /*
+ NOTE: If select() and ioctl(ptyfd,FIONREAD,&n) return true indications
+ on the pty, we don't need nonblocking reads. Performance of either
+ method seems to be about the same, so use whatever works.
+ */
+ errno = 0;
+ x = fcntl(ptyfd,F_SETFL,fcntl(ptyfd,F_GETFL, 0)|O_NDELAY);
+ ckmakmsg(msgbuf,500,
+ "ttptycmd set O_NDELAY errno=",
+ ckitoa(errno),
+ " fcntl=",
+ ckitoa(x));
+ debug(F100,msgbuf,"",0);
+#endif /* PTY_USE_NDELAY */
+
+#ifdef COMMENT
+/* Not necessary, the protocol module already did this */
+
+#ifdef USE_CFMAKERAW
+ if (tcgetattr(ttyfd, &term) > -1) {
+ cfmakeraw(&term);
+ debug(F101,"ttptycmd net cfmakeraw errno","",errno);
+ x tcsetattr(ttyfd, TCSANOW, &term);
+ debug(F101,"ttptycmd net tcsetattr","",x);
+ debug(F101,"ttptycmd net tcsetattr","",errno);
+ }
+#else
+ if (local) /* Put network connection in */
+ ttpkt(ttspeed,ttflow,ttprty); /* "packet mode". */
+ else
+ conbin((char)escchr); /* OR... pty_make_raw(0) */
+#endif /* USE_CFMAKERAW */
+#endif /* COMMENT */
+
+#ifdef TNCODE
+ if (is_tn) {
+ debug(F101,"<<< ttptycmd TELOPT_ME_BINARY","",TELOPT_ME(TELOPT_BINARY));
+ debug(F101,"<<< ttptycmd TELOPT_U_BINARY","",TELOPT_U(TELOPT_BINARY));
+ }
+#endif /* TNCODE */
+
+ debug(F101,"ttptycmd entering loop - seconds_to_wait","",seconds_to_wait);
+
+ while (have_pty || have_net) {
+ FD_ZERO(&in); /* Initialize select() structs */
+ FD_ZERO(&out);
+ FD_ZERO(&err); /* (not used because useless) */
+ nfds = -1;
+
+ debug(F101,"ttptycmd loop top have_pty","",have_pty);
+ debug(F101,"ttptycmd loop top have_net","",have_net);
+
+ /* Pty is open and we have room to read from it? */
+ if (have_pty && pbuf_avail < PTY_PBUF_SIZE) {
+ debug(F100,"ttptycmd FD_SET ptyfd in","",0);
+ FD_SET(ptyfd, &in);
+ nfds = ptyfd;
+ }
+ /* Network is open and we have room to read from it? */
+ if (have_net && have_pty && tbuf_avail < PTY_TBUF_SIZE) {
+ debug(F100,"ttptycmd FD_SET ttyfd in","",0);
+ FD_SET(ttyfd, &in);
+ if (ttyfd > nfds) nfds = ttyfd;
+ }
+ /* Pty is open and we have stuff to write to it? */
+ if (have_pty && tbuf_avail - tbuf_written > 0) {
+ debug(F100,"ttptycmd FD_SET ptyfd out","",0);
+ FD_SET (ptyfd, &out);
+ if (ptyfd > nfds) nfds = ptyfd;
+ }
+ /* Net is open and we have stuff to write to it? */
+ debug(F101,"ttptycmd pbuf_avail-pbuf_written","",
+ pbuf_avail - pbuf_written);
+ if (have_net && pbuf_avail - pbuf_written > 0) {
+ debug(F100,"ttptycmd FD_SET ttyfd out","",0);
+ FD_SET (ttyfd, &out);
+ if (ttyfd > nfds) nfds = ttyfd;
+ }
+ /* We don't use err because it's not really for errors, */
+ /* but for out of band data on the TCP socket, which, if it is */
+ /* to be handled at all, is handled in the tt*() routines */
+
+ nfds++; /* 0-based to 1-based */
+ debug(F101,"ttptycmd nfds","",nfds);
+ if (!nfds) {
+ debug(F100,"ttptycmd NO FDs set for select","",0);
+ if (have_pty) {
+ /* This is not right -- sleeping won't accomplish anything */
+ debug(F101,"ttptycmd msleep","",100);
+ msleep(100);
+ } else {
+ debug(F100,"ttptycmd no pty - quitting loop","",0);
+ break;
+ }
+ }
+ errno = 0;
+
+ if (seconds_to_wait > 0L) { /* Timeout in case nothing happens */
+ tv.tv_sec = seconds_to_wait; /* for a long time */
+ tv.tv_usec = 0L;
+ tv2 = &tv;
+ } else {
+ tv2 = NULL;
+ }
+ x = select(nfds, &in, &out, NULL, tv2);
+ debug(F101,"ttptycmd select","",x);
+ if (x < 0) {
+ if (errno == EINTR)
+ continue;
+ debug(F101,"ttptycmd select error","",errno);
+ break;
+ }
+ if (x == 0) {
+ debug(F101,"ttptycmd +++ select timeout","",seconds_to_wait);
+ if (have_pty) {
+ status = pty_get_status(ptyfd,pty_fork_pid);
+ debug(F101,"ttptycmd pty_get_status A","",status);
+ if (status > -1) pexitstat = status;
+ have_pty = 0;
+ }
+ break;
+ }
+ /* We want to handle any pending writes first to make room */
+ /* for new incoming. */
+
+ if (FD_ISSET(ttyfd, &out)) { /* Can write to net? */
+ CHAR * s;
+ s = pbuf + pbuf_written; /* Current spot for sending */
+#ifdef TNCODE
+ if (is_tn) { /* ttol() doesn't double IACs */
+ CHAR c; /* Rewrite string with IACs doubled */
+ int i;
+ s = pbuf + pbuf_written; /* Source */
+ x = 0; /* Count */
+ for (i = 0; i < pbuf_avail - pbuf_written; i++) {
+ c = s[i]; /* Next character */
+ if (c == IAC) { /* If it's IAC */
+ dbuf[x++] = c; /* put another one */
+ debug(F000,">>> QUOTED IAC","",c);
+ } else if (c != 0x0a && out_prev == 0x0d) { /* Bare CR */
+ if (!TELOPT_ME(TELOPT_BINARY)) { /* NVT rule */
+ c = 0x00;
+ dbuf[x++] = c;
+ debug(F000,">>> CR-NUL","",c);
+ }
+ }
+ dbuf[x++] = c; /* Copy and count it */
+ debug(F000,">>> char",ckitoa(in_state),c);
+ out_prev = c;
+ }
+ s = dbuf; /* New source */
+ } else
+#endif /* TNCODE */
+ x = pbuf_avail - pbuf_written; /* How much to send */
+
+ debug(F101,"ttptycmd bytes to send","",x);
+ x = ttol(s, x);
+ debug(F101,">>> ttol","",x);
+ if (x < 0) {
+ net_err++;
+ debug(F111,"ttptycmd ttol error",ckitoa(x),errno);
+ x = 0;
+ }
+ write_net_bytes += x;
+ pbuf_written += x;
+ }
+ if (FD_ISSET(ptyfd, &out)) { /* Can write to pty? */
+ debug(F100,"ttptycmd FD_ISSET ptyfd out","",0);
+ errno = 0;
+#ifndef COMMENT
+ x = write(ptyfd,tbuf + tbuf_written,tbuf_avail - tbuf_written);
+#else
+ /* Byte loop to rule out data overruns in the pty */
+ /* (it makes no difference) */
+ {
+ char *p = tbuf+tbuf_written;
+ int n = tbuf_avail - tbuf_written;
+ for (x = 0; x < n; x++) {
+ msleep(10);
+ if (write(ptyfd,&(p[x]),1) < 0)
+ break;
+ }
+ }
+#endif /* COMMENT */
+ debug(F111,"ttptycmd ptyfd write",ckitoa(errno),x);
+ if (x > 0) {
+ tbuf_written += x;
+ write_pty_bytes += x;
+ } else {
+ x = 0;
+ pty_err++;
+ if (pexitstat < 0) {
+ status = pty_get_status(ptyfd,pty_fork_pid);
+ debug(F101,"ttptycmd pty_get_status B","",status);
+ if (status > -1) pexitstat = status;
+ have_pty = 0;
+ }
+ debug(F100,"ttptycmd +++ ptyfd write error","",0);
+ }
+ }
+ if (FD_ISSET(ttyfd, &in)) { /* Can read from net? */
+ tset++;
+ debug(F100,"ttptycmd FD_ISSET ttyfd in","",0);
+ n = in_chk(1,ttyfd);
+ debug(F101,"ttptycmd in_chk(ttyfd)","",n);
+ if (n < 0 || ttyfd == -1) {
+ debug(F101,"ttptycmd +++ ttyfd errno","",errno);
+ net_err++;
+ } else if (n > 0) {
+ if (n > PTY_TBUF_SIZE - tbuf_avail)
+ n = PTY_TBUF_SIZE - tbuf_avail;
+ debug(F101,"ttptycmd net read size adjusted","",n);
+ if (xlocal && netconn) {
+ /*
+ We have to use a byte loop here because ttxin()
+ does not decrypt or, for that matter, handle Telnet.
+ */
+ int c;
+ CHAR * p;
+ p = tbuf + tbuf_avail;
+ for (x = 0; x < n; x++) {
+ if ((c = ttinc(0)) < 0)
+ break;
+ if (!is_tn) { /* Not Telnet - keep all bytes */
+ *p++ = (CHAR)c;
+ debug(F000,"<<< char","",c);
+#ifdef TNCODE
+ } else { /* Telnet - must handle IAC and NVT */
+ debug(F000,"<<< char",ckitoa(in_state),c);
+ switch (c) {
+ case 0x00: /* NUL */
+ if (in_state == HAVE_CR) {
+ debug(F000,"<<< SKIP","",c);
+ } else {
+ *p++ = c;
+ debug(F000,"<<< Keep","",c);
+ }
+ in_state = 0;
+ break;
+ case 0x0d: /* CR */
+ if (!TELOPT_U(TELOPT_BINARY))
+ in_state = HAVE_CR;
+ *p++ = c;
+ debug(F000,"<<< Keep","",c);
+ break;
+#ifdef COMMENT
+ case 0x0f: /* Ctrl-O */
+ case 0x16: /* Ctrl-V */
+ *p++ = 0x16;
+ *p++ = c;
+ debug(F000,"<<< QUOT","",c);
+ break;
+#endif /* COMMENT */
+ case 0xff: /* IAC */
+ if (in_state == HAVE_IAC) {
+ debug(F000,"<<< KEEP","",c);
+ *p++ = c;
+ in_state = 0;
+ } else {
+ debug(F000,"<<< SKIP","",c);
+ in_state = HAVE_IAC;
+ }
+ break;
+ default: /* All others */
+ if (in_state == HAVE_IAC) {
+#ifdef COMMENT
+/*
+ tn_doop() will consume an unknown number of bytes and we'll overshoot
+ the for-loop. The only Telnet command I've ever seen arrive here is
+ a Data Mark, which comes when the remote protocol exits and the remote
+ job returns to its shell prompt. On the assumption it's a 1-byte command,
+ we don't write out the IAC or the command, and we clear the state. If
+ we called tn_doop() we'd have no way of knowing how many bytes it took
+ from the input stream.
+*/
+ int xx;
+ xx = tn_doop((CHAR)c,duplex,ttinc);
+ debug(F111,"<<< DOOP",ckctoa(c),xx);
+#else
+ debug(F101,"<<< DOOP","",c);
+#endif /* COMMENT */
+ in_state = 0;
+ } else {
+ *p++ = c;
+ debug(F000,"<<< keep","",c);
+ in_state = 0;
+ }
+ }
+#endif /* TNCODE */
+ }
+ }
+ ckmakmsg(msgbuf,500,
+ "ttptycmd read net [ttinc loop] errno=",
+ ckitoa(errno),
+ " count=",
+ ckitoa(x));
+ debug(F100,msgbuf,"",0);
+ } else {
+ x = ttxin(n,tbuf+tbuf_avail);
+ debug(F101,"ttptycmd ttxin x","",x);
+ }
+
+ if (x < 0) {
+ debug(F101,"ttptycmd read net error","",x);
+ net_err++;
+ }
+ tbuf_avail += x;
+ read_net_bytes += x;
+ }
+
+ } else
+ tnotset++;
+
+ if (FD_ISSET(ptyfd, &in)) { /* Read from pty? */
+ pset++;
+ debug(F100,"ttptycmd FD_ISSET ptyfd in","",0);
+#ifdef PTY_USE_NDELAY
+ n = PTY_PBUF_SIZE;
+#else
+ /*
+ This does not work on nonblocking channels
+ on certain platforms such as NetBSD.
+ */
+ n = pty_chk(ptyfd);
+#endif /* PTY_USE_NDELAY */
+ debug(F101,"ttptycmd pty_chk() n","",n);
+
+ if (n < 0)
+ n = 0;
+ if (n > 0) {
+ if (n > PTY_PBUF_SIZE - pbuf_avail)
+ n = PTY_PBUF_SIZE - pbuf_avail;
+ debug(F101,"ttptycmd pty read size adjusted","",n);
+ errno = 0;
+ x = read(ptyfd,pbuf+pbuf_avail,n);
+#ifdef DEBUG
+ if (deblog) {
+ ckmakmsg(msgbuf,500,
+ "ttptycmd read pty errno=",
+ ckitoa(errno),
+ " count=",
+ ckitoa(x));
+ debug(F100,msgbuf,"",0);
+ }
+#endif /* DEBUG */
+
+ if (x < 0 && errno == EAGAIN)
+ x = 0;
+
+ if (x < 0) { /* This works on Solaris and Linux */
+ pty_err++; /* but not NetBSD */
+ debug(F100,"TERMINATION TEST A","",0);
+#ifdef COMMENT
+ if (errno == EIO)
+ rc = 1;
+#endif /* COMMENT */
+ if (pexitstat < 0) {
+ status = pty_get_status(ptyfd,pty_fork_pid);
+ debug(F101,"ttptycmd pty_get_status C","",status);
+ if (status > -1) pexitstat = status;
+ }
+ have_pty = 0;
+ x = 0;
+ }
+ if (x == 0 && !pty_err) { /* This works on NetBSD but */
+ debug(F100,"TERMINATION TEST B","",0);
+ status = pexitstat > -1 ? pexitstat :
+ pty_get_status(ptyfd,pty_fork_pid);
+ debug(F101,"ttptycmd pty_get_status D","",status);
+ if (status > -1) {
+ pexitstat = status;
+ pty_err++;
+ have_pty = 0;
+ } else { /* Select() lied */
+ pty_err = 0; /* pty still there but has nothing */
+ msleep(100); /* sleep a bit */
+ }
+ x = 0;
+ }
+ /* Hopefully the next two are no longer needed... */
+ if (!pty_err && (
+#ifndef PTY_USE_NDELAY
+ x < 1 || errno
+#else
+ errno != 0 && errno != EAGAIN
+#endif /* PTY_USE_NDELAY */
+ )) {
+ debug(F100,"TERMINATION TEST C","",0);
+ pty_err++;
+ debug(F101,"ttptycmd SET pty_err","",pty_err);
+ if (errno == EIO) /* errno == EIO is like EOF */
+ rc = 1;
+ if (x < 0)
+ x = 0;
+ }
+#ifdef COMMENT
+#ifdef DEBUG
+ if (deblog) {
+ pbuf[pbuf_avail + x] = '\0';
+ debug(F111,"ttptycmd added to pty buffer",
+ pbuf+pbuf_avail,x);
+ }
+#endif /* DEBUG */
+#endif /* COMMENT */
+ pbuf_avail += x;
+ read_pty_bytes += x;
+ } else { /* n == 0 with blocking reads */
+ debug(F100,
+ "PTY READ RETURNED ZERO BYTES - SHOULD NOT HAPPEN",
+ "",0);
+ }
+ } else
+ pnotset++;
+
+ /* If writes have caught up to reads, reset the buffers */
+
+ if (pbuf_written == pbuf_avail)
+ pbuf_written = pbuf_avail = 0;
+ if (tbuf_written == tbuf_avail)
+ tbuf_written = tbuf_avail = 0;
+
+ /* See if we can exit */
+
+ x1 = pbuf_avail - pbuf_written;
+ x2 = tbuf_avail - tbuf_written;
+
+ debug(F101,"ttptycmd pty_err LOOP EXIT TEST pty_err","",pty_err);
+ debug(F101,"ttptycmd pty_err LOOP EXIT TEST x1 [write to net]","",x1);
+ debug(F101,"ttptycmd pty_err LOOP EXIT TEST x2 [write to pty]","",x2);
+ debug(F101,"ttptycmd pty_err LOOP EXIT TEST rc","",rc);
+ debug(F101,"ttptycmd pty_err LOOP EXIT TEST status","",status);
+ debug(F101,"ttptycmd pty_err LOOP EXIT TEST pexitstat","",pexitstat);
+
+ if (net_err) { /* Net error? */
+ debug(F101,"ttptycmd net_err LOOP EXIT TEST net_err","",net_err);
+ if (have_net) {
+ if (local) {
+ ttclos(0);
+ printf("?Connection closed\n");
+ }
+ have_net = 0;
+ }
+ debug(F101,"ttptycmd net_err LOOP EXIT TEST x1","",x1);
+ if (x1 == 0)
+ break;
+ }
+ if (pty_err) { /* Pty error? */
+ if (have_pty) {
+ if (pexitstat < 0) {
+ status = pty_get_status(ptyfd,pty_fork_pid);
+ debug(F101,"ttptycmd pty_get_status E","",status);
+ if (status > -1) pexitstat = status;
+ }
+ have_pty = 0;
+ }
+ if (x1 == 0 && x2 == 0) { /* If buffers are caught up */
+ rc = 1; /* set preliminary return to success */
+ debug(F101,"ttptycmd pty_err LOOP EXIT TEST rc 2","",rc);
+ break; /* and exit the loop */
+ }
+ }
+ }
+ debug(F101,"ttptycmd +++ have_pty","",have_pty);
+ if (have_pty) { /* In case select() failed */
+#ifdef USE_CKUPTY_C
+ end_pty();
+ close(ptyfd);
+#else
+ close(slavefd);
+ close(masterfd);
+#endif /* USE_CKUPTY_C */
+ }
+ pty_master_fd = -1;
+ debug(F101,"ttptycmd +++ pexitstat","",pexitstat);
+ if (pexitstat < 0) { /* Try one last time to get status */
+ status = pty_get_status(ptyfd,pty_fork_pid);
+ debug(F101,"ttptycmd pty_get_status F","",status);
+ if (status > -1) pexitstat = status;
+ }
+ debug(F101,"ttptycmd +++ final pexitstat","",pexitstat);
+ if (deblog) { /* Stats for debug log */
+ debug(F101,"ttptycmd +++ pset ","",pset);
+ debug(F101,"ttptycmd +++ pnotset","",pnotset);
+ debug(F101,"ttptycmd +++ tset ","",tset);
+ debug(F101,"ttptycmd +++ tnotset","",tnotset);
+
+ debug(F101,"ttptycmd +++ read_pty_bytes","",read_pty_bytes);
+ debug(F101,"ttptycmd +++ write_net_bytes","",write_net_bytes);
+ debug(F101,"ttptycmd +++ read_net_bytes","",read_net_bytes);
+ debug(F101,"ttptycmd +++ write_pty_bytes","",write_pty_bytes);
+ }
+/*
+ If we got the external protocol's exit status from waitpid(), we use that
+ to set our return code. If not, we fall back on whatever rc was previously
+ set to, namely 1 (success) if the pty fork seemed to terminate, 0 otherwise.
+*/
+ if (save_sigchld) { /* Restore this if we changed it */
+ (VOID) signal(SIGCHLD,save_sigchld);
+ save_sigchld = NULL;
+ }
+ msleep(500);
+ x = kill(pty_fork_pid,SIGHUP); /* In case it's still there */
+ pty_fork_pid = -1;
+ debug(F101,"ttptycmd fork kill SIGHUP","",x);
+ if (pexitstat > -1)
+ rc = (pexitstat == 0 ? 1 : 0);
+ debug(F101,"ttptycmd +++ rc","",rc);
+ if (!local) { /* If in remote mode */
+ conres(); /* restore console to CBREAK mode */
+ concb((char)escchr);
+ }
+ return(rc);
+}
+#endif /* NETPTY */
+#endif /* SELECT */
+
+/* T T R U N C M D -- Redirect an external command over the connection. */
+
+/*
+ TTRUNCMD is the routine that was originally used for running external
+ protocols. It is very simple and works fine provided (a) the connection
+ is not encrypted, and (b) the external protocol uses standard i/o
+ (file descriptors 0 and 1) for file transfer.
+*/
+