#include #include #include #include #include #include /* * MODEM -- Kind of like CU. Connects your terminal to the modem port. */ #define TERM "/dev/tty" /* user terminal device */ #define PORT "/dev/tty0" /* default to modem port */ #define BAUD 2400 /* default baud rate */ #define ESCAPE '\001' /* escape character */ #define SZ_OBUF 4096 /* max output data to buffer */ #define SZ_LINE 1024 /* max size line */ #define SZ_DBUF 128 /* size of a data buffer for copies */ #define SZ_FNAME 256 /* max size filename */ #define NLWAIT 200 /* pause, msec/line, for copies */ #define IOWAIT 50 /* delay between reads, heavy i/o */ #define CMDWAIT 1000 /* wait after command, msec */ #define EOFWAIT 200 /* wait after sending EOF, msec */ #define NFAST 8 /* number of "fast" reads after type */ #define TOP 128 /* flush output every TOP bytes */ extern int errno; static int rfd = 0; static int debug = 0; static int watch = 0; static int top = TOP; static int nonblock = 0; static int keepalive = 1; static int binary = 0; static int dataline = 0; static int baud = BAUD; static int hwflow = 0; static char cmd[SZ_LINE]; static char obuf[SZ_OBUF]; static char lbuf[SZ_LINE]; static char dbuf[SZ_DBUF]; static char port[SZ_FNAME]; static char lockfile[SZ_FNAME]; static char infile[SZ_FNAME]; static char outfile[SZ_FNAME]; static char initmsg[SZ_LINE]; static char exitmsg[SZ_LINE]; static char eofstr[2] = { 0x1a, 0 }; static int ringring, terminal, modem; static FILE *logfile = NULL; /* MODEM -- Open a virtual terminal connection to a modem port. The port is * assumed to already be active, i.e., this program does not do any dialing. * On my system I just type "modem" and I am talking to the modem, or if I * have already dialed into a remote system, to the remote system. One can * suspend or exit/restart the modem program without losing the connection. * * Usage: * * modem [-b baud] [-p port] [-i initmsg] [-e exitmsg] [-data] * * Baud rates of up to 19200 are supported. The default baud rate is 19200. * The escape character is ctrl/a. The following escapes are recognized: * * ctrl/a ctrl/a exit * ctrl/a ctrl/x suspend * ctrl/a ctrl/p put ascii file (can lose data) * ctrl/a ctrl/l log [stop logging] output to a file * ctrl/a ctrl/d toggle debug output (to file "modem.io") * * Edit the program if you want a different default baud rate, escape char, * or modem port. */ main (argc, argv) int argc; char **argv; { register char *op, *otop; register int fast; int efd, bits, stat, arg, nchars, locked; int kb, bytes, tflag, mflag, exitstatus; struct termio tty, o_tty; struct termio mty, o_mty; struct timeval delay, idle; char *argp, *ip, *cp; FILE *db, *fp; char ch; locked = 0; exitstatus = 0; initmsg[0] = '\0'; exitmsg[0] = '\0'; strcpy (port, PORT); /* Process the argument list. */ for (arg=1; arg < argc; arg++) if (argp = argv[arg]) { if (!strcmp (argp, "-d")) { /* debug output */ debug++; } else if (!strcmp (argp, "-w")) { /* local echo */ watch++; } else if (!strcmp (argp, "-nb")) { /* nonblocking */ nonblock++; } else if (!strcmp (argp, "-hw")) { /* hw control */ hwflow++; } else if (!strcmp (argp, "-b") || !strcmp (argp, "-s")) { /* baud rate */ if (argp = argv[++arg]) baud = atoi(argp); } else if (!strcmp (argp, "-data")) { dataline++; } else if (!strcmp (argp, "-p")) { /* port */ if (argp = argv[++arg]) if (argp[0] == '/') strcpy (port, argp); else sprintf (port, "/dev/%s", argp); } else if (!strcmp (argp, "-i")) { /* initmsg */ if (argp = argv[++arg]) { for (ip=argp, op=initmsg; *ip; ip++) { if (*ip == '\\') { switch (*(++ip)) { case 'n': *op++ = '\n'; break; case 'r': *op++ = '\r'; break; default: *op++ = *ip; break; } } else *op++ = *ip; } *op = '\0'; } } else if (!strcmp (argp, "-e")) { /* exitmsg */ if (argp = argv[++arg]) { for (ip=argp, op=exitmsg; *ip; ip++) { if (*ip == '\\') { switch (*(++ip)) { case 'n': *op++ = '\n'; break; case 'r': *op++ = '\r'; break; default: *op++ = *ip; break; } } else *op++ = *ip; } *op = '\0'; } } else if (!strcmp (argp, "-obuf")) { /* output buf size */ if (argp = argv[++arg]) { top = atoi(argp); if (top > SZ_OBUF) top = SZ_OBUF; } } } /* Check that the desired port is not locked. */ for (ip=cp=port; *ip; ip++) if (*ip == '/') cp = ip + 1; sprintf (lockfile, "/usr/spool/uucp/LCK..%s", cp); if (access (lockfile, 0) == 0) { fprintf (stderr, "port %s is already in use\n", cp); exitstatus = 1; goto abort; } /* Open the modem port. */ errno = 0; if ((modem = open (port, 2)) < 0) { fprintf (stderr, "cannot open %s\n", port); exitstatus = 1; goto abort; } else { /* Lockfile creation will fail unless the modem program is suid * root or uucp or otherwise has write perm in spool/uucp. Ignore * the error if we cannot create a lockfile. */ int fd; if ((fd = creat (lockfile, 0666)) < 0) errno = 0; else { locked++; close (fd); } } /* Set raw mode no echo on modem port. */ if (ioctl (modem, TCGETA, &o_mty) < 0) { fprintf (stderr, "cannot read modem port status\n"); exitstatus = 2; goto abort; } else { mty = o_mty; if (dataline) { mty.c_iflag = IGNBRK; mty.c_oflag = 0; mty.c_cflag = (CS8|CREAD|HUPCL); } else { mty.c_iflag &= ~(INLCR|ICRNL|IUCLC|BRKINT); mty.c_iflag |= (IGNBRK|IGNPAR|IXON|IXOFF|ISTRIP); mty.c_oflag &= ~(OPOST|ONLCR|TAB3); mty.c_lflag &= ~(ICANON|ISIG|ECHO|ECHOK); mty.c_cflag &= ~(PARENB|CSTOPB|HUPCL|CLOCAL); mty.c_cc[4] = 0; /* min */ mty.c_cc[5] = 0; /* time */ } switch (baud) { case 1200: mty.c_cflag = ((mty.c_cflag & ~CBAUD) | B1200); break; case 2400: mty.c_cflag = ((mty.c_cflag & ~CBAUD) | B2400); break; case 4800: mty.c_cflag = ((mty.c_cflag & ~CBAUD) | B4800); break; case 19200: mty.c_cflag = ((mty.c_cflag & ~CBAUD) | B19200); break; default: mty.c_cflag = ((mty.c_cflag & ~CBAUD) | B9600); break; } if (ioctl (modem, TCSETAF, &mty) < 0) { fprintf (stderr, "cannot set modem port mode\n"); perror ("modem"); exitstatus = 3; goto abort; } /* Enable hardware flow control. */ if (hwflow) { if (ioctl (modem, UIOCFLOW, 0) < 0) { fprintf (stderr, "cannot enable flow control\n"); perror ("modem"); exitstatus = 3; goto abort; } } /* Output the initialization string, if any, to the modem. */ if (initmsg[0]) write (modem, initmsg, strlen(initmsg)); } /* Open the user terminal. */ if ((terminal = open (TERM, 2)) < 0) { fprintf (stderr, "cannot open %s\n", TERM); exitstatus = 1; goto abort; } /* Set raw mode no echo on user terminal. */ if (ioctl (terminal, TCGETA, &o_tty) < 0) { fprintf (stderr, "cannot read terminal status\n"); exitstatus = 2; goto abort; } else { tty = o_tty; tty.c_iflag &= ~(INLCR|ICRNL|IUCLC|ISTRIP|BRKINT); tty.c_oflag &= ~OPOST; tty.c_lflag &= ~(ICANON|ISIG|ECHO); tty.c_cc[4] = 0; /* min */ tty.c_cc[5] = 0; /* time */ if (ioctl (terminal, TCSETAF, &tty) < 0) { fprintf (stderr, "cannot set terminal mode\n"); exitstatus = 3; goto abort; } } /* Enable nonblocking i/o for terminal ouptut. */ do_fcntl (terminal, F_SETFD, O_NDELAY|O_RDWR); if (debug) { if ((db = fopen ("modem.io", "a")) == NULL) { ioctl (terminal, TCSETAF, &o_tty); fprintf (stderr, "cannot open debug output file\n"); exitstatus = 6; goto abort; } fprintf (db, "------ terminal on %d, modem on %d\n", terminal, modem); fflush (db); } /* Check for any errors. */ if (errno) { ioctl (terminal, TCSETAF, &o_tty); perror ("Warning"); ioctl (terminal, TCSETAF, &tty); } /* Compute the select bitmask. */ tflag = (1 << terminal); mflag = (1 << modem); bits = (tflag|mflag); delay.tv_sec = 0; delay.tv_usec = 10000; /* 10 milliseconds */ idle.tv_sec = 5*60; idle.tv_usec = 0; otop = &obuf[SZ_OBUF]; errno = 0; op = obuf; fast = 0; /* Read successive characters from the terminal and copy to the remote * job, and read output from the remote job and copy to the local * terminal. */ do { if ((stat = select (32, (rfd=bits,&rfd), 0, 0, &delay)) < 0) { fprintf (stderr, "modem: select error\n"); fflush (db); break; } else if (stat == 0) { /* timeout */ if (op > obuf) goto flush; else { while ((stat = select(32,(rfd=bits,&rfd),0,0,&idle)) <= 0) { if (stat < 0) { fprintf (stderr, "modem: select error\n"); fflush (db); break; } if (keepalive) write (modem, " \177", 2); } } } if (debug) fprintf (db, "select returns %08o\n", rfd); /* Read from the user terminal. */ if (rfd & tflag) { if (read (terminal, &ch, 1) < 1) break; if (dataline) ch &= 0177; if (debug) fprintf (db, "read %03x from %d\n", ch, terminal); if (ch == ESCAPE) { /* ctrl/a */ if (debug) fprintf (db, "ctrl/a seen\n"); /* Get the escape command. */ if (select (32, (efd=tflag,&efd), 0,0,NULL) < 0) goto quit; if (read (terminal, &ch, 1) < 1) goto quit; /* Process the escape. */ switch (ch) { case 'A' - 64: /* ctrl/a ctrl/a */ goto quit; break; case 'X' - 64: /* ctrl/a ctrl/x */ /* Suspend. */ if (logfile) fflush (logfile); ioctl (terminal, TCSETAF, &o_tty); kill (getpid(), SIGSTOP); ioctl (terminal, TCSETAF, &tty); break; case 'D' - 64: /* ctrl/a ctrl/d */ /* Toggle debug output. */ if (debug) { fprintf (stderr, "[debug off] "); fflush (stderr); fclose (db); debug = 0; db = NULL; } else { if ((db = fopen ("modem.io", "a")) == NULL) { ioctl (terminal, TCSETAF, &o_tty); fprintf (stderr, "cannot open debug output file\n"); exitstatus = 6; goto abort; } fprintf (db, "------ terminal on %d, modem on %d\n", terminal, modem); fflush (db); debug = 1; fprintf (stderr, "[debug on] "); fflush (stderr); } break; case 'B' - 64: /* ctrl/a ctrl/b */ binary = 0; /* (disabled) */ goto putfile; case 'P' - 64: /* ctrl/a ctrl/p */ /* Put a file. */ binary = 0; putfile: ioctl (terminal, TCSETAF, &o_tty); do_fcntl (modem, F_SETFD, O_RDWR); /* Get the file names. */ printf ("put: "); fflush (stdout); if (fgets (lbuf, SZ_LINE, stdin) != NULL) { for (ip=lbuf; isspace(*ip); ip++) ; for (cp=infile; *ip; ip++) if (isspace (*ip)) break; else *cp++ = *ip; *cp = '\0'; for (; isspace(*ip); ip++) ; for (cp=outfile; *ip; ip++) if (isspace (*ip)) break; else *cp++ = *ip; *cp = '\0'; } if (!infile[0] || ((fp = fopen(infile,"r")) == NULL)) { printf ("cannot open %s\n", infile); ioctl (terminal, TCSETAF, &tty); do_fcntl (modem, F_SETFD, O_NDELAY|O_RDWR); goto next; } if (!outfile[0]) strcpy (outfile, infile); /* Send the file. */ sprintf (cmd, "stty %s ; cat - >%s; stty %s\n", binary ? "raw -echo" : "-echo", outfile, binary ? "-raw echo" : "echo" ); puttext (modem, cmd, strlen(cmd)); wmsec (CMDWAIT); kb = bytes = 0; if (binary) { while ((nchars=fread(dbuf,1,SZ_DBUF,fp)) != NULL) { puttext (modem, dbuf, nchars); bytes += nchars; if (!watch && bytes/1024 > kb) { printf ("\r%d", ++kb); fflush (stdout); } } } else { while ((fgets (dbuf, SZ_DBUF, fp)) != NULL) { puttext (modem, dbuf, nchars=strlen(dbuf)); bytes += nchars; if (!watch && bytes/1024 > kb) { printf ("\r%d", ++kb); fflush (stdout); } } } fclose (fp); puttext (modem, eofstr, 1); wmsec (EOFWAIT); ioctl (terminal, TCSETA, &tty); do_fcntl (modem, F_SETFD, O_NDELAY|O_RDWR); op = obuf; break; case 'L' - 64: /* ctrl/a ctrl/l */ /* Toggle logging of output to a file. */ if (logfile) { fclose (logfile); logfile = NULL; fprintf (stderr, "[logging off] "); fflush (stderr); } else { ioctl (terminal, TCSETAF, &o_tty); /* Get the logfile name. */ printf ("logfile: "); fflush (stdout); if (fgets (lbuf, SZ_LINE, stdin) != NULL) { for (ip=lbuf; isspace(*ip); ip++) ; for (cp=outfile; *ip; ip++) if (isspace (*ip)) break; else *cp++ = *ip; *cp = '\0'; } ioctl (terminal, TCSETAF, &tty); if (outfile[0]) { if ((logfile = fopen (outfile, "a")) == NULL) { fprintf (stderr, "cannot append to %s\r\n", outfile); goto next; } } } op = obuf; break; default: goto next; break; } } else { stat = write (modem, &ch, 1); if (debug) fprintf (db, "write %03x to %d: stat=%d\n", ch, modem, stat); if (stat != 1) goto quit; /* Cancel the output buffer if ctrl/c. */ if (ch == '\003') op = obuf; } /* Typing causes the time delays to be adjusted to increase * responsiveness at the expense of shorter i/o transfers and * decreased bandwidth. If the result is a lot of output the * delays will quickly adjust back to the greater data volume. */ fast = NFAST; } next: /* Read from the remote job. */ if (rfd & mflag) { if (!fast) wmsec (IOWAIT); if ((nchars = read (modem, op, otop-op)) < 0) break; else if (nchars > 0) op += nchars; if (debug) { char cbuf[80], *ii, *oo; int n = (nchars < 8) ? nchars : 8; /* Dump the first 8 character values in printable form. */ for (ii=(op-nchars), oo=cbuf; --n >= 0; ii++) { *oo++ = '\''; if (isprint (*ii)) *oo++ = *ii; else { *oo++ = '^'; *oo++ = *ii + 'A' - 1; } *oo++ = '\''; *oo++ = ' '; } *oo++ = '\0'; fprintf (db, "read %03d chars from %d\t\t%s\n", nchars, modem, cbuf); } } /* Flush the output buffer periodically or if it fills. */ if (op > obuf && (fast || op - obuf > top)) { flush: if (debug) fprintf (db, "flush output buffer, %d chars\n", op-obuf); if ((stat = write (terminal, obuf, op - obuf)) != op-obuf) { if (stat < 0) { fprintf (stderr, "modem write error, errno=%d\n", errno); break; } else { op += stat; wmsec (IOWAIT); goto flush; } } putlog (obuf, op-obuf); if (fast) --fast; op = obuf; } } while (1); quit: /* Shutdown. */ while (op > obuf) { stat = write (terminal, obuf, op - obuf); if (stat < 0) { fprintf (stderr, "modem write error, errno=%d\n", errno); break; } else { op += stat; wmsec (IOWAIT); } } if (logfile) { putlog (obuf, op-obuf); fclose (logfile); } if (debug) fclose (db); ioctl (terminal, TCSETA, &o_tty); close (terminal); /* Output the exit string, if any, to the modem, and close. */ if (exitmsg[0]) write (modem, exitmsg, strlen(exitmsg)); ioctl (modem, TCSETA, &o_mty); close (modem); if (!exitstatus) printf ("\n"); abort: if (locked) unlink (lockfile); exit (exitstatus); } /* PUTTEXT -- Put a line of text to the output device, then pause to allow * the system on the receiving end to process the line of input. */ puttext (fd, lbuf, nchars) int fd; /* output file */ char *lbuf; /* line of text */ int nchars; /* nchars to be output */ { if (watch) { write (1, lbuf, nchars); write (1, "\r", 1); } write (fd, lbuf, nchars); wmsec (NLWAIT + nchars * 2); } /* PUTLOG -- Put a line of text to the log file. Convert CRLF into LF in * the process. */ putlog (lbuf, nchars) char *lbuf; /* line of text */ int nchars; /* nchars to be written */ { register char *ip; register int n, ch; static int cr; if (logfile) for (n=nchars, ip=lbuf; --n >= 0 && *ip; ip++) { ch = *ip; if (cr && ch != '\n') fputc ('\r', logfile); else if (cr = (ch == '\r')) continue; fputc (ch, logfile); } } /* WMSEC -- Suspend task execution (sleep) for the specified number * of milliseconds. */ wmsec (msec) int msec; { struct itimerval itv, oitv; register struct itimerval *itp = &itv; int (*old)(); static int napmsx(); int stat; if (msec <= 0) return; timerclear (&itp->it_interval); timerclear (&itp->it_value); if (setitimer (ITIMER_REAL, itp, &oitv) < 0) return; itp->it_value.tv_usec = (msec % 1000) * 1000; itp->it_value.tv_sec = (msec / 1000); if (timerisset (&oitv.it_value)) { if (timercmp(&oitv.it_value, &itp->it_value, >)) oitv.it_value.tv_sec -= itp->it_value.tv_sec; else { itp->it_value = oitv.it_value; oitv.it_value.tv_sec = 1; oitv.it_value.tv_usec = 0; } } old = signal (SIGALRM, napmsx); ringring = 0; setitimer (ITIMER_REAL, itp, (struct itimerval *)0); while (!ringring) sigpause (0); setitimer (ITIMER_REAL, &oitv, (struct itimerval *)0); signal (SIGALRM, old); } static int napmsx() { ringring = 1; } /* DO_FCNTL -- Conditionally execute fcntl. */ do_fcntl (fd, flag, arg) int fd; int flag; int arg; { if (nonblock) fcntl (fd, flag, arg); }