Path: seismo!harvard!talcott!panda!sources-request From: sources-request@panda.UUCP Newsgroups: mod.sources Subject: dial -- a state transition controlled communications program Message-ID: <1396@panda.UUCP> Date: 10 Feb 86 17:26:18 GMT Sender: jpn@panda.UUCP Organization: Technical University of Berlin, Dept. of Computer Science Lines: 1326 Approved: jpn@panda.UUCP Mod.sources: Volume 3, Issue 118 Submitted by: "Oliver Laumann" #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # README # dial.1 # dial.c # This archive created: Thu Feb 6 15:15:02 1986 export PATH; PATH=/bin:$PATH echo shar: extracting "'README'" '(1552 characters)' if test -f 'README' then echo shar: will not over-write existing file "'README'" else cat << \SHAR_EOF > 'README' Dial is a (4.xBSD-only) communications program for tty lines. It is similar to tip(1), though it doesn't support as many options as tip and no file transfer. In addition to interactive access to a remote computer, dial can operate in a non-interactive way using a user-supplied state transition script. This is useful for accessing certain `server logins' on remote machines without human interaction and in cases where the connect/login procedure for a remote computer is complex, e.g. when a remote system is accessed through a PABX, LAN, or terminal selector. Compile dial using the following command: cc -o dial dial.c -ltermcap (the termcap library is solely used to read entries from /etc/remote). `dial.1' contains the manual page for dial. `example' contains a sample state transition script that can be interpreted by dial. It is probably of no particular use for you; we are using it here to connect to a server login on a remote UNIX machine through our local PABX (see the comments in `example'). But you can get an idea how a dial script looks like. Dial makes use of select(2), remote(5), and the Berkeley-UNIX tty driver. Thus, it is probably impossible to port it to non-BSD systems. I don't know if it will run under 4.3BSD (we haven't got 4.3 here, yet). Please mail bug-reports and useful modifications to: ...ihnp4!seismo!unido!tub!net or net@DB0TUI6.BITNET ...!mcvax!unido!tub!net Regards, Oliver Laumann Technical University of Berlin, Communications and Operating Systems Research Group. SHAR_EOF if test 1552 -ne "`wc -c < 'README'`" then echo shar: error transmitting "'README'" '(should have been 1552 characters)' fi fi # end of overwriting check echo shar: extracting "'dial.1'" '(7835 characters)' if test -f 'dial.1' then echo shar: will not over-write existing file "'dial.1'" else cat << \SHAR_EOF > 'dial.1' .TH DIAL 1 "23 January 1986" .UC 4 .SH NAME dial \- connect to a remote system using a state transition script .SH SYNOPSIS .B dial [ .B \-v ] [ .B \-p ] [ \-l\f2line\fP ] [script-file [ arguments... ]] .SH DESCRIPTION .I Dial establishes a connection to a remote machine using a user-supplied state transition script. .I Dial is mainly used to communicate with remote server-logins in a non-interactive way, or for interactive sessions on a remote machine when the login/logout procedures should be carried out automatically. .PP The .B \-l\f2line\fP parameter specifies the line to be opened to establish the connection. If it is a relative pathname, .I dial prepends the string .I ``/dev/'' to the specified line. If the line does not exist, .I dial interprets .I line as the system name of the remote machine and uses the file /etc/remote to find out how to reach the system and how the line parameters (e.g. baud rate) should be set; refer to .IR remote (5) for a full description. .PP The .B \-v option causes .I dial to print on standard output diagnostic information for each state transition. This can be helpful for debugging .I dial scripts. .PP .I Script-file contains the state transition table; if .I script-file is `\-', standard input is used. If .I script-file cannot be opened, the value of the environment variable ``DIALDIR'' (if present) is prepended to the file name. If this also fails, the path ``/usr/local/lib/dial/'' is prepended to the specified file name. .PP Lines in the script beginning with `#' are treated as comment lines. Comment lines and blank lines are ignored. All occurrences of `$\f2n\fP' in the script are substituted by the \f2n\fPth optional argument from the command line; the first argument is `$0'. `$*' is substituted by the concatenation of all arguments separated by blanks. All occurrences of `${\f2symbol\fP}' are replaced by the value of the environment variable .I symbol. If .I symbol begins with `~' or `/', it is interpreted as the name of a file, and the contents of the file is interpolated into the script (\f2dial\fP performs .IR csh (1)-style expansion of `~' and `~user'). .PP The script file contains a list of state definitions; each definition is of the form .RS .HP .nf state-name send-string [[time-out][,delay]] next-state pattern [output-string] next-state pattern [output-string] \0\0\0... .fi .RE .PP The first line of each state definition starts in column 0; the following lines are indented by spaces or tabs. .I State-name is the name of the state; .I send-string is a string that is sent to the remote system whenever the state is entered. When a state has been entered, .I dial receives data from the remote system until the characters read so far match one of the .I patterns listed in the state definitions. If a match occurs, the optional .I output-string is sent to the standard output, and then the corresponding .I next-state is entered. The order of the .I "next-state\-pattern" pairs is significant. .PP When the .B \-p options is given, the .I output-string field of each rule is ignored. Instead, all characters received from the remote system are sent to the standard output. .PP When a time-out occurs while receiving from the remote system, a transition to the state .B exit is performed (if not specified otherwise). The default time-out for each state is 10 seconds; it can be changed as described below. The optional .I time-out field in each state definition can be used to assign a different time-out value to this state. Likewise, the .I .delay field can be used to specify a delay (measured in seconds) to be performed before the .I send-string is transmitted. Both fields are optional. Thus, .B ``,1.5'' means ``use the default time-out and a delay of 1.5 seconds''. .PP Each time the symbol `\\<' or `\\?' is encountered in the .I send-string field of a state definition, .I dial reads one input line from standard input and replaces the symbol with that line (not including the terminating newline). In case of `\\?', the read is performed in CBREAK mode with all special characters as well as echo turned off. This is useful when passwords should be interpolated into the data sent to the remote machine, but should not appear directly in the script. .PP .I Pattern is a simple character string; `.' within a pattern matches any character. The special pattern `*' matches anything (useful for catch-all rules); the pattern `#' denotes .IR timeout , that is, the corresponding .I next-state is entered when a time-out occurs. In .IR output-string, the symbol `&' denotes the string that matched the corresponding pattern. This mechanism is used to print on standard output characters received from the remote system or diagnostic messages. The special interpretation of `.', `#', `*', `&', `$', `\\<', and `\\?' is suppressed when the symbol is preceded by `\\' (backslash). .PP .IR Send-string , .IR pattern , and .I output-string can be surrounded by double-quotes. This is useful when blanks should be included or to denote an empty string. The symbol `\\r' can be used to denote .IR "carriage return" , `\\n' stands for .IR "line feed" . In addition, a `\\' followed by one to three octal digits stands for the character whose ASCII code is given by those digits. .PP When .I dial is called, a transition to the first state listed in the script file is performed. A transition to the special state .B exit causes .I dial to drop the connection and terminate. A transition to the state .B user causes .I dial to enter interactive mode. In this mode, all typed characters are transmitted to the remote machine, while all characters received from the remote machine are sent to standard output (that is, .I dial acts as if you had been connected to the remote machine using .IR tip (1)). .PP If no script file is given, or if the specified script-file does not contain any state definitions, .I dial performs a transition to the state .BR user , that is, opens an interactive session. .PP If the special .I "escape character" (usually `~') followed by carriage return or space is typed in interactive mode, .I dial sends the .I "prompt string" (usually `:') to standard output and reads from standard input the name of a new state. If a valid state name has been entered, .I dial leaves the interactive mode and enters the given state. .PP If the .I "escape character" is followed by either `.' or the end-of-file character (usually `^D'), .I dial enters the state .B exit immediately, that is, closes the connection and terminates. .I Dial can be suspended by typing .I escape followed by the suspend process character (usually `^Z'). .I Escape followed by `!' forks a shell; .I escape followed by `?' prints a summary of available .I escape commands. .PP Lines in the script file of the form .IP keyword = value .PP are used to assign values to certain parameters. Valid keywords are .B timeout (the default time-out to be used), .B delay (the default delay), .B line (the line to be opened), .B escape (the .IR "escape character" ), and .B prompt (the .IR "prompt string" ). The value for .B line is overridden by the .B \-l\f2line\fP option. .SH EXAMPLES The following simple script prints on standard output each character received from /dev/tty0. It terminates when a `.' is received or when a timeout of 0.5 second occurs. .PP .RS .nf # Simple example for a dial script: line=tty0 loop "" .5 exit \\. "Done.\\n" exit # "Got time-out.\\n" loop * & .fi .RE .ta \w'/usr/spool/uucp/LCK..*\0\0\0'u .SH FILES .nf /usr/local system-wide \f2dial\fP scripts .br ${DIALDIR} private \f2dial\fP scripts .br /usr/spool/uucp/LCK..* lock file for \f2uucp\fP and \f2tip\fP .fi .SH "SEE ALSO" tip(1C) .SH AUTHOR Oliver Laumann .SH BUGS The number of characters actually matched by the pattern `*' is not deterministic. SHAR_EOF if test 7835 -ne "`wc -c < 'dial.1'`" then echo shar: error transmitting "'dial.1'" '(should have been 7835 characters)' fi fi # end of overwriting check echo shar: extracting "'dial.c'" '(21088 characters)' if test -f 'dial.c' then echo shar: will not over-write existing file "'dial.c'" else cat << \SHAR_EOF > 'dial.c' /* dial -- dial a remote computer using a state transition table * * Copyright (c) 1986, Oliver Laumann, Technical University of Berlin. * Not derived from licensed software. * * Permission is granted to freely use, copy, modify, and redistribute * this software, provided that no attempt is made to gain profit from it, * the author is not construed to be liable for any results of using the * software, alterations are clearly marked as such, and this notice is * not modified. */ #include #include #include #include #include #include #include #include #include #define TIMEOUT 10 #define DELAY 0 #define BUFLEN 1024 #define EXIT ((struct state *)0) #define LOCKFN "/usr/spool/uucp/LCK..%s" #define DIALLIB "/usr/local/lib/dial" #define REMOTE "/etc/remote" #define FULL 0 /* match() return values */ #define PART 1 #define NONE 2 #define ISSPACE(x) (x == ' ' || x == '\t') #define ISOCT(x) (x >= '0' && x <= '7') #define ISDEC(x) (x >= '0' && x <= '9') char *script; char *line; int escape = '~'; char *prompt = "\r\n:"; struct timeval timeout = {TIMEOUT, 0}; struct timeval delay = {DELAY, 0}; char obuf[BUFLEN]; char *obp = obuf; char rbuf[BUFLEN]; char *rbp = rbuf; int vflag; int pflag; int ttyf; struct sgttyb ottyb; struct sgttyb consb; struct tchars otc; struct ltchars oltc; short coflags; int ttydone; int consdone; char *myname; char **argp; int numargs; char lockfile[BUFLEN]; int locked; int undef; char *dialdir; int baud = -1; int bdconst[] = { B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600, EXTA, EXTB }; int bdnum[] = { 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 0 }; #define S_STATE 1 struct state { struct state *s_next; char *s_string; int s_len; char *s_name; char *s_output; int s_outlen; struct timeval s_timeout; struct timeval s_delay; int s_stat; } *stab; extern errno; extern char **environ; char *malloc(), *alloc(), *memsav(), *getenv(), *tgetstr(), *fnexpand(); struct state *enter(), *receive(), *next_state(), *user(); struct passwd *getpwnam(), *getpwuid(); long atol(); main (ac, av) char **av; { register struct state *next; register char *p; int Exit(); myname = ac < 1 ? "dial" : av[0]; if (ac < 1) { help: err ("Use: %s [-v] [-p] [-lline] [script-file [args...]]\n", myname); } while (--ac) { p = *++av; if (*p == '-' && p[1] != '\0') { if (*++p == 'v') { ++vflag; } else if (*p == 'p') { ++pflag; } else if (*p == 'l') { line = ++p; } else goto help; } else if (!script) { script = p; } else { argp = av; numargs = ac; break; } } if (script) { dialdir = getenv ("DIALDIR"); get_script (); check_script (); } if (!line || line[0] == '\0') err ("No tty line specified.\n"); signal (SIGHUP, Exit); signal (SIGINT, Exit); open_line (); if (!stab) say ("Connected.\n"); set_tty (); next = (stab ? stab : user ()); while (next != EXIT) next = enter (next); if (vflag) say ("State `exit' -- done.\n"); Exit (0); } get_script () { FILE *f = NULL; char fn[BUFLEN], lbuf[BUFLEN], buf[BUFLEN]; register char *p, *s; register struct state *sp, *tmp; register c, delim, lno = 0; struct stat st; if (!strcmp (script, "-")) { f = stdin; script = "stdin"; } else { if (stat (script, &st) || (st.st_mode & S_IFMT) == S_IFDIR || (f = fopen (script, "r")) == NULL) { if (dialdir) { sprintf (fn, "%s/%s", dialdir, script); f = fopen (fn, "r"); } if (f == NULL) { sprintf (fn, "%s/%s", DIALLIB, script); if ((f = fopen (fn, "r")) == NULL) err ("Cannot open `%s'.\n", script); } } } while (1) { ++lno; if (fgetstr (f, lbuf, BUFLEN-1) == EOF) break; if (!expand_args (lbuf, buf)) goto error; for (p = buf; ISSPACE(*p); ++p) ; if (*p == '#' || *p == '\0') continue; if (p == buf) { if (getkey (p)) continue; } else if (!stab) goto error; tmp = (struct state *)alloc (sizeof (struct state)); tmp->s_stat = (p == buf ? S_STATE : 0); for (s = p; *s && !ISSPACE(*s); ++s) ; if (*s == '\0') goto error; *s = '\0'; tmp->s_name = memsav (p, s-p+1); for (++s; ISSPACE(*s); ++s) ; if (delim = (*s == '"')) ++s; for (p = s; *s && (delim ? *s != '"' : !ISSPACE(*s)); ++s) ; if (delim) { if (*s == '\0') goto error; *s++ = '\0'; } c = *s; *s = '\0'; tmp->s_len = cook (p, p); tmp->s_string = memsav (p, tmp->s_len); tmp->s_outlen = 0; tmp->s_timeout = timeout; tmp->s_delay = delay; if (c) { for (++s; ISSPACE(*s); ++s) ; if (*s) { if (delim = (*s == '"')) ++s; for (p = s; *s && (delim ? *s != '"' : !ISSPACE(*s)); ++s) ; if (delim) { if (*s == '\0') goto error; *s++ = '\0'; } *s = '\0'; if (tmp->s_stat & S_STATE) { if (!set_par (tmp, p)) goto error; } else { tmp->s_outlen = cook (p, p); tmp->s_output = memsav (p, tmp->s_outlen); } } } if (stab) sp = sp->s_next = tmp; else stab = sp = tmp; sp->s_next = 0; } fclose (f); return; error: err ("%s: syntax error in line %d.\n", script, lno); } expand_args (from, to) char *from, *to; { register char *p, *q, *t, *s = to, *endp = to+BUFLEN-1; register n; FILE *f; for (p = from; *p; ++p) { if (s == endp) break; if (*p == '$') { if (p > from && p[-1] == '\\') { s[-1] = '$'; } else if (ISDEC(p[1])) { if (*++p - '0' <= numargs) { for (q = argp[*p - '0']; s < endp && *q; *s++ = *q++) ; } } else if (p[1] == '*') { for (n = 0; n < numargs; ++n) { for (q = argp[n]; s < endp && *q; *s++ = *q++) ; if (n < numargs-1 && s < endp) *s++ = ' '; } ++p; } else if (p[1] == '{') { ++p; for (q = ++p; *p && *p != '}'; ++p) ; if (*p == '\0') return (0); *p = '\0'; if (*q == '~' || *q == '/') { t = fnexpand (q); if ((f = fopen (t, "r")) == NULL) err ("Cannot open `%s'.\n", t); while ((n = getc (f)) != EOF && s < endp) *s++ = n; fclose (f); } else if (t = getenv (q)) { while (s < endp && *t) *s++ = *t++; } } else *s++ = '$'; } else *s++ = *p; } *s = '\0'; return (1); } char *fnexpand (s) char *s; { static char buf[BUFLEN]; struct passwd *pw; register char c, *p, *q = s+1; if (*s != '~') return (s); for (p = q; (c = *p) && c != '/'; ++p) ; *p = '\0'; if (p == q) { if (!(pw = getpwuid (getuid ()))) err ("Cannot get home directory.\n"); } else if (!(pw = getpwnam (q))) err ("Unknown user: %s.\n", q); if (c) { sprintf (buf, "%s/%s", pw->pw_dir, ++p); return (buf); } else return (pw->pw_dir); } set_par (sp, s) struct state *sp; char *s; { register char *p; for (p = s; *p && *p != ','; ++p) ; if (*p == ',') *p++ = '\0'; if (*s) { if (!isnum (s)) return (0); set_time (&sp->s_timeout, s); } if (*p) { if (!isnum (p)) return (0); set_time (&sp->s_delay, p); } return (1); } set_time (tp, s) struct timeval *tp; char *s; { register char *p; register long div; for (p = s; *p && *p != '.'; ++p) ; if (*p == '.') *p++ = '\0'; tp->tv_sec = atoi (s); if (p) { p[6] = '\0'; tp->tv_usec = atol (p); div = 1; while (*p) { div *= 10; ++p; } tp->tv_usec *= 1000000L / div; } else tp->tv_usec = 0; } getkey (s) char *s; { register char *k, *p, *q; for (p = s; *p; ++p) if (*p == '=' && !(p > s && p[-1] == '\\')) break; if (*p == '\0') return (0); for (*p++ = '\0'; ISSPACE(*p); ++p) ; for (q = p; *q && !ISSPACE(*q); ++q) ; *q = '\0'; for (k = s; *k && !ISSPACE(*k); ++k) ; *k = '\0'; if (!strcmp (s, "line")) { if (*p == '\0') err ("Bad value for keyword `line'.\n"); if (!line) line = memsav (p, strlen (p)+1); } else if (!strcmp (s, "timeout")) { if (!isnum (p)) err ("Bad value for keyword `timeout'.\n"); set_time (&timeout, p); } else if (!strcmp (s, "delay")) { if (!isnum (p)) err ("Bad value for keyword `delay'.\n"); set_time (&delay, p); } else if (!strcmp (s, "escape")) { if (strlen (p) != 1) err ("Bad value for keyword `escape'.\n"); escape = *p & 0177; } else if (!strcmp (s, "prompt")) { prompt = memsav (p, strlen (p)+1); } else err ("Invalid keyword `%s'.\n", s); return (1); } check_script () { register struct state *sp; for (sp = stab; sp; sp = sp->s_next) { if (!(sp->s_stat & S_STATE)) continue; if (!sp->s_next || (sp->s_next->s_stat & S_STATE)) err ("No transitions for state `%s'.\n", sp->s_name); } } struct state *enter (sp) struct state *sp; { if (vflag) say ("Entering state %s.\n", sp->s_name); if (sp->s_delay.tv_sec || sp->s_delay.tv_usec) { flushbuf (); if (select (0, (int *)0, (int *)0, (int *)0, &sp->s_delay) == -1) { perror ("select"); Exit (1); } } sendstr (sp->s_string, sp->s_len); return (receive (sp)); } sendstr (sbuf, len) char *sbuf; register len; { char buf[BUFLEN], ibuf[BUFLEN]; register char *s, *p, *t; register n; char c; for (s = sbuf, p = buf; len && p < buf+BUFLEN; --len, ++p, ++s) { if (*s == '\\') { if (s[1] == '\\') { *p = '\\'; ++s; } else if (s[1] == '<' || s[1] == '?') { ++s; flushbuf (); if (*s == '<') { n = Read (0, ibuf, BUFLEN); c = '\n'; } else { set_cons (); for (t = ibuf, n = 0; n < BUFLEN; ++n, ++t) { Read (0, t, 1); if (*t == '\r') break; } reset_cons (); if (!pflag) say ("\n"); c = '\r'; } for (t = ibuf; n && *t != c && p < buf+BUFLEN; --n) *p++ = *t++; } else *p = '\\'; } else *p = *s; } if (vflag) { say ("Sending \""); prstr (buf, p-buf); say ("\".\n"); } if (p > buf) Write (ttyf, buf, p-buf); } fill (sp) struct state *sp; { register i; register char *p; int rbits = (1 << ttyf); if (rbp == rbuf+BUFLEN) err ("Pattern space overflow.\n"); flushbuf (); i = select (ttyf+1, &rbits, (int *)0, (int *)0, &sp->s_timeout); if (i == -1) { perror ("select"); Exit (1); } if (i == 0) { if (vflag) say (" Got time-out.\n"); return (0); } i = Read (ttyf, rbp, rbuf+BUFLEN-rbp); p = rbp; do { *p++ &= 0177; } while (--i); if (vflag) { say (" Got \""); prstr (rbp, p-rbp); say ("\".\n"); } if (pflag) Write (1, rbp, p-rbp); rbp = p; return (1); } shift (n) { bcopy (rbuf+n, rbuf, rbp-rbuf-n); rbp -=n; } struct state *receive (sp) struct state *sp; { register struct state *p, *fullp, *partp; register r, mlen, dofill = 0; int len; while (1) { if ((rbp == rbuf || dofill) && !fill (sp)) { rbp = rbuf; for (p = sp->s_next; p && !(p->s_stat & S_STATE); p = p->s_next) { if (p->s_len == 1 && p->s_string[0] == '#') { output (p, (char *)0, 0); return (next_state (p->s_name, 1)); } } return (EXIT); } dofill = 0; fullp = partp = 0; for (p = sp->s_next; p && !(p->s_stat & S_STATE); p = p->s_next) { if (p->s_len == 1 && p->s_string[0] == '#') continue; r = match (rbuf, rbp-rbuf, p->s_string, p->s_len, &len); if (r == FULL && !fullp) { fullp = p; mlen = len; } else if (r == PART && !partp) { partp = p; } } if (!fullp && !partp) { shift (1); if (vflag) say ("No match -- shift(1).\n"); continue; } if (fullp && !partp) { if (fullp->s_len == 1 && fullp->s_string[0] == '*') mlen = amatch (sp); if (vflag) say ("Full match <%s> -- shift(%d).\n", fullp->s_name, mlen); output (fullp, rbuf, mlen); shift (mlen); return (next_state (fullp->s_name, 1)); } if (vflag) say ("Partial match <%s>.\n", partp->s_name); dofill = 1; } } amatch (ap) struct state *ap; { register struct state *sp; register char *p; int notused; for (p = rbuf; p < rbp; ++p) { for (sp = ap->s_next; sp && !(sp->s_stat & S_STATE); sp = sp->s_next) { if (sp->s_len == 1 && (sp->s_string[0] == '#' || sp->s_string[0] == '*')) continue; if (match (p, rbp-p, sp->s_string, sp->s_len, ¬used) != NONE) goto done; } } done: return (p-rbuf); } match (s, slen, t, tlen, len) register char *s, *t; register slen, tlen; int *len; { register otlen = tlen, oslen = slen; if (tlen == 1 && t[0] == '*') return (FULL); while (tlen) { if (!slen) return (PART); if (*t != '\\') { if (*s != *t) if (*t != '.' || (tlen < otlen && t[-1] == '\\')) return (NONE); ++s; --slen; } ++t; --tlen; } *len = oslen-slen; return (FULL); } struct state *next_state (s, noise) char *s; { register struct state *sp; undef = 0; if (!strcmp (s, "exit")) return (EXIT); if (!strcmp (s, "user")) { return (user ()); } for (sp = stab; sp; sp = sp->s_next) if ((sp->s_stat & S_STATE) && !strcmp (sp->s_name, s)) return (sp); ++undef; if (noise) err ("State `%s' not found in script.\n", s); #ifdef lint return (sp); #endif } struct state *user () { char c, buf[BUFLEN]; register n, l, gotesc = 0; int rbits; register char *p, *s; register struct state *sp; flushbuf (); signal (SIGINT, SIG_IGN); set_cons (); while (1) { rbits = (1 << 0) | (1 << ttyf); n = select (ttyf+1, &rbits, (int *)0, (int *)0, (struct timeval *)0); if (n == -1) { perror ("select"); Exit (1); } if (rbits & (1 << 0)) { if (gotesc) { Read (0, &c, 1); if (c == '.' || c == otc.t_eofc) { reset_cons (); say ("\nConnection closed.\n"); return (EXIT); } else if (c == oltc.t_suspc) { reset_cons (); kill (getpid (), SIGTSTP); set_cons (); } else if (c == '!') { reset_cons (); say ("\n"); if (system ((s = getenv ("SHELL")) ? s : "/bin/sh") == 127) say ("Cannot execute shell.\n"); else say ("\n!\n"); set_cons (); } else if (c == '?') { reset_cons (); print_help (); set_cons (); } else if (c == ' ' || c == '\r') { Write (1, prompt, strlen (prompt)); reset_cons (); if ((n = Read (0, buf, BUFLEN)) > 1) { buf[n-1] = '\0'; for (p = buf; ISSPACE(*p); ++p) ; for (s = p; *s && !ISSPACE(*s); ++s) ; *s = '\0'; if (s > p) { sp = next_state (p, 0); if (undef) { say ("`"); prstr (p, s-p); say ("' is undefined.\n"); } else { signal (SIGINT, Exit); return (sp); } } } set_cons (); } else Write (ttyf, &c, 1); gotesc = 0; } else { n = Read (0, buf, BUFLEN); for (p = buf, l = n; l && *p != escape; ++p, --l) ; if (l) { if (p > buf) Write (ttyf, buf, p-buf); ++gotesc; while (--l) ioctl (0, TIOCSTI, ++p); } else Write (ttyf, buf, n); } } if (rbits & (1 << ttyf)) Write (1, buf, Read (ttyf, buf, BUFLEN)); } } print_help () { char e[3]; if (escape >= ' ' && escape <= '~') sprintf (e, "%c", escape); else sprintf (e, "^%c", escape ^ '@'); say ("\n%s. close connection and terminate.\n", e); say ("%s! fork a shell.\n", e); say ("%s^Z suspend %s.\n", e, myname); say ("%s\n", e); say ("%s leave interactive mode and enter new state.\n", e); } output (sp, buf, len) struct state *sp; char *buf; { char tbuf[BUFLEN]; register char *s = sp->s_output, *p = tbuf; register i, olen = sp->s_outlen; if (pflag || !olen) return; while (olen) { if (p == tbuf+BUFLEN) break; if (*s == '&' && !(s > sp->s_output && s[-1] == '\\')) { for (i = 0; i < len && p < tbuf+BUFLEN; ++i) *p++ = buf[i]; } else if (*s != '\\') *p++ = *s; ++s; --olen; } if (vflag) { say ("Print \""); prstr (tbuf, p-tbuf); say ("\".\n"); } else { outbuf (tbuf, p-tbuf); } } outbuf (buf, len) register char *buf; register len; { register l; again: l = obuf + BUFLEN - obp; if (l > len) l = len; bcopy (buf, obp, l); obp += l; if (len -= l) { flushbuf (); buf += l; goto again; } } flushbuf () { if (obp > obuf) { Write (1, obuf, obp-obuf); obp = obuf; } } open_line () { char fn[BUFLEN]; char tbuf[1024], cbuf[1024]; int i, f; struct stat statbuf; char **oldenv, *envp[2], *p, *pp = cbuf; sprintf (fn, line[0] == '/' ? "%s" : "/dev/%s", line); if (stat (fn, &statbuf) == -1 && errno == ENOENT) { sprintf (tbuf, "TERMCAP=%s", REMOTE); envp[0] = memsav (tbuf, strlen (tbuf)); envp[1] = 0; oldenv = environ; environ = envp; if ((i = tgetent (tbuf, line)) == -1) err ("Cannot open `%s'.\n", REMOTE); else if (i == 0) err ("`%s': no such line or remote system.\n", line); if ((p = tgetstr ("dv", &pp)) == 0) err ("No `dv' capability for system `%s'.\n", line); strcpy (fn, p); if ((baud = tgetnum ("br")) != -1) { for (i = 0; bdnum[i] && baud != bdnum[i]; ++i) ; if (!bdnum[i]) err ("Invalid baud rate %d for system `%s'.\n", baud, line); baud = bdconst[i]; } environ = oldenv; } if (!strncmp (fn, "/dev/", 5)) { sprintf (lockfile, LOCKFN, fn+5); if ((f = open (lockfile, O_CREAT|O_EXCL, 0)) == -1) { if (errno == EEXIST) err ("`%s' is already in use.\n", line); perror (lockfile); Exit (1); } locked++; close (f); } if ((ttyf = open (fn, O_RDWR)) == -1) { perror (line); Exit (1); } ioctl (ttyf, TIOCHPCL, (char *)0); /* Hang up phone on last close. */ } set_cons () { struct tchars tc; struct ltchars ltc; ioctl (0, TIOCGETP, &consb); coflags = consb.sg_flags; consb.sg_flags &= ~ECHO; consb.sg_flags &= ~CRMOD; consb.sg_flags |= CBREAK; ioctl (0, TIOCSETP, &consb); ioctl (0, TIOCGETC, &tc); otc = tc; tc.t_intrc = tc.t_quitc = -1; ioctl (0, TIOCSETC, &tc); ioctl (0, TIOCGLTC, <c); oltc = ltc; ltc.t_suspc = ltc.t_dsuspc = ltc.t_lnextc = -1; ioctl (0, TIOCSLTC, <c); ++consdone; } reset_cons () { if (consdone) { consb.sg_flags = coflags; ioctl (0, TIOCSETP, &consb); ioctl (0, TIOCSETC, &otc); ioctl (0, TIOCSLTC, &oltc); consdone = 0; } } set_tty () { struct sgttyb ttyb; ioctl (ttyf, TIOCGETP, &ttyb); ottyb = ttyb; ttyb.sg_flags &= ~ECHO; ttyb.sg_flags &= ~CRMOD; ttyb.sg_flags |= (RAW|TANDEM); if (baud != -1) ttyb.sg_ispeed = ttyb.sg_ospeed = baud; ioctl (ttyf, TIOCSETP, &ttyb); ++ttydone; } reset_tty () { if (ttydone) ioctl (ttyf, TIOCSETP, &ottyb); } Exit (c) { flushbuf (); reset_tty (); reset_cons (); if (locked) { if (unlink (lockfile) == -1) perror (lockfile); } exit (c); } Write (f, buf, len) char *buf; { if (write (f, buf, len) != len) { if (errno == EIO) err ("Lost line.\n"); else { perror ("write"); Exit (1); } } } Read (f, buf, len) char *buf; { register n; if ((n = read (f, buf, len)) == -1) { if (errno == EIO) err ("Lost line.\n"); else { perror ("read"); Exit (1); } } if (n == 0) err ("EOF on tty.\n"); return (n); } char *memsav (s, len) char *s; { register char *p; p = alloc (len); bcopy (s, p, len); return (p); } char *alloc (n) { register char *p; if ((p = malloc (n)) == 0) err ("Out of memory.\n"); return (p); } cook (dst, src) char *dst, *src; { register char *p, *t; register n, c; for (p = dst, t = src; c = *t; ++t) { if (c == '\\') { if (ISOCT(t[1])) { for (n = c = 0; n < 3; ++n) { c = (c << 3) | (*++t - '0'); if (!ISOCT(t[1])) break; } } else { if (t[1] == 'r') { c = '\r'; ++t; } else if (t[1] == 'n') { c = '\n'; ++t; } else if (t[1] == '\0') break; } } *p = c; ++p; } return (p - dst); } prstr (s, len) char *s; int len; { register c, l; for (l = len; l; --l) { if ((c = *s++) == '\r') say ("\\r"); else if (c == '\n') say ("\\n"); else if (c < ' ' || c > '~') say ("\\%03o", c & 0377); else say ("%c", c); } } fgetstr (f, buf, len) register FILE *f; char *buf; { register c, n; register char *p = buf; n = len; while ((c = getc (f)) != EOF && c != '\n') if (n) { --n; *p++ = c; } *p = '\0'; return (c == EOF && p == buf ? EOF : p - buf); } isnum (s) register char *s; { register point = 0; if (*s == '\0') return (0); for ( ; *s; ++s) { if (*s == '.') { if (point) return (0); ++point; } else if (!ISDEC(*s)) return (0); } return (1); } /*VARARGS1*/ say (fmt, p1, p2, p3, p4) char *fmt; { fprintf (stdout, fmt, p1, p2, p3, p4); } /*VARARGS1*/ err (fmt, p1, p2, p3, p4) char *fmt; { fprintf (stderr, fmt, p1, p2, p3, p4); Exit (1); } SHAR_EOF if test 21088 -ne "`wc -c < 'dial.c'`" then echo shar: error transmitting "'dial.c'" '(should have been 21088 characters)' fi fi # end of overwriting check # End of shell archive exit 0