OpenSolaris_b135/cmd/bnu/cu.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

#pragma ident	"%Z%%M%	%I%	%E% SMI"


/*
 * cu [-cdevice] [-sspeed] [-lline] [-bbits] [-h] [-t] [-d] [-n]
 *		[-o|-e] [-L] [-C] telno | systemname [local-cmd]
 *
 *	legal baud rates: 300, 1200, 2400, 4800, 9600, 19200, 38400.
 *
 *	-c is used to specify which device will be used for making the
 *		call.  The device argument is compared to the Type (first)
 *		field in the Devices file, and only those records that
 *		match will be used to make the call.  Either -d or -t
 *		would be more intuitive options designations, but they
 *		are already in use.
 *	-l is for specifying a line unit from the file whose
 *		name is defined in /etc/uucp/Devices.
 *	-b is for forcing the number of bits per character processed on
 *		the connection. Valid values are '7' or '8'.
 *	-h is for half-duplex (local echoing).
 *	-t is for adding CR to LF on output to remote (for terminals).
 *	-d can be used  to get some tracing & diagnostics.
 *	-o or -e is for odd or even parity on transmission to remote.
 *	-n will request the phone number from the user.
 *	-L will cause cu to go through the login chat sequence in the
 *		Systems file.
 *	-C will cause cu to run the local command specified at the end
 *		of the command line, instead of entering interactive mode.
 *	Telno is a telephone number with `=' for secondary dial-tone.
 *	If "-l dev" is used, speed is taken from /etc/uucp/Devices.
 *	Only systemnames that are included in /etc/uucp/Systems may
 *	be used.
 *
 *	Escape with `~' at beginning of line:
 *
 *	~.	quit,
 *
 *	~![cmd]			execute shell (or 'cmd') locally,
 *
 *	~$cmd			execute 'cmd' locally, stdout to remote,
 *
 *	~%break	(alias ~%b)	transmit BREAK to remote,
 *	~%cd [dir]		change directory to $HOME (or 'dir'),
 *	~%debug (alias ~%d)	toggles on/off the program debug trace,
 *	~%divert		allow unsolicited diversions to files,
 *	~%ifc (alias ~%nostop)	toggles on/off the DC3/DC1 input control,
 *	~%ofc (alias ~%noostop)	toggles on/off the DC3/DC1 output control,
 *		(certain remote systems cannot cope with DC3 or DC1).
 *	~%old			recognize old style silent diversions,
 *	~%put from [to]		put file from local to remote,
 *	~%take from [to]	take file from remote to local,
 *
 *	~l			dump communication line ioctl settings,
 *	~t			dump terminal ioctl settings.
 *
 *	Silent diversions are enabled only for use with the ~%take
 *	command by default for security reasons. Unsolicited diversions
 *	may be enabled using the ~%divert toggle. The 'new-style'
 *	diversion syntax is "~[local]>:filename", and is terminaled
 *	by "~[local]>", where 'local' is the nodename of the local
 *	system. This enables ~%take to operate properly when cu
 *	is used over multiple hops. 'old-style' diversion syntax may
 *	be enabled using the ~%old toggle. ('old-style' diversion
 *	should be avoided!)
 *
 *	Cu no longer uses dial.c to reach the remote.  Instead, cu places
 *	a telephone call to a remote system through the uucp conn() routine
 *	when the user picks the systemname option or through altconn()--
 *	which bypasses /etc/uucp/Systems -- if a telno or direct
 *	line is chosen. The line termio attributes are set in fixline(),
 *	before the remote connection is made.  As a device-lockout semaphore
 *	mechanism, uucp creates an entry in /var/spool/locks whose name is
 *	LK.<MAJ>.<maj>.<min> where MAJ is the major device of the
 *	filesystem containing the device, and <maj> and <min> are the
 *	major and minor of the device.
 *	When cu terminates, for whatever reason, cleanup() must be
 *	called to "release" the device, and clean up entries from
 *	the locks directory.  Cu runs with uucp ownership, and thus provides
 *	extra insurance that lock files will not be left around.
 */

#include "uucp.h"
#include <locale.h>
#include <stropts.h>

#define	MID	BUFSIZ/2	/* mnemonic */
#define	RUB	'\177'		/* mnemonic */
#define	XON	'\21'		/* mnemonic */
#define	XOFF	'\23'		/* mnemonic */
#define	TTYIN	0		/* mnemonic */
#define	TTYOUT	1		/* mnemonic */
#define	TTYERR	2		/* mnemonic */
#define	HUNGUP  2
#define	YES	1		/* mnemonic */
#define	NO	0		/* mnemonic */
#define	IOERR	4		/* exit code */
#define	MAXPATH	100
#define	NPL	50

int Sflag=0;
int Cn;				/*fd for remote comm line */
jmp_buf Sjbuf;			/*needed by uucp routines*/

/*	io buffering	*/
/*	Wiobuf contains, in effect, 3 write buffers (to remote, to tty	*/
/*	stdout, and to tty stderr) and Riobuf contains 2 read buffers	*/
/*	(from remote, from tty).  [WR]IOFD decides which one to use.	*/
/*	[RW]iop holds current position in each.				*/
#define	WIOFD(fd)	(fd == TTYOUT ? 0 : (fd == Cn ? 1 : 2))
#define	RIOFD(fd)	(fd == TTYIN ? 0 : 1)
#define	WMASK(fd)	(fd == Cn ? line_mask : term_mask)
#define	RMASK(fd)	(fd == Cn ? line_mask : term_mask)
#define	WRIOBSZ 256
static char Riobuf[2*WRIOBSZ];
static char Wiobuf[3*WRIOBSZ];
static int Riocnt[2] = {0, 0};
static char *Riop[2];
static char *Wiop[3];

extern int optind;		/* variable in getopt() */

extern char
	*optarg;

static struct call Cucall;	/* call structure for altconn()	*/

static int Saved_tty;		/* was TCGETAW of _Tv0 successful?	*/
static int Saved_termios;	/* was TCGETSW of _Tv0 successful?	*/
static struct termio _Tv, _Tv0;	/* for saving, changing TTY atributes */
static struct termios _Tv0s;	/* for saving, changing TTY atributes */
static struct termio _Lv;	/* attributes for the line to remote */
static struct termios _Lvs;	/* attributes for the line to remote */
static char prompt[BUFSIZ]= "[";
static struct utsname utsn;
static int command_line_hups = 0;

static char filename[BUFSIZ] = "/dev/null";

static char
	_Cxc,			/* place into which we do character io*/
	_Tintr,			/* current input INTR */
	_Tquit,			/* current input QUIT */
	_Terase,		/* current input ERASE */
	_Tkill,			/* current input KILL */
	_Teol,			/* current secondary input EOL */
	_Myeof,			/* current input EOF */
	term_mask,		/* mask value for local terminal */
	line_mask;		/* mask value for remote line */
				/* either '0177' or '0377' */

