/* Copyright (c) 1984 AT&T */ /* All Rights Reserved */ /* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ /* The copyright notice above does not evidence any */ /* actual or intended publication of such source code. */ #ident "$Revision: 1.19 $" /******************************************************************** *cu [-sspeed] [-lline] [-h] [-t] [-d] [-n] [-o|-e] telno | systemname * * legal baud rates: 300, 1200, 2400, 4800, 9600. * * -l is for specifying a line unit from the file whose * name is defined in /usr/lib/uucp/Devices. * -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. * Telno is a telephone number with `=' for secondary dial-tone. * If "-l dev" is used, speed is taken from /usr/lib/uucp/Devices. * Only systemnames that are included in /usr/lib/uucp/Systems may * be used. * * Escape with `~' at beginning of line. * Silent output diversions are ~>:filename and ~>>:filename. * Terminate output diversion with ~> alone. * ~. is quit, and ~![cmd] gives local shell [or command]. * Also ~$ for local procedure with stdout to remote. * Both ~%put from [to] and ~%take from [to] invoke built-ins. * Also, ~%break or just ~%b will transmit a BREAK to remote. * ~%nostop toggles on/off the DC3/DC1 input control from remote, * (certain remote systems cannot cope with DC3 or DC1). * ~%debug(~%b) toggles on/off the program tracing and diagnostics output; * cu should no longer be compiled with #ifdef ddt. * * 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 /usr/lib/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 * LCK..dev where dev is the device name from the Devices file. * 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 #include #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 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 */ static char *_Cnname; /*to save associated ttyname */ 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 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 errno, /* supplied by system interface */ optind; /* variable in getopt() */ extern char *optarg; static struct call { /*NOTE-also included in altconn.c--> */ /*any changes must be made in both places*/ char *speed; /* transmission baud rate */ char *line; /* device name for outgoing line */ char *telno; /* ptr to tel-no digit string */ char *class; /* call class */ }Cucall; static int Saved_tty; /* was TCGETAW of _Tv0 successful? */ static struct termio _Tv, _Tv0; /* for saving, changing TTY atributes */ static struct termio _Lv; /* attributes for the line to remote */ 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 */ _Tswtch; /* current input SWTCH */ int Terminal=0, /* flag; remote is a terminal */ Oddflag = 0, /*flag- odd parity option*/ Evenflag = 0, /*flag- even parity option*/ Echoe, /* save users ECHOE bit */ Echok, /* save users ECHOK bit */ Child, /* pid for receive process */ Intrupt=NO, /* interrupt indicator */ Duplex=YES, /* Unix= full duplex=YES; half = NO */ Sstop=YES, /* NO means remote can't XON/XOFF */ Rtn_code=0, /* default return code */ Takeflag=NO, /* indicates a ~%take is in progress */ input_eof_file=0, /* if set, input side was not a tty, and got eof */ w_char(int), /* local io routine */ r_char(int); /* local io routine */ static void _onintrpt(void), /* interrupt routines */ _rcvdead(int), _quit(int), _bye(int); extern void tdmp(int); static void _flush(void), _shell(char *), _dopercen(char *), _receive(void), _mode(int), _w_str(char *); static void recfork(void); static int dofork(void); int transmit(void); static int wioflsh(int); static void blckcnt(long); int tilda(char *); extern int altconn(struct call *); char *Myline = NULL; /* flag to force the requested line to be used */ /* by rddev() in uucp conn.c */ char *P_USAGE= "USAGE:%s [-s speed] [-l line] [-c class] [-h] [-n] [-t] [-d] [-o|-e] telno | systemname\n"; char *P_CON_FAILED = "Connect failed: %s\r\n"; char *P_Ct_OPEN = "Cannot open: %s\r\n"; char *P_LINE_GONE = "Remote line gone\r\n"; char *P_Ct_EXSH = "Can't execute shell\r\n"; char *P_Ct_DIVERT = "Can't divert %s\r\n"; char *P_STARTWITH = "Use `~~' to start line with `~'\r\n"; char *P_CNTAFTER = "after %ld bytes\r\n"; char *P_CNTLINES = "%d lines/"; char *P_CNTCHAR = "%ld characters\r\n"; char *P_FILEINTR = "File transmission interrupted\r\n"; char *P_Ct_FK = "Can't fork -- try later\r\n"; char *P_Ct_SPECIAL = "r\nCan't transmit special character `%#o'\r\n"; char *P_TOOLONG = "\nLine too long\r\n"; char *P_IOERR = "r\nIO error\r\n"; char *P_USECMD = "Use `~$'cmd \r\n"; char *P_USEPLUSCMD ="Use `~+'cmd \r\n"; char *P_NOTERMSTAT = "Can't get terminal status\r\n"; char *P_3BCONSOLE = "Sorry, you can't cu from a 3B console\r\n"; char *P_PARITY = "Parity option error\r\n"; 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. ***************************************************************/ void main(argc, argv) char *argv[]; { char s[MAXPH]; char *string; char *p; int i; int errflag=0; int lflag=0; int nflag=0; int systemname = 0; 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*/ strcpy(Progname,"cu"); setservice(Progname); if ( sysaccess(EACCESS_SYSTEMS) != 0 ) { (void)fprintf(stderr, "cu: cannot read Systems files\n"); exit(1); } if ( sysaccess(EACCESS_DEVICES) != 0 ) { (void)fprintf(stderr, "cu: cannot read Devices files\n"); exit(1); } if ( sysaccess(EACCESS_DIALERS) != 0 ) { (void)fprintf(stderr, "cu: cannot read Dialers files\n"); exit(1); } Cucall.speed = "Any"; /*default speed*/ Cucall.line = NULL; Cucall.telno = NULL; Cucall.class = NULL; /*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:")) != EOF) switch(i) { case 'd': Debug = 9; /*turns on uucp debugging-level 9*/ break; case 'h': Duplex = NO; Sstop = NO; break; case 't': Terminal = YES; break; case 'e': if ( Oddflag ) { (void)fprintf(stderr, "%s: cannot have both even and odd parity\n", argv[0]); exit(1); } Evenflag = 1; break; case 'o': if ( Evenflag ) { (void)fprintf(stderr, "%s: cannot have both even and odd parity\n", argv[0]); exit(1); } Oddflag = 1; break; case 's': Sflag++; Cucall.speed = optarg; break; case 'l': lflag++; Cucall.line = optarg; break; /* -c specifies the class-selecting an entry type from the Devices file */ case 'c': Cucall.class = optarg; break; case 'n': nflag++; printf("Please enter the number: "); fgets(s, MAXPH, stdin); break; case '?': ++errflag; } #ifdef u3b if(fstat(TTYIN, &buff) < 0) { VERBOSE(P_NOTERMSTAT,""); exit(1); } else if ( (buff.st_mode & S_IFMT) == S_IFCHR && buff.st_rdev == 0 ) { VERBOSE(P_3BCONSOLE,""); exit(1); } #endif if((optind < argc && optind > 0) || (nflag && optind > 0)) { if(nflag) string=s; else string = argv[optind]; if(p = strchr(string, '\n')) *p = '\0'; if((strlen(string) == strspn(string, "0123456789=-*#")) || (Cucall.class != NULL)) Cucall.telno = string; else { /*if it's not a legitimate telno, */ /*then it should be a systemname */ if ( nflag ) { (void)fprintf(stderr, "%s: bad phone number %s\nPhone numbers may contain only the digits 0 through 9 and the special\ncharacters =, -, * and #.\n", argv[0], string ); exit(1); } systemname++; } } else if(Cucall.line == NULL) /*if none of above, must be direct */ ++errflag; if(errflag) { VERBOSE(P_USAGE, argv[0]); exit(1); } if(Cucall.telno != NULL && strlen(Cucall.telno) >= (MAXPH - 1)) { VERBOSE(P_TELLENGTH,""); exit(0); } /* save initial tty state */ Saved_tty = ( ioctl(TTYIN, TCGETA, &_Tv0) == 0 ); _Tintr = _Tv0.c_cc[VINTR]? _Tv0.c_cc[VINTR]: '\377'; _Tquit = _Tv0.c_cc[VQUIT]? _Tv0.c_cc[VQUIT]: '\377'; _Terase = _Tv0.c_cc[VERASE]? _Tv0.c_cc[VERASE]: '\377'; _Tkill = _Tv0.c_cc[VKILL]? _Tv0.c_cc[VKILL]: '\377'; _Teol = _Tv0.c_cc[VEOL]? _Tv0.c_cc[VEOL]: '\377'; _Myeof = _Tv0.c_cc[VEOF]? _Tv0.c_cc[VEOF]: '\04'; /* Make sure the line discipline supports job control */ _Tswtch = (_Tv0.c_line == LDISC1) ? _Tv0.c_cc[VSUSP] : CNSWTCH; Echoe = (_Tv0.c_lflag & ECHOE) != 0; Echok = (_Tv0.c_lflag & ECHOK) != 0; (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, "%s: warning: -l flag ignored when system name used\n", argv[0]); if ( Sflag ) (void)fprintf(stderr, "%s: warning: -s flag ignored when system name used\n", argv[0]); Cn = conn(string); } else Cn = altconn(&Cucall); _Cnname = ttyname(Cn); if(_Cnname != NULL) { struct stat Cnsbuf; if ( fstat(Cn, &Cnsbuf) == 0 ) Dev_mode = Cnsbuf.st_mode; else Dev_mode = R_DEVICEMODE; chmod(_Cnname, M_DEVICEMODE); } Euid = geteuid(); if(setuid(getuid()) && setgid(getgid()) < 0) { VERBOSE("Unable to setuid/gid\n",""); cleanup(-1); } if(Cn < 0) { VERBOSE(P_CON_FAILED,UERRORTEXT); cleanup(-Cn); } if(Debug) tdmp(Cn); /* At this point succeeded in getting an open communication line */ /* Conn() takes care of closing the Systems file */ (void)signal(SIGINT,_onintrpt); _mode(1); /* put terminal in `raw' mode */ VERBOSE("Connected\007\r\n",""); /*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); recfork(); /* checks for child == 0 */ if(Child > 0) { Rtn_code = transmit(); _quit(Rtn_code); } else { cleanup(Cn); } } /* * Kill the present child, if it exists, then fork a new one. */ static void recfork(void) { if (Child) kill(Child, SIGKILL); 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*/ } } /*************************************************************** * 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(void) { char b[BUFSIZ]; char prompt[MAXHOSTNAMELEN]; register char *p; register int escape; register int id = 0; /*flag for systemname prompt on tilda escape*/ CDEBUG(4,"transmit started\n\r",""); if (gethostname(prompt, sizeof prompt) < 0) { strcpy(prompt, "Local"); } /* In main loop, always waiting to read characters from */ /* keyboard; writes characters to remote, or to TTYOUT */ /* on a tilda escape */ while(TRUE && !input_eof_file) { 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",""); } else { _Cxc = '\r'; if( w_char(Cn) == NO) { VERBOSE(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", ""); else (void)w_char(TTYOUT); } else { (void)w_char(TTYOUT); if(p-b < BUFSIZ) *p++ = _Cxc; else { VERBOSE(P_TOOLONG,""); break; } } /*not a tilda escape command*/ } else { if(Intrupt && _Cxc == '\0') { CDEBUG(4,"got break in transmit\n\r",""); Intrupt = NO; (*genbrk)(Cn); _flush(); break; } if(w_char(Cn) == NO) { VERBOSE(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",""); _flush(); break; } if(_Cxc == '\n' || _Cxc == '\r' || _Cxc == _Teol || _Cxc == _Tkill) { id=0; Takeflag = NO; break; } p = (char*)0; } } } /* The nap is because some people may have counted on the old * behavior of cu looping 'forever' on reaching end of file when * stdin is a script, to allow the remote side to have seen the * earlier stuff passed from a script, and do something about it, * before the line drops. 15 seconds seems reasonable. */ sginap(15*HZ); CDEBUG(0,"return from transmit after eof from non-tty input\r\n", 0); return 0; /* must have gotten EOF and not from a tty */ } /*************************************************************** * routine to halt input from remote and flush buffers ***************************************************************/ static void _flush(void) { (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(P_FILEINTR,""); (void)sleep(3); _w_str("echo '\n~>\n';mesg y;stty echo\n"); Takeflag = NO; } /************************************************************** * command interpreter for escape lines **************************************************************/ int tilda(char *cmd) { VERBOSE("\r\n",""); CDEBUG(4,"call tilda(%s)\r\n", cmd); switch(cmd[0]) { case '.': if(Cucall.telno == NULL) if(cmd[1] != '.') { _w_str("\04\04\04\04\04"); if (Child) kill(Child, SIGKILL); (void) ioctl (Cn, TCGETA, &_Lv); /* speed to zero for hangup */ _Lv.c_ospeed = 0; (void) ioctl (Cn, TCSETAW, &_Lv); (void) sleep (2); } return(YES); case '!': _shell(cmd); /* local shell */ VERBOSE("\r%c\r\n", *cmd); VERBOSE("(continue)",""); break; case '$': if(cmd[1] == '\0') { VERBOSE(P_USECMD,""); VERBOSE("(continue)",""); } else { _shell(cmd); /*Local shell */ VERBOSE("\r%c\r\n", *cmd); } break; #ifdef forfutureuse case '+': if(cmd[1] == '\0') { VERBOSE(P_USEPLUSCMD, ""); VERBOSE("(continue)",""); } 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)",""); break; case 'l': tdmp(Cn); VERBOSE("(continue)",""); break; default: if (_Tswtch != CNSWTCH && cmd[0] == _Tswtch) { _mode(0); kill(0, SIGTSTP); _mode(1); VERBOSE("(continue)",""); break; } VERBOSE(P_STARTWITH,""); VERBOSE("(continue)",""); 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(char *str) { int fk; void (*xx)(), (*yy)(); CDEBUG(4,"call _shell(%s)\r\n", str); fk = dofork(); if(fk < 0) return; _mode(0); /* restore normal tty attributes */ xx = signal(SIGINT, SIG_IGN); yy = signal(SIGQUIT, SIG_IGN); if(fk == 0) { char *shell; shell = getenv("SHELL"); if(shell == NULL) shell = "/bin/sh"; /*user's shell is set if*/ /*different from default*/ (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,0); else (void)execl(shell,"sh","-c",str,0); VERBOSE(P_Ct_EXSH,""); exit(0); } while(wait((int*)0) != fk); (void)signal(SIGINT, xx); (void)signal(SIGQUIT, yy); _mode(1); } /*************************************************************** * This function implements the 'put', 'take', 'break', and * 'nostop' commands which are internal to cu. ***************************************************************/ static void _dopercen(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; } if (arg[0] == NULL) { VERBOSE("(continue)",""); return; } /* ~%take file option */ if(EQUALS(arg[0], "take")) { if(narg < 2 || narg > 3) { VERBOSE("usage: ~%%take from [to]\r\n",""); VERBOSE("(continue)",""); return; } if(narg == 2) arg[2] = arg[1]; /* * 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 '~'>:arg2; cat arg1; echo '~'>) * 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(arg[2]); _w_str(";cat "); _w_str(arg[1]); _w_str(";echo '~>'); 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",""); VERBOSE("(continue)",""); return; } if(narg == 2) arg[2] = arg[1]; if((file = fopen(arg[1], "r")) == NULL) { VERBOSE(P_Ct_OPEN, arg[1]); VERBOSE("(continue)",""); 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=_Tv0.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= 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 && _Teol != '\n')) { VERBOSE(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(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; VERBOSE(P_FILEINTR,""); _w_str("\n"); VERBOSE(P_CNTAFTER, ++chars); } else VERBOSE(P_CNTLINES, lines); VERBOSE(P_CNTCHAR,chars); _w_str("\04"); (void)sleep(3); 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)",""); return; } /* ~%nostop toggles start/stop input control */ if(EQUALS(arg[0], "nostop")) { (void)ioctl(Cn, TCGETA, &_Tv); if(Sstop == NO) _Tv.c_iflag |= IXOFF; else _Tv.c_iflag &= ~IXOFF; (void)ioctl(Cn, TCSETAW, &_Tv); Sstop = !Sstop; _mode(1); VERBOSE("(continue)",""); return; } /* Change local current directory */ if(EQUALS(arg[0], "cd")) { if (narg < 2) { getpath = getenv("HOME"); strcpy(mypath, getpath); if(chdir(mypath) < 0) { VERBOSE("Cannot change to %s\r\n", mypath); VERBOSE("(continue)",""); return; } } else if (chdir(arg[1]) < 0) { VERBOSE("Cannot change to %s\r\n", arg[1]); VERBOSE("(continue)",""); return; } recfork(); /* fork a new child so it know about change */ VERBOSE("(continue)",""); return; } VERBOSE("~%%%s unknown to cu\r\n", arg[0]); VERBOSE("(continue)",""); } /*************************************************************** * receive: read from remote line, write to fd=1 (TTYOUT) * catch: * ~>[>]:file * . * . stuff for file * . * ~> (ends diversion) ***************************************************************/ static void _receive(void) { register silent=NO, file; register char *p; int tic; char b[BUFSIZ]; long count; CDEBUG(4,"_receive started\r\n",""); b[0] = '\0'; file = -1; p = b; while(r_char(Cn) == YES) { if(silent == NO) /* ie., if not redirecting to file*/ 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; /*********************************************** * The rest of this code is to deal with what * happens at the beginning, middle or end of * a diversion to a file. ************************************************/ if(b[0] == '~' && b[1] == '>') { /**************************************** * The line is the beginning or * end of a diversion to a file. ****************************************/ if((file < 0) && (b[2] == ':' || b[2] == '>')) { /********************************** * Beginning of a diversion *********************************/ int append; *(p-1) = NULL; /* terminate file name */ append = (b[2] == '>')? 1:0; p = b + 3 + append; if(append && (file=open(p,O_WRONLY))>0) (void)lseek(file, 0L, 2); else file = creat(p, 0666); if(file < 0) { VERBOSE(P_Ct_DIVERT, p); perror("cu: open|creat failed"); (void)sleep(5); /* 10 seemed too long*/ } else { silent = YES; count = tic = 0; } } else { /******************************* * End of a diversion (or queer data) *******************************/ if(b[2] != '\n') goto D; /* queer data */ if(silent = close(file)) { VERBOSE(P_Ct_DIVERT, b); perror("cu: close failed"); silent = NO; } blckcnt((long)(-2)); VERBOSE("~>\r\n",""); VERBOSE(P_CNTLINES, tic); VERBOSE(P_CNTCHAR, count); file = -1; } } else { /*************************************** * This line is not an escape line. * Either no diversion; or else yes, and * we've got to divert the line to the file. ***************************************/ D: if(file > 0) { (void)write(file, b, (unsigned)(p-b)); count += p-b; /* tally char count */ ++tic; /* tally lines */ blckcnt((long)count); } } p = b; } VERBOSE("\r\nLost Carrier\r\n",""); _rcvdead(IOERR); } /*************************************************************** * 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(int arg) { CDEBUG(4,"call _mode(%d)\r\n", arg); if(arg == 0) { if ( Saved_tty ) (void)ioctl(TTYIN, TCSETAW, &_Tv0); } else { (void)ioctl(TTYIN, TCGETA, &_Tv); if(arg == 1) { _Tv.c_iflag &= ~(INLCR | ICRNL | IGNCR | IXOFF | IUCLC); _Tv.c_iflag |= ISTRIP; _Tv.c_oflag |= OPOST; _Tv.c_oflag &= ~(OLCUC | ONLCR | OCRNL | ONOCR | ONLRET); _Tv.c_lflag &= ~(ICANON | ISIG | ECHO); if(Sstop == NO) _Tv.c_iflag &= ~IXON; else _Tv.c_iflag |= IXON; 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); } } static int dofork(void) { register int x,i; for(i = 0; i < 6; ++i) { if((x = fork()) >= 0) { return(x); } } if(Debug) perror("dofork"); VERBOSE(P_Ct_FK,""); return(x); } int r_char(int 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",""); break; /* something wrong */ } } if (rtn > 0) { /* reset current position in buffer */ /* and count of available chars */ Riop[rfd] = riobuf; Riocnt[rfd] = rtn; } else if(!isatty(rfd)) { /* We hit end of file, and input was from a pipe or file. cu * used to loop 'forever' in this case, until the line dropped. * and if it never did for some reason, we'd just eat up the * whole cpu... */ input_eof_file = 1; return NO; } } if ( rtn > 0 ) { _Cxc = *(Riop[rfd]++) & 0177; /*must mask off parity bit*/ return(YES); } else { _Cxc = '\0'; return(NO); } } int w_char(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[WRIOBSZ]) { /* full output buffer - flush it */ if ( wioflsh(fd) == NO ) return(NO); } *(Wiop[wfd]++) = _Cxc; return(YES); } /* wioflsh flush output buffer */ static int wioflsh(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",""); _quit(IOERR); } else continue; /* alarm went off */ else { Wiop[wfd] = wiobuf; return(NO); /* bad news */ } } } Wiop[wfd] = wiobuf; return(YES); } static void _w_str(char *string) { int len; len = strlen(string); if ( write(Cn, string, (unsigned)len) != len ) VERBOSE(P_LINE_GONE,""); } static void _onintrpt(void) { (void)signal(SIGINT, _onintrpt); (void)signal(SIGQUIT, _onintrpt); Intrupt = YES; } static void _rcvdead(int arg) /* this is executed only in the receive process */ { CDEBUG(4,"call _rcvdead(%d)\r\n", arg); (void)kill(getppid(), SIGUSR1); exit((arg == SIGHUP)? SIGHUP: arg); /*NOTREACHED*/ } static void _quit(int arg) /* this is executed only in the parent process */ { CDEBUG(4,"call _quit(%d)\r\n", arg); (void)kill(Child, SIGKILL); _bye(arg); /*NOTREACHED*/ } static void _bye(int arg) /* this is executed only in the parent proccess */ { int status; CDEBUG(4,"call _bye(%d)\r\n", arg); (void)signal(SIGINT, SIG_IGN); (void)signal(SIGQUIT, SIG_IGN); (void)wait(&status); VERBOSE("\r\nDisconnected\007\r\n",""); cleanup((arg == SIGUSR1)? (status >>= 8): arg); /*NOTREACHED*/ } void /*this is executed only in the parent process*/ cleanup(int code) /*Closes device; removes lock files */ { CDEBUG(4,"call cleanup(%d)\r\n", code); (void) setuid(Euid); if(Cn > 0) { chmod(_Cnname, Dev_mode); (void)close(Cn); } rmlock((char*) NULL); /*uucp routine in ulockf.c*/ _mode(0); /*which removes lock files*/ exit(code); /* code=negative for signal causing disconnect*/ } void tdmp(int arg) { struct termio xv; int i; VERBOSE("\rdevice status for fd=%d\n", arg); VERBOSE("F_GETFL=%o,", fcntl(arg, F_GETFL,1)); if(ioctl(arg, TCGETA, &xv) < 0) { char buf[100]; i = errno; (void)sprintf(buf, "\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("ospeed=`%d',", xv.c_ospeed); 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",""); } static void blckcnt(long count) { static long lcharcnt = 0; register long c1, c2; register 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; } } /* ARGSUSED */ void assert(char *s1, char *s2, int i1, char *file, int line){ /* for ASSERT in gnamef.c */ } /* ARGSUSED */ void logent(char *text, char *status){} /* so we can load ulockf() */