/* * 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 */ /* * 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... where MAJ is the major device of the * filesystem containing the device, and and 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 #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 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() */