int
	Echoe,			/* save users ECHOE bit */
	Echok,			/* save users ECHOK bit */
	Intrupt=NO,		/* interrupt indicator */
	Ifc=YES,		/* NO means remote can't XON/XOFF */
	Ofc=YES,		/* NO means local can't XON/XOFF */
	Rtn_code=0,		/* default return code */
	Divert=NO,		/* don't allow unsolicited redirection */
	OldStyle=NO,		/* don't handle old '~>:filename' syntax */
				/* this will be mandatory in SVR4.1 */
	Takeflag=NO,		/* indicates a ~%take is in progress */
	Dologin=NO,		/* go through the login chat sequence */
	Docmd=NO;		/* execute command instead of interactive cu */

EXTERN int			/* These are initialized in line.c */
	Terminal,		/* flag; remote is a terminal */
	Oddflag,		/* flag- odd parity option*/
	Evenflag,		/* flag- even parity option*/
	Duplex,			/* Unix= full duplex=YES; half = NO */
	term_8bit,		/* is terminal set for 8 bit processing */
	line_8bit;		/* is line set for 8 bit processing */

EXTERN int clear_hup();

pid_t
	Child,			/* pid for receive process */
	Shell;			/* pid for escape process */

static pid_t
	dofork();		/* fork and return pid */

static int
	r_char(),		/* local io routine */
	w_char(),		/* local io routine */
	wioflsh();

static void
	_onintrpt(),		/* interrupt routines */
	_rcvdead(),
	_quit(),
	_bye();

extern void	cleanup();
extern void	tdmp();
extern int conn(), altconn(), transmit(), tilda();

static void
	recfork(),
	sysname(),
	blckcnt(),
	_flush(),
	_shell(),
	_dopercen(),
	_receive(),
	_mode(),
	_w_str();

extern char *Myline;	/* flag to force the requested line to be used  */
extern char *Mytype;	/* flag to force requested line type to be used
			 * rddev() will compare the string to the D_TYPE
			 * (first) field of the Devices record and skip any
			 * records where they are not equal. Mytype is set
			 * to point to the argument of the -c option from
			 * the command line. */
static char *P_USAGE= "Usage: %s [-dhtnLC] [-c device] [-s speed] [-l line] [-b 7|8]\n\t[-o | -e] telno | systemname [local-cmd]\n";
static char *P_CON_FAILED = "Connect failed: %s\r\n";
static char *P_Ct_OPEN = "Cannot open: %s\r\n";
static char *P_LINE_GONE = "Remote line gone\r\n";
static char *P_Ct_EXSH = "Can't execute shell\r\n";
static char *P_Ct_DIVERT = "Can't divert to %s\r\n";
static char *P_Ct_UNDIVERT = "Can't end diversion to %s\r\n";
static char *P_Bad_DIVERT = "Won't divert to %s. Unsolicited.\r\n";
static char *P_STARTWITH = "Use `~~' to start line with `~'\r\n";
static char *P_CNTAFTER = "File transmission interrupted after %ld bytes.\r\n";
static char *P_CNTLINES = "%d lines/";
static char *P_CNTCHAR = "%ld characters\r\n";
static char *P_FILEINTR = "File transmission interrupted\r\n";
static char *P_Ct_FK = "Can't fork -- try later\r\n";
static char *P_Ct_SPECIAL = "r\nCan't transmit special character `%#o'\r\n";
static char *P_TOOLONG = "\nLine too long\r\n";
static char *P_IOERR = "r\nIO error\r\n";
static char *P_USECMD = "Use `~$'cmd \r\n";
#ifdef forfutureuse
static char *P_USEPLUSCMD ="Use `~+'cmd \r\n";
#endif
#ifdef u3b
static char *P_NOTERMSTAT = "Can't get terminal status\r\n";
static char *P_3BCONSOLE = "Sorry, you can't cu from a 3B console\r\n";
#endif
static char *P_TELLENGTH = "Telno cannot exceed 58 digits!\r\n";

/***************************************************************
 *	main: get command line args, establish connection, and fork.
 *	Child invokes "receive" to read from remote & write to TTY.
 *	Main line invokes "transmit" to read TTY & write to remote.
 ***************************************************************/

int
main(argc, argv)
int argc;
char *argv[];
{
    extern void setservice();
    extern int sysaccess();
    char s[MAXPH];
    char *string;
    int i;
    int errflag=0;
    int lflag=0;
    int nflag=0;
    int systemname = 0;
    char vdisable;

    /* Set locale environment variables local definitions */
    (void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it wasn't */
#endif
    (void) textdomain(TEXT_DOMAIN);

    Riop[0] = &Riobuf[0];
    Riop[1] = &Riobuf[WRIOBSZ];
    Wiop[0] = &Wiobuf[0];
    Wiop[1] = &Wiobuf[WRIOBSZ];
    Wiop[2] = &Wiobuf[2*WRIOBSZ];

    Verbose = 1;		/*for uucp callers,  dialers feedback*/
    if ((string = strrchr(argv[0], '/')) != NULL)
	string++;
    else
	string = argv[0];
    if (strlcpy(Progname, string, NAMESIZE) >= NAMESIZE) {
	errno = ENAMETOOLONG;
	perror("cu");
	exit(1);
    }
    setservice(Progname);
    if ( sysaccess(EACCESS_SYSTEMS) != 0 ) {
	(void)fprintf(stderr,
	     gettext("%s: Cannot read Systems files\n"), Progname);
	exit(1);
    }
    if ( sysaccess(EACCESS_DEVICES) != 0 ) {
	(void)fprintf(stderr,
	     gettext("%s: Cannot read Devices files\n"), Progname);
	exit(1);
    }
    if ( sysaccess(EACCESS_DIALERS) != 0 ) {
	(void)fprintf(stderr,
	    gettext("%s: Cannot read Dialers files\n"), Progname);
	exit(1);
    }

    Cucall.speed = "Any";	/*default speed*/
    Cucall.line = CNULL;
    Cucall.telno = CNULL;
    Cucall.type = CNULL;

/*Flags for -h, -t, -e, and -o options set here; corresponding line attributes*/
/*are set in fixline() in culine.c before remote connection is made	   */

    while((i = getopt(argc, argv, "dhteons:l:c:b:LCH")) != EOF)
	switch(i) {
	    case 'd':
		Debug = 9; /*turns on uucp debugging-level 9*/
		break;
	    case 'h':
		Duplex  = NO;
		Ifc = NO;
		Ofc = NO;
		break;
	    case 't':
		Terminal = YES;
		break;
	    case 'e':
		if ( Oddflag ) {
		    (void)fprintf(stderr,
			gettext("%s: Cannot have both even and odd parity\n"),
			argv[0]);
		    exit(1);
		}
		Evenflag = 1;
		break;
	    case 'o':
		if ( Evenflag ) {
		    (void)fprintf(stderr,
			gettext("%s: Cannot have both even and odd parity\n"),
			argv[0]);
		    exit(1);
		}
		Oddflag = 1;
		break;
	    case 'n':
		nflag++;
		printf(gettext("Please enter the number: "));
		/* Read line from stdin, remove trailing newline, if any */
		if (fgets(s, sizeof(s), stdin) != NULL &&
			strchr(s, '\n') != NULL)
		   s[strlen(s)-1] = '\0';
		break;
	    case 's':
		Sflag++;
		Cucall.speed = optarg;
		break;
	    case 'l':
		lflag++;
		Cucall.line = optarg;
		break;
	    case 'c':
		Cucall.type = optarg;
		Mytype = optarg;
		break;
	    case 'b':
		line_8bit = ((*optarg=='7') ? NO : ((*optarg=='8') ? YES : -1));
		if ( line_8bit == -1 ) {
		    (void) fprintf(stderr,
			gettext("%s: b option value must be '7' or '8'\n"),
			argv[0]);
		    exit(1);
		}
		break;
	    case 'L':
		Dologin++;
		break;
	    case 'C':
		Docmd++;
		break;
	    case 'H':
		command_line_hups++;
		break;
	    case '?':
		++errflag;
	}

#ifdef  u3b
    {
    struct stat buff;
    if(fstat(TTYIN, &buff) < 0) {
	VERBOSE(gettext(P_NOTERMSTAT),"");
	exit(1);
    } else if ( (buff.st_mode & S_IFMT) == S_IFCHR && buff.st_rdev == 0 ) {
	VERBOSE(gettext(P_3BCONSOLE),"");
	exit(1);
	}
    }
#endif

    if((optind < argc && optind > 0) || (nflag && optind > 0)) {
	if(nflag)
	    string=s;
	else
	    string = strdup(argv[optind++]);
	Cucall.telno = string;
	if ( strlen(string) != strspn(string, "0123456789=-*#") ) {
	    /* if it's not a legitimate telno, then it should be a systemname */
	    if ( nflag ) {
		(void)fprintf(stderr, gettext("%s: Bad phone number %s\n"),
				argv[0], string);
		(void) fprintf(stderr, gettext("Phone numbers may contain "
		    "only the digits 0 through 9 and the special\n"
		    "characters =, -, * and #.\n"));
		exit(1);
	    }
	    systemname++;
	}
    } else
	if(Cucall.line == CNULL)   /*if none of above, must be direct */
	    ++errflag;

    if(errflag) {
	VERBOSE(gettext(P_USAGE), argv[0]);
	exit(1);
    }

    if ((Cucall.telno != CNULL) &&
		(strlen(Cucall.telno) >= (size_t)(MAXPH - 1))) {
	VERBOSE(gettext(P_TELLENGTH),"");
	exit(0);
    }

    /* save initial tty state */
    if (!(Saved_termios = ( ioctl(TTYIN, TCGETS, &_Tv0s) >= 0 ))) {
	Saved_tty = ( ioctl(TTYIN, TCGETA, &_Tv0) == 0 );
	_Tv0s.c_lflag = _Tv0.c_lflag;
	_Tv0s.c_oflag = _Tv0.c_oflag;
	_Tv0s.c_iflag = _Tv0.c_iflag;
	_Tv0s.c_cflag = _Tv0.c_cflag;
	for(i = 0; i < NCC; i++)
		_Tv0s.c_cc[i] = _Tv0.c_cc[i];
    }

    if (Saved_termios || Saved_tty) {
	char *p;

	/*
	 * We consider the terminal to be in 8 bit mode only if cs8 is set,
	 * istrip is not set, and we're not in the "C" locale.  The "C"
	 * locale is by definition 7 bit only.  This provides reasonable
	 * compatibility when running in the "C" locale (currently the default)
	 * and connecting to other systems, which are most often 7 bit systems.
	 */
	term_8bit = ( (_Tv0s.c_cflag & CS8) && !(_Tv0s.c_iflag & ISTRIP) &&
	  ((p = setlocale(LC_CTYPE, NULL)) != NULL) && (strcmp(p, "C") != 0) );
	if ( !Oddflag && !Evenflag )
	    if (_Tv0s.c_cflag & PARENB)
		if (_Tv0s.c_cflag & PARODD)
		    Oddflag = 1;
		else
		    Evenflag = 1;
    }

    if (line_8bit == -1)
	line_8bit = term_8bit;

    term_mask = ( term_8bit ? 0377 : 0177 );
    line_mask = ( line_8bit ? 0377 : 0177 );

    /* if not set, use the POSIX disabled designation */
#ifdef _POSIX_VDISABLE
    vdisable = _POSIX_VDISABLE;
#else
    vdisable = fpathconf(TTYIN, _PC_VDISABLE);
#endif
    _Tintr = _Tv0s.c_cc[VINTR] ? _Tv0s.c_cc[VINTR] : vdisable;
    _Tquit = _Tv0s.c_cc[VQUIT] ? _Tv0s.c_cc[VQUIT] : vdisable;
    _Terase = _Tv0s.c_cc[VERASE] ? _Tv0s.c_cc[VERASE] : vdisable;
    _Tkill = _Tv0s.c_cc[VKILL] ? _Tv0s.c_cc[VKILL] : vdisable;
    _Teol = _Tv0s.c_cc[VEOL] ? _Tv0s.c_cc[VEOL] : vdisable;
    _Myeof = _Tv0s.c_cc[VEOF] ? _Tv0s.c_cc[VEOF] : '\04';
    Echoe = _Tv0s.c_lflag & ECHOE;
    Echok = _Tv0s.c_lflag & ECHOK;

    (void)signal(SIGHUP, cleanup);
    (void)signal(SIGQUIT, cleanup);
    (void)signal(SIGINT, cleanup);

/* place call to system; if "cu systemname", use conn() from uucp
   directly.  Otherwise, use altconn() which dummies in the
   Systems file line.
*/

    if(systemname) {
	if ( lflag )
	    (void)fprintf(stderr,
	        gettext("%s: Warning: -l flag ignored when system name used\n"),
	        argv[0]);
	if ( Sflag )
	    (void)fprintf(stderr,
	        gettext("%s: Warning: -s flag ignored when system name used\n"),
	        argv[0]);
	Cn = conn(string);
	if ( (Cn < 0) && (Cucall.type != CNULL) )
	    Cn = altconn(&Cucall);
    } else
	Cn = altconn(&Cucall);

    if(Cn < 0) {
	VERBOSE(gettext(P_CON_FAILED),UERRORTEXT);
	cleanup(-Cn);
    } else {
	struct stat Cnsbuf;
	if ( fstat(Cn, &Cnsbuf) == 0 )
	    Dev_mode = Cnsbuf.st_mode;
	else
	    Dev_mode = R_DEVICEMODE;
	fchmod(Cn, M_DEVICEMODE);
    }

    if ((Docmd) && (argv[optind] == NULL)) {
        (void) fprintf(stderr,gettext("cu: local cmd is required, -C is ignored.\n"));
        VERBOSE(gettext(P_USAGE), argv[0]);
        Docmd=NO;
    }

    if (!Docmd) {
	Euid = geteuid();
	if((setuid(getuid()) < 0) || (setgid(getgid()) < 0)) {
	    VERBOSE("Unable to setuid/gid\n%s", "");
	    cleanup(101);
	}
    }

    if(Debug)
	tdmp(Cn);

    /* At this point succeeded in getting an open communication line	*/
    /* Conn() takes care of closing the Systems file			*/

    if (!Docmd) {
	(void)signal(SIGINT,_onintrpt);
	_mode(1);			/* put terminal in `raw' mode */
	VERBOSE("Connected\007\r\n%s", "");	/*bell!*/

	/* must catch signals before fork.  if not and if _receive()	*/
	/* fails in just the right (wrong?) way, _rcvdead() can be	*/
	/* called and do "kill(getppid(),SIGUSR1);" before parent	*/
	/* has done calls to signal() after recfork().			*/
	(void)signal(SIGUSR1, _bye);
	(void)signal(SIGHUP, cleanup);
	(void)signal(SIGQUIT, _onintrpt);

	sysname(&prompt[1]);	/* set up system name prompt */
	(void) strcat(prompt, "]");

	recfork();		/* checks for child == 0 */

	if(Child > 0) {
	    /*
	     * Because the child counts hangups for the -H flag,
	     * and because we fork a new child when doing (e.g.)
	     * ~%take, we assume the first child we fork has
	     * processed all the hangups and we reset the count here.
	     * We really should pass the remaining count back from
	     * the child to the parent when we kill the child.
	     */
	    command_line_hups = 0;
	    Rtn_code = transmit();
	    _quit(Rtn_code);
	    /*NOTREACHED*/
	}
    } else {
	/*
	 * Fork a child to run the specified command,
	 * wait for it to finish, and clean up.
	 */
	Child = dofork();
	if (Child == 0) {
	    close(0);
	    close(1);
	    dup(Cn);
	    dup(Cn);
	    close(Cn);
	    setgid(getgid());
	    setuid(getuid());
	    execvp(argv[optind], &argv[optind]);
	    exit(-1);
	    /* NOTREACHED */
	}
	wait(0);
	/* XXX - should return wait status as our exit code */
    }
    cleanup(Cn);
    /*NOTREACHED*/
	return (0);
}

/*
 *	Kill the present child, if it exists, then fork a new one.
 */

static void
recfork()
{
    int ret, status;
    if (Child) {
	kill(Child, SIGKILL);
	while ( (ret = wait(&status)) != Child )
	    if (ret == -1 && errno != EINTR)
		break;
    }
    Child = dofork();
    if(Child == 0) {
	(void)signal(SIGUSR1, SIG_DFL);
	(void)signal(SIGHUP, _rcvdead);
	(void)signal(SIGQUIT, SIG_IGN);
	(void)signal(SIGINT, SIG_IGN);

	_receive();	/* This should run until killed */
	/*NOTREACHED*/
    }
    return;
}

/***************************************************************
 *	transmit: copy stdin to remote fd, except:
 *	~.	terminate
 *	~!	local login-style shell
 *	~!cmd	execute cmd locally
 *	~$proc	execute proc locally, send output to line
 *	~%cmd	execute builtin cmd (put, take, or break)
 ****************************************************************/
#ifdef forfutureuse
 /*****************************************************************
  *	~+proc	execute locally, with stdout to and stdin from line.
  ******************************************************************/
#endif

int
transmit()
{
    char b[BUFSIZ];
    char *p;
    int escape;
    int id = 0;  /* flag for systemname prompt on tilda escape */

    CDEBUG(4,"transmit started\n\r%s", "");

    /* In main loop, always waiting to read characters from	*/
    /* keyboard; writes characters to remote, or to TTYOUT	*/
    /* on a tilda escape					*/

    for (;;) {
	p = b;
	while(r_char(TTYIN) == YES) {
	    if(p == b)  	/* Escape on leading  ~    */
		escape = (_Cxc == '~');
	    if(p == b+1)   	/* But not on leading ~~   */
		escape &= (_Cxc != '~');
	    if(escape) {
		 if(_Cxc == '\n' || _Cxc == '\r' || _Cxc == _Teol) {
		    *p = '\0';
		    if(tilda(b+1) == YES)
			return(0);
		    id = 0;
		    break;
		}
		if(_Cxc == _Tintr || _Cxc == _Tkill || _Cxc == _Tquit ||
			(Intrupt && _Cxc == '\0')) {
		    if(_Cxc == _Tkill) {
			if(Echok)
			    VERBOSE("\r\n%s", "");
		    } else {
			_Cxc = '\r';
			if( w_char(Cn) == NO) {
			    VERBOSE(gettext(P_LINE_GONE),"");
			    return(IOERR);
			}
			id=0;
		    }
		    break;
		}
		if((p == b+1) && (_Cxc != _Terase) && (!id)) {
		    id = 1;
		    VERBOSE("%s", prompt);
		}
		if(_Cxc == _Terase) {
		    p = (--p < b)? b:p;
		    if(p > b)
			if(Echoe) {
			    VERBOSE("\b \b%s", "");
			} else
			    (void)w_char(TTYOUT);
		} else {
		    (void)w_char(TTYOUT);
		    if(p-b < BUFSIZ)
			*p++ = _Cxc;
		    else {
			VERBOSE(gettext(P_TOOLONG),"");
			break;
		    }
		}
    /*not a tilda escape command*/
	    } else {
		if(Intrupt && _Cxc == '\0') {
		    CDEBUG(4,"got break in transmit\n\r%s", "");
		    Intrupt = NO;
		    (*genbrk)(Cn);
		    _flush();
		    break;
		}
		if(w_char(Cn) == NO) {
		    VERBOSE(gettext(P_LINE_GONE),"");
		    return(IOERR);
		}
		if(Duplex == NO) {
		    if((w_char(TTYERR) == NO) || (wioflsh(TTYERR) == NO))
			return(IOERR);
		}
		if ((_Cxc == _Tintr) || (_Cxc == _Tquit) ||
		     ( (p==b) && (_Cxc == _Myeof) ) ) {
		    CDEBUG(4,"got a tintr\n\r%s", "");
		    _flush();
		    break;
		}
		if(_Cxc == '\n' || _Cxc == '\r' ||
		    _Cxc == _Teol || _Cxc == _Tkill) {
		    id=0;
		    Takeflag = NO;
		    break;
		}
		p = (char*)0;
	    }
	}
    }
}

/***************************************************************
 *	routine to halt input from remote and flush buffers
 ***************************************************************/
static void
_flush()
{
    (void)ioctl(TTYOUT, TCXONC, 0);	/* stop tty output */
    (void)ioctl(Cn, TCFLSH, 0);		/* flush remote input */
    (void)ioctl(TTYOUT, TCFLSH, 1);	/* flush tty output */
    (void)ioctl(TTYOUT, TCXONC, 1);	/* restart tty output */
    if(Takeflag == NO) {
	return;		/* didn't interupt file transmission */
    }
    VERBOSE(gettext(P_FILEINTR),"");
    (void)sleep(3);
    _w_str("echo '\n~>\n';mesg y;stty echo\n");
    Takeflag = NO;
    return;
}

/**************************************************************
 *	command interpreter for escape lines
 **************************************************************/
int
tilda(cmd)
char	*cmd;
{

    VERBOSE("\r\n%s", "");
    CDEBUG(4,"call tilda(%s)\r\n", cmd);

    switch(cmd[0]) {
	case CSUSP:
	case CDSUSP:
	    _mode(0);
	    kill(cmd[0] == CDSUSP ? getpid() : (pid_t) 0, SIGTSTP);
	    _mode(1);
	    break;
	case '.':
	    if(Cucall.telno == CNULL)
		if(cmd[1] != '.') {
		    _w_str("\04\04\04\04\04");
		    if (Child)
			kill(Child, SIGKILL);
		    if (ioctl (Cn, TCGETS, &_Lvs) < 0) {
		    	(void) ioctl (Cn, TCGETA, &_Lv);
		    	/* speed to zero for hangup */
		    	_Lv.c_cflag = 0;
		    	(void) ioctl (Cn, TCSETAW, &_Lv);
		    } else {
		    	/* speed to zero for hangup */
			_Lvs.c_cflag &= 0xffff0000;
			cfsetospeed(&_Lvs, B0);
		    	(void) ioctl (Cn, TCSETSW, &_Lvs);
		    }
		    (void) sleep (2);
		}
	    return(YES);
	case '!':
	    _shell(cmd);	/* local shell */
	    VERBOSE("\r%c\r\n", *cmd);
	    VERBOSE("(continue)%s", "");
	    break;
	case '$':
	    if(cmd[1] == '\0') {
		VERBOSE(gettext(P_USECMD),"");
		VERBOSE("(continue)%s", "");
	    } else {
		_shell(cmd);	/*Local shell  */
		VERBOSE("\r%c\r\n", *cmd);
	    }
	    break;

#ifdef forfutureuse
	case '+':
	    if(cmd[1] == '\0') {
		VERBOSE(gettext(P_USEPLUSCMD), "");
		VERBOSE("(continue)%s", "");
	    } else {
		if (*cmd == '+')
			  /* must suspend receive to give*/
			  /*remote out to stdin of cmd */
		    kill(Child, SIGKILL);
		    _shell(cmd);	/* Local shell */
		if (*cmd == '+')
		    recfork();
		VERBOSE("\r%c\r\n", *cmd);
	    }
	    break;
#endif
	case '%':
	    _dopercen(++cmd);
	    break;

	case 't':
	    tdmp(TTYIN);
	    VERBOSE("(continue)%s", "");
	    break;
	case 'l':
	    tdmp(Cn);
	    VERBOSE("(continue)%s", "");
	    break;

	default:
	    VERBOSE(gettext(P_STARTWITH),"");
	    VERBOSE("(continue)%s", "");
	    break;
    }
    return(NO);
}

/***************************************************************
 *	The routine "shell" takes an argument starting with
 *	either "!" or "$", and terminated with '\0'.
 *	If $arg, arg is the name of a local shell file which
 *	is executed and its output is passed to the remote.
 *	If !arg, we escape to a local shell to execute arg
 *	with output to TTY, and if arg is null, escape to
 *	a local shell and blind the remote line.  In either
 *	case, '^D' will kill the escape status.
 **************************************************************/

#ifdef forfutureuse
/***************************************************************
 *	Another argument to the routine "shell" may be +.  If +arg,
 *	arg is the name of a local shell file which is executed with
 *	stdin from and stdout to the remote.
 **************************************************************/
#endif

static void
_shell(str)
char	*str;
{
    pid_t	fk, w_ret;
    void	(*xx)(), (*yy)();

    CDEBUG(4,"call _shell(%s)\r\n", str);
    fk = dofork();
    if(fk < 0)
	return;
    Shell = fk;
    _mode(0);	/* restore normal tty attributes */
    xx = signal(SIGINT, SIG_IGN);
    yy = signal(SIGQUIT, SIG_IGN);
    if(fk == 0) {
	char *shell;

	if( (shell = getenv("SHELL")) == NULL)
	    /* use default if user's shell is not set */
	    shell = SHELL;
	(void)close(TTYOUT);

	/***********************************************
	 * Hook-up our "standard output"
	 * to either the tty for '!' or the line
	 * for '$'  as appropriate
	 ***********************************************/
#ifdef forfutureuse

	/************************************************
	 * Or to the line for '+'.
	 **********************************************/
#endif

	(void)fcntl((*str == '!')? TTYERR:Cn,F_DUPFD,TTYOUT);

#ifdef forfutureuse
	/*************************************************
	 * Hook-up "standard input" to the line for '+'.
	 * **********************************************/
	if (*str == '+') {
	    (void)close(TTYIN);
	    (void)fcntl(Cn,F_DUPFD,TTYIN);
	    }
#endif

	/***********************************************
	 * Hook-up our "standard input"
	 * to the tty for '!' and '$'.
	 ***********************************************/

	(void)close(Cn);   	/*parent still has Cn*/
	(void)signal(SIGINT, SIG_DFL);
	(void)signal(SIGHUP, SIG_DFL);
	(void)signal(SIGQUIT, SIG_DFL);
	(void)signal(SIGUSR1, SIG_DFL);
	if(*++str == '\0')
	    (void)execl(shell,shell,(char*) 0,(char*) 0,(char *) 0);
	else
	    (void)execl(shell,"sh","-c",str,(char *) 0);
	VERBOSE(gettext(P_Ct_EXSH),"");
	exit(0);
    }
    while ((w_ret = wait((int*)0)) != fk)
	if (w_ret == -1 && errno != EINTR)
	    break;
    Shell = 0;
    (void)signal(SIGINT, xx);
    (void)signal(SIGQUIT, yy);
    _mode(1);
    return;
}


/***************************************************************
 *	This function implements the 'put', 'take', 'break',
 *	'ifc' (aliased to nostop) and 'ofc' (aliased to noostop)
 *	commands which are internal to cu.
 ***************************************************************/

static void
_dopercen(cmd)
char *cmd;
{
    char	*arg[5];
    char	*getpath;
    char	mypath[MAXPATH];
    int	narg;

    blckcnt((long)(-1));

    CDEBUG(4,"call _dopercen(\"%s\")\r\n", cmd);

    arg[narg=0] = strtok(cmd, " \t\n");

    /* following loop breaks out the command and args */
    while((arg[++narg] = strtok((char*) NULL, " \t\n")) != NULL) {
	if(narg < 4)
	    continue;
	else
	    break;
    }

    /* ~%take file option */
    if(EQUALS(arg[0], "take")) {
	if(narg < 2 || narg > 3) {
	    VERBOSE("usage: ~%%take from [to]\r\n%s", "");
	    VERBOSE("(continue)%s", "");
	    return;
	}
	if(narg == 2)
	    arg[2] = arg[1];
	(void) strcpy(filename, arg[2]);
	recfork();	/* fork so child (receive) knows filename */

	/*
	 * be sure that the remote file (arg[1]) exists before
	 * you try to take it.   otherwise, the error message from
	 * cat will wind up in the local file (arg[2])
	 *
	 * what we're doing is:
	 *	stty -echo; \
	 *	if test -r arg1
	 *	then (echo '~[local]'>arg2; cat arg1; echo '~[local]'>)
	 *	else echo can't open: arg1
	 *	fi; \
	 *	stty echo
	 *
	 */
	_w_str("stty -echo;if test -r ");
	_w_str(arg[1]);
	_w_str("; then (echo '~");
	_w_str(prompt);
	_w_str(">'");
	_w_str(arg[2]);
	_w_str(";cat ");
	_w_str(arg[1]);
	_w_str(";echo '~");
	_w_str(prompt);
	_w_str(">'); else echo cant\\'t open: ");
	_w_str(arg[1]);
	_w_str("; fi;stty echo\n");
	Takeflag = YES;
	return;
    }
    /* ~%put file option*/
    if(EQUALS(arg[0], "put")) {
	FILE	*file;
	char	ch, buf[BUFSIZ], spec[NCC+1], *b, *p, *q;
	int	i, j, len, tc=0, lines=0;
	long	chars=0L;

	if(narg < 2 || narg > 3) {
	    VERBOSE("usage: ~%%put from [to]\r\n%s", "");
	    VERBOSE("(continue)%s", "");
	    return;
	}
	if(narg == 2)
	    arg[2] = arg[1];

	if((file = fopen(arg[1], "r")) == NULL) {
	    VERBOSE(gettext(P_Ct_OPEN), arg[1]);
	    VERBOSE("(continue)%s", "");
	    return;
	}
	/*
	 * if cannot write into file on remote machine, write into
	 * /dev/null
	 *
	 * what we're doing is:
	 *	stty -echo
	 *	(cat - > arg2) || cat - > /dev/null
	 *	stty echo
	 */
	_w_str("stty -echo;(cat - >");
	_w_str(arg[2]);
	_w_str(")||cat - >/dev/null;stty echo\n");
	Intrupt = NO;
	for(i=0,j=0; i < NCC; ++i)
	    if((ch=_Tv0s.c_cc[i]) != '\0')
		spec[j++] = ch;
	spec[j] = '\0';
	_mode(2);	/*accept interrupts from keyboard*/
	(void)sleep(5);	/*hope that w_str info digested*/

	/* Read characters line by line into buf to write to	*/
	/* remote with character and line count for blckcnt	*/
	while(Intrupt == NO &&
		fgets(b= &buf[MID],MID,file) != NULL) {
	    /* worse case is each char must be escaped*/
	    len = strlen(b);
	    chars += len;		/* character count */
	    p = b;
	    while(q = strpbrk(p, spec)) {
		if(*q == _Tintr || *q == _Tquit || *q == _Teol) {
		    VERBOSE(gettext(P_Ct_SPECIAL), *q);
		    (void)strcpy(q, q+1);
		    Intrupt = YES;
		} else {
		    b = strncpy(b-1, b, q-b);
		    *(q-1) = '\\';
		}
		p = q+1;
	    }
	    if((tc += len) >= MID) {
		(void)sleep(1);
		tc = len;
	    }
	    if(write(Cn, b, (unsigned)strlen(b)) < 0) {
		VERBOSE(gettext(P_IOERR),"");
		Intrupt = YES;
		break;
	    }
	    ++lines;		/* line count */
	    blckcnt((long)chars);
	}
	_mode(1);
	blckcnt((long)(-2));		/* close */
	(void)fclose(file);
	if(Intrupt == YES) {
	    Intrupt = NO;
	    _w_str("\n");
	    VERBOSE(gettext(P_CNTAFTER), ++chars);
	} else {
	    VERBOSE(gettext(P_CNTLINES), lines);
	    VERBOSE(gettext(P_CNTCHAR),chars);
	}
	(void)sleep(3);
	_w_str("\04");
	return;
    }

	/*  ~%b or ~%break  */
    if(EQUALS(arg[0], "b") || EQUALS(arg[0], "break")) {
	(*genbrk)(Cn);
	return;
    }
	/*  ~%d or ~%debug toggle  */
    if(EQUALS(arg[0], "d") || EQUALS(arg[0], "debug")) {
	if(Debug == 0)
	    Debug = 9;
	else
	    Debug = 0;
	VERBOSE("(continue)%s", "");
	return;
    }
	/*  ~%[ifc|nostop]  toggles start/stop input control  */
    if( EQUALS(arg[0], "ifc") || EQUALS(arg[0], "nostop") ) {
	(void)ioctl(Cn, TCGETA, &_Tv);
	Ifc = !Ifc;
	if(Ifc == YES)
	    _Tv.c_iflag |= IXOFF;
	else
	    _Tv.c_iflag &= ~IXOFF;
	(void)ioctl(Cn, TCSETAW, &_Tv);
	_mode(1);
	VERBOSE("(ifc %s)", (Ifc ? "enabled" : "disabled"));
	VERBOSE("(continue)%s", "");
	return;
    }
	/*  ~%[ofc|noostop]  toggles start/stop output control  */
    if( EQUALS(arg[0], "ofc") || EQUALS(arg[0], "noostop") ) {
	(void)ioctl(Cn, TCGETA, &_Tv);
	Ofc = !Ofc;
	if(Ofc == YES)
	    _Tv.c_iflag |= IXON;
	else
	    _Tv.c_iflag &= ~IXON;
	(void)ioctl(Cn, TCSETAW, &_Tv);
	_mode(1);
	VERBOSE("(ofc %s)", (Ofc ? "enabled" : "disabled"));
	VERBOSE("(continue)%s", "");
	return;
    }
	/*  ~%divert toggles unsolicited redirection security */
    if( EQUALS(arg[0], "divert") ) {
	Divert = !Divert;
	recfork();	/* fork a new child so it knows about change */
	VERBOSE("(unsolicited diversion %s)", (Divert ? "enabled" : "disabled"));
	VERBOSE("(continue)%s", "");
	return;
    }
	/*  ~%old toggles recognition of old-style '~>:filename' */
    if( EQUALS(arg[0], "old") ) {
	OldStyle = !OldStyle;
	recfork();	/* fork a new child so it knows about change */
	VERBOSE("(old-style diversion %s)", (OldStyle ? "enabled" : "disabled"));
	VERBOSE("(continue)%s", "");
	return;
    }
	/* Change local current directory */
    if(EQUALS(arg[0], "cd")) {
	if (narg < 2) {
	    getpath = getenv("HOME");
	    strlcpy(mypath, getpath, sizeof (mypath));
	    if(chdir(mypath) < 0) {
		VERBOSE("Cannot change to %s\r\n", mypath);
		VERBOSE("(continue)%s", "");
		return;
	    }
	} else if (chdir(arg[1]) < 0) {
	    VERBOSE("Cannot change to %s\r\n", arg[1]);
	    VERBOSE("(continue)%s", "");
	    return;
	}
	recfork();	/* fork a new child so it knows about change */
	VERBOSE("(continue)%s", "");
	return;
    }

   if (arg[0] == (char *) NULL)
       arg[0] = "";

    VERBOSE("~%%%s unknown to cu\r\n", arg[0]);
    VERBOSE("(continue)%s", "");
    return;
}

/***************************************************************
 *	receive: read from remote line, write to fd=1 (TTYOUT)
 *	catch:
 *	~>[>]:file
 *	.
 *	. stuff for file
 *	.
 *	~>	(ends diversion)
 ***************************************************************/

static void
_receive()
{
    int silent = NO, file = -1;
    char *p;
    int	tic;
    int for_me = NO;
    char	b[BUFSIZ];
    char	*b_p;
    long	count;
    int		line_ok = 1, rval;

    CDEBUG(4,"_receive started\r\n%s", "");

    b[0] = '\0';
    b_p = p = b;

    while(line_ok) {
	rval = r_char(Cn);
	if (rval == NO) {
	    line_ok = 0;
	    continue;
	}
	if (rval == HUNGUP) {
	    if (command_line_hups > 0) {
		CDEBUG(4, "Ignoring device hangup\n%s", "");
		command_line_hups--;
		(void) setuid(Euid);	/* reacquire privileges */
		if (clear_hup(Cn) != SUCCESS) {
		    DEBUG(4, "Unable to clear hup on device\n%s", "");
		    line_ok = 0;
		}
		(void) setuid(getuid());  /* relinquish privileges */
	    } else
		line_ok = 0;
	    continue;
	}

	if(silent == NO)    /* ie., if not redirecting from screen */
	    if(w_char(TTYOUT) == NO)
		_rcvdead(IOERR);    /* this will exit */
	/* remove CR's and fill inserted by remote */
	if(_Cxc == '\0' || _Cxc == RUB || _Cxc == '\r')
	    continue;
	*p++ = _Cxc;
	if(_Cxc != '\n' && (p-b) < BUFSIZ)
	    continue;
	/* ****************************************** */
	/* This code deals with ~%take file diversion */
	/* ****************************************** */
	if (b[0] == '~') {
	    int    append;

	    if (EQUALSN(&b[1],prompt,strlen(prompt))) {
		b_p = b + 1 + strlen(prompt);
		for_me = YES;
	    } else {
		b_p = b + 1;
		for_me = NO;
	    }
	    if ( (for_me || OldStyle) && (*b_p == '>') ) {
		/* This is an acceptable '~[uname]>' line */
		b_p++;
		if ( (*b_p == '\n') && (silent == YES) ) {
		    /* end of diversion */
		    *b_p = '\0';
		    (void) strcpy(filename, "/dev/null");
		    if ( file >= 0 && close(file) ) {
			VERBOSE(gettext(P_Ct_UNDIVERT), b_p);
			perror(gettext("cu: close failed"));
			VERBOSE("%s","\r");
		    }
		    silent = NO;
		    blckcnt((long)(-2));
		    VERBOSE("%s\r\n", b);
		    VERBOSE(gettext(P_CNTLINES), tic);
		    VERBOSE(gettext(P_CNTCHAR), count);
		    file = -1;
		    p = b;
		    continue;
		} else if (*b_p != '\n') {
		    if ( *b_p == '>' ) {
			append = 1;
			b_p++;
		    }
		    if ( (for_me || (OldStyle && (*b_p == ':'))) && (silent == NO) ) {
			/* terminate filename string */
			*(p-1) = '\0';
			if ( *b_p == ':' )
			    b_p++;
			if ( !EQUALS(filename, b_p) ) {
			    if ( !Divert  || !EQUALS(filename, "/dev/null") ) {
				VERBOSE(gettext(P_Bad_DIVERT), b_p);
				(void) strcpy(filename, "/dev/null");
				append = 1;
			    } else {
				(void) strcpy(filename, b_p);
			    }
			}
			if ( append && ((file=open(filename,O_WRONLY)) >= 0) )
			    (void)lseek(file, 0L, 2);
			else
			    file = creat(filename, PUB_FILEMODE);
			if (file < 0) {
			    VERBOSE(gettext(P_Ct_DIVERT), filename);
			    perror(gettext("cu: open|creat failed"));
			    VERBOSE("%s","\r");
			    (void)sleep(5); /* 10 seemed too long*/
			}
			silent = YES;
			count = tic = 0;
			p = b;
			continue;
		    }
		}
	    }
	}
	/* Regular data, divert if appropriate */
	if ( silent == YES ) {
	    if ( file >= 0)
		(void)write(file, b, (unsigned)(p-b));
	    count += p-b;	/* tally char count */
	    ++tic;		/* tally lines */
	    blckcnt((long)count);
	}
	p = b;
    }
    /*
     * we used to tell of lost carrier here, but now
     * defer to _bye() so that escape processes are
     * not interrupted.
     */
    _rcvdead(IOERR);
    return;
}

/***************************************************************
 *	change the TTY attributes of the users terminal:
 *	0 means restore attributes to pre-cu status.
 *	1 means set `raw' mode for use during cu session.
 *	2 means like 1 but accept interrupts from the keyboard.
 ***************************************************************/
static void
_mode(arg)
{
    int i;

    CDEBUG(4,"call _mode(%d)\r\n", arg);
    if(arg == 0) {
	if ( Saved_termios )
		(void)ioctl(TTYIN, TCSETSW, &_Tv0s);
	else if ( Saved_tty ) {
		_Tv0.c_lflag = _Tv0s.c_lflag;
		_Tv0.c_oflag = _Tv0s.c_oflag;
		_Tv0.c_iflag = _Tv0s.c_iflag;
		_Tv0.c_cflag = _Tv0s.c_cflag;
		for(i = 0; i < NCC; i++)
			_Tv0.c_cc[i] = _Tv0s.c_cc[i];
		(void)ioctl(TTYIN, TCSETAW, &_Tv0);
	}
    } else {
	(void)ioctl(TTYIN, TCGETA, &_Tv);
	if(arg == 1) {
	    _Tv.c_iflag &= ~(INLCR | ICRNL | IGNCR | IUCLC);
	    if ( !term_8bit )
		_Tv.c_iflag |= ISTRIP;
	    _Tv.c_oflag |= OPOST;
	    _Tv.c_oflag &= ~(OLCUC | ONLCR | OCRNL | ONOCR | ONLRET);
	    _Tv.c_lflag &= ~(ICANON | ISIG | ECHO);
	    if(Ifc == NO)
		_Tv.c_iflag &= ~IXON;
	    else
		_Tv.c_iflag |= IXON;
	    if(Ofc == NO)
		_Tv.c_iflag &= ~IXOFF;
	    else
		_Tv.c_iflag |= IXOFF;
	    if(Terminal) {
		_Tv.c_oflag |= ONLCR;
		_Tv.c_iflag |= ICRNL;
	    }
	    _Tv.c_cc[VEOF] = '\01';
	    _Tv.c_cc[VEOL] = '\0';
	}
	if(arg == 2) {
	    _Tv.c_iflag |= IXON;
	    _Tv.c_lflag |= ISIG;
	}
	(void)ioctl(TTYIN, TCSETAW, &_Tv);
    }
    return;
}


static pid_t
dofork()
{
    int i;
    pid_t x;

    for(i = 0; i < 6; ++i) {
	if((x = fork()) >= 0) {
	    return(x);
	}
    }

    if(Debug) perror("dofork");

    VERBOSE(gettext(P_Ct_FK),"");
    return(x);
}

static int
r_char(fd)
{
    int rtn = 1, rfd;
    char *riobuf;

    /* find starting pos in correct buffer in Riobuf	*/
    rfd = RIOFD(fd);
    riobuf = &Riobuf[rfd*WRIOBSZ];

    if (Riop[rfd] >= &riobuf[Riocnt[rfd]]) {
	/* empty read buffer - refill it	*/

	/*	flush any waiting output	*/
	if ( (wioflsh(Cn) == NO ) || (wioflsh(TTYOUT) == NO) )
	    return(NO);

	while((rtn = read(fd, riobuf, WRIOBSZ)) < 0){
	    if(errno == EINTR) {
		/* onintrpt() called asynchronously before this line */
		if(Intrupt == YES) {
		    /* got a BREAK */
		    _Cxc = '\0';
		    return(YES);
		} else {
		    /*a signal other than interrupt*/
		    /*received during read*/
		    continue;
		}
	    } else {
		CDEBUG(4,"got read error, not EINTR\n\r%s", "");
		break;			/* something wrong */
	    }
	}
	if (rtn > 0) {
	    /* reset current position in buffer	*/
	    /* and count of available chars		*/
	    Riop[rfd] = riobuf;
	    Riocnt[rfd] = rtn;
	}
    }

    if ( rtn > 0 ) {
	_Cxc = *(Riop[rfd]++) & RMASK(fd);	/* mask off appropriate bits */
	return(YES);
    } else if (rtn == 0) {
	_Cxc = '\0';
	return (HUNGUP);
    } else {
	_Cxc = '\0';
	return(NO);
    }
}

static int
w_char(fd)
{
    int wfd;
    char *wiobuf;

    /* find starting pos in correct buffer in Wiobuf	*/
    wfd = WIOFD(fd);
    wiobuf = &Wiobuf[wfd*WRIOBSZ];

    if (Wiop[wfd] >= &wiobuf[WRIOBSZ]) {
	/* full output buffer - flush it */
	if ( wioflsh(fd) == NO )
	    return(NO);
    }
    *(Wiop[wfd]++) = _Cxc & WMASK(fd);	/* mask off appropriate bits */
    return(YES);
}

/* wioflsh	flush output buffer	*/
static int
wioflsh(fd)
int fd;
{
    int wfd;
    char *wiobuf;

    /* find starting pos in correct buffer in Wiobuf	*/
    wfd = WIOFD(fd);
    wiobuf = &Wiobuf[wfd*WRIOBSZ];

    if (Wiop[wfd] > wiobuf) {
	/* there's something in the buffer */
	while(write(fd, wiobuf, (Wiop[wfd] - wiobuf)) < 0) {
	    if(errno == EINTR) {
		if(Intrupt == YES) {
		    VERBOSE("\ncu: Output blocked\r\n%s", "");
		    _quit(IOERR);
		} else
		    continue;	/* alarm went off */
	    } else {
		Wiop[wfd] = wiobuf;
		return(NO);			/* bad news */
	    }
	}
    }
    Wiop[wfd] = wiobuf;
    return(YES);
}


static void
_w_str(string)
char *string;
{
    int len;

    len = strlen(string);
    if ( write(Cn, string, (unsigned)len) != len )
	VERBOSE(gettext(P_LINE_GONE),"");
    return;
}

/* ARGSUSED */
static void
_onintrpt(sig)
int sig;
{
    (void)signal(SIGINT, _onintrpt);
    (void)signal(SIGQUIT, _onintrpt);
    Intrupt = YES;
    return;
}

static void
_rcvdead(arg)	/* this is executed only in the receive process */
int arg;
{
    CDEBUG(4,"call _rcvdead(%d)\r\n", arg);
    (void)kill(getppid(), SIGUSR1);
    exit((arg == SIGHUP)? SIGHUP: arg);
    /*NOTREACHED*/
}

static void
_quit(arg)	/* this is executed only in the parent process */
int arg;
{
    CDEBUG(4,"call _quit(%d)\r\n", arg);
    (void)kill(Child, SIGKILL);
    _bye(arg);
    /*NOTREACHED*/
}

static void
_bye(arg)	/* this is executed only in the parent proccess */
int arg;
{
    int status;
    pid_t obit;

    if ( Shell > 0 )
	while ((obit = wait(&status)) != Shell) {
	    if (obit == -1 && errno != EINTR)
		break;
	    /* _receive (Child) may have ended - check it out */
	    if (obit == Child)
		Child = 0;
	}

    /* give user customary message after escape command returns */
    if (arg == SIGUSR1)
	VERBOSE("\r\nLost Carrier\r\n%s", "");

    CDEBUG(4,"call _bye(%d)\r\n", arg);

    (void)signal(SIGINT, SIG_IGN);
    (void)signal(SIGQUIT, SIG_IGN);
    /* if _receive() ended already, don't wait for it again */
    if ( Child != 0 )
	while ((obit = wait(&status)) != Child)
	    if (obit == -1 && errno != EINTR)
		break;
    VERBOSE("\r\nDisconnected\007\r\n%s", "");
    cleanup((arg == SIGUSR1)? (status >>= 8): arg);
    /*NOTREACHED*/
}



void
cleanup(code) 	/*this is executed only in the parent process*/
int code;	/*Closes device; removes lock files	  */
{

    CDEBUG(4,"call cleanup(%d)\r\n", code);

    if (Docmd) {
	if (Child > 0)
	    (void)kill(Child, SIGTERM);
    } else
	(void) setuid(Euid);
    if(Cn > 0) {
	fchmod(Cn, Dev_mode);
	fd_rmlock(Cn);
	(void)close(Cn);
    }


    rmlock((char*) NULL);	/* remove all lock files for this process */
    if (!Docmd)
	_mode(0);
    exit(code);		/* code=negative for signal causing disconnect*/
}



void
tdmp(arg)
int arg;
{

    struct termio xv;
    int i;

    VERBOSE("\rdevice status for fd=%d\r\n", arg);
    VERBOSE("F_GETFL=%o,", fcntl(arg, F_GETFL,1));
    if(ioctl(arg, TCGETA, &xv) < 0) {
	char	buf[100];
	i = errno;
	(void)snprintf(buf, sizeof (buf), gettext("\rtdmp for fd=%d"), arg);
	errno = i;
	perror(buf);
	return;
    }
    VERBOSE("iflag=`%o',", xv.c_iflag);
    VERBOSE("oflag=`%o',", xv.c_oflag);
    VERBOSE("cflag=`%o',", xv.c_cflag);
    VERBOSE("lflag=`%o',", xv.c_lflag);
    VERBOSE("line=`%o'\r\n", xv.c_line);
    VERBOSE("cc[0]=`%o',",  xv.c_cc[0]);
    for(i=1; i<8; ++i) {
	VERBOSE("[%d]=", i);
	VERBOSE("`%o',",xv.c_cc[i]);
    }
    VERBOSE("\r\n%s", "");
    return;
}



static void
sysname(name)
char * name;
{

    char *s;

    if(uname(&utsn) < 0)
	s = "Local";
    else
	s = utsn.nodename;

    strcpy(name, s);
    return;
}


static void
blckcnt(count)
long count;
{
    static long lcharcnt = 0;
    long c1, c2;
    int i;
    char c;

    if(count == (long) (-1)) {	/* initialization call */
	lcharcnt = 0;
	return;
    }
    c1 = lcharcnt/BUFSIZ;
    if(count != (long)(-2)) {	/* regular call */
	c2 = count/BUFSIZ;
	for(i = c1; i++ < c2;) {
	    c = '0' + i%10;
	    write(2, &c, 1);
	    if(i%NPL == 0)
		write(2, "\n\r", 2);
	}
	lcharcnt = count;
    } else {
	c2 = (lcharcnt + BUFSIZ -1)/BUFSIZ;
	if(c1 != c2)
	    write(2, "+\n\r", 3);
	else if(c2%NPL != 0)
	    write(2, "\n\r", 2);
	lcharcnt = 0;
    }
    return;
}

/*VARARGS*/
/*ARGSUSED*/
void
assert (s1, s2, i1, s3, i2)
char *s1, *s2, *s3;
int i1, i2;
{ }		/* for ASSERT in gnamef.c */

/*ARGSUSED*/
void
logent (s1, s2)
char *s1, *s2;
{ }		/* so we can load ulockf() */