#include #include #include #include "telnet.h" int ctl = -1; /* control fd (for break's) */ int consctl = -1; /* consctl fd */ int ttypid; /* pid's if the 2 processes (used to kill them) */ int netpid; int interrupted; int localecho; int notkbd; typedef struct Comm Comm; struct Comm { int returns; int stopped; }; Comm *comm; int dodial(char*); void fromkbd(int); void fromnet(int); int menu(Biobuf*, int); void notifyf(void*, char*); void rawoff(void); void rawon(void); void telnet(int); char* system(int, char*); int echochange(Biobuf*, int); int termsub(Biobuf*, uchar*, int); int xlocsub(Biobuf*, uchar*, int); void* share(ulong); static int islikeatty(int); void usage(void) { fatal("usage: telnet [-Cdnr] net!host[!service]", 0, 0); } void main(int argc, char *argv[]) { int returns; returns = 1; ARGBEGIN{ case 'C': opt[Echo].noway = 1; break; case 'd': debug = 1; break; case 'n': notkbd = 1; break; case 'r': returns = 0; break; default: usage(); }ARGEND if(argc != 1) usage(); /* options we need routines for */ opt[Echo].change = echochange; opt[Term].sub = termsub; opt[Xloc].sub = xlocsub; comm = share(sizeof(comm)); comm->returns = returns; telnet(dodial(argv[0])); } /* * dial and return a data connection */ int dodial(char *dest) { char *name; int data; char devdir[NETPATHLEN]; name = netmkaddr(dest, "tcp", "telnet"); data = dial(name, 0, devdir, 0); if(data < 0) fatal("%s: %r", name, 0); fprint(2, "connected to %s on %s\n", name, devdir); return data; } /* * two processes pass bytes back and forth between the * terminal and the network. */ void telnet(int net) { int pid; rawoff(); ttypid = getpid(); switch(pid = rfork(RFPROC|RFFDG|RFMEM)){ case -1: perror("con"); exits("fork"); case 0: rawoff(); notify(notifyf); fromnet(net); sendnote(ttypid, "die"); exits(0); default: netpid = pid; notify(notifyf); fromkbd(net); if(notkbd) for(;;)sleep(0); sendnote(netpid, "die"); exits(0); } } /* * Read the keyboard and write it to the network. '^\' gets us into * the menu. */ void fromkbd(int net) { Biobuf ib, ob; int c, likeatty; int eofs; Binit(&ib, 0, OREAD); Binit(&ob, net, OWRITE); likeatty = islikeatty(0); eofs = 0; for(;;){ c = Bgetc(&ib); /* * with raw off, all ^D's get turned into Eof's. * change them back. * 10 in a row implies that the terminal is really gone so * just hang up. */ if(c < 0){ if(notkbd) return; if(eofs++ > 10) return; c = 004; } else eofs = 0; /* * if not in binary mode, look for the ^\ escape to menu. * also turn \n into \r\n */ if(likeatty || !opt[Binary].local){ if(c == 0034){ /* CTRL \ */ if(Bflush(&ob) < 0) return; if(menu(&ib, net) < 0) return; continue; } } if(!opt[Binary].local){ if(c == '\n'){ /* * This is a very strange use of the SGA option. * I did this because some systems that don't * announce a willingness to supress-go-ahead * need the \r\n sequence to recognize input. * If someone can explain this to me, please * send me mail. - presotto */ if(opt[SGA].remote){ c = '\r'; } else { if(Bputc(&ob, '\r') < 0) return; } } } if(Bputc(&ob, c) < 0) return; if(Bbuffered(&ib) == 0) if(Bflush(&ob) < 0) return; } } /* * Read from the network and write to the screen. If 'stopped' is set * spin and don't read. Filter out spurious carriage returns. */ void fromnet(int net) { int c; int crnls = 0, freenl = 0, eofs; Biobuf ib, ob; Binit(&ib, net, OREAD); Binit(&ob, 1, OWRITE); eofs = 0; for(;;){ if(Bbuffered(&ib) == 0) Bflush(&ob); if(interrupted){ interrupted = 0; send2(net, Iac, Interrupt); } c = Bgetc(&ib); if(c < 0){ if(eofs++ >= 2) return; continue; } eofs = 0; switch(c){ case '\n': /* skip nl after string of cr's */ if(!opt[Binary].local && !comm->returns){ ++crnls; if(freenl == 0) break; freenl = 0; continue; } break; case '\r': /* first cr becomes nl, remainder dropped */ if(!opt[Binary].local && !comm->returns){ if(crnls++ == 0){ freenl = 1; c = '\n'; break; } continue; } break; case 0: /* remove nulls from crnl string */ if(crnls) continue; break; case Iac: crnls = 0; freenl = 0; c = Bgetc(&ib); if(c == Iac) break; if(Bflush(&ob) < 0) return; if(control(&ib, c) < 0) return; continue; default: crnls = 0; freenl = 0; break; } if(Bputc(&ob, c) < 0) return; } } /* * turn keyboard raw mode on */ void rawon(void) { if(debug) fprint(2, "rawon\n"); if(consctl < 0) consctl = open("/dev/consctl", OWRITE); if(consctl < 0){ fprint(2, "can't open consctl: %r\n"); return; } write(consctl, "rawon", 5); } /* * turn keyboard raw mode off */ void rawoff(void) { if(debug) fprint(2, "rawoff\n"); if(consctl < 0) consctl = open("/dev/consctl", OWRITE); if(consctl < 0){ fprint(2, "can't open consctl: %r\n"); return; } write(consctl, "rawoff", 6); } /* * control menu */ #define STDHELP "\t(b)reak, (i)nterrupt, (q)uit, (r)eturns, (!cmd), (.)continue\n" int menu(Biobuf *bp, int net) { char *cp; int done; comm->stopped = 1; rawoff(); fprint(2, ">>> "); for(done = 0; !done; ){ cp = Brdline(bp, '\n'); if(cp == 0){ comm->stopped = 0; return -1; } cp[Blinelen(bp)-1] = 0; switch(*cp){ case '!': system(Bfildes(bp), cp+1); done = 1; break; case '.': done = 1; break; case 'q': comm->stopped = 0; return -1; case 'o': switch(*(cp+1)){ case 'd': send3(net, Iac, Do, atoi(cp+2)); break; case 'w': send3(net, Iac, Will, atoi(cp+2)); break; } break; case 'r': comm->returns = !comm->returns; done = 1; break; case 'i': send2(net, Iac, Interrupt); break; case 'b': send2(net, Iac, Break); break; default: fprint(2, STDHELP); break; } if(!done) fprint(2, ">>> "); } rawon(); comm->stopped = 0; return 0; } /* * ignore interrupts */ void notifyf(void *a, char *msg) { USED(a); if(strcmp(msg, "interrupt") == 0){ interrupted = 1; noted(NCONT); } if(strcmp(msg, "hangup") == 0) noted(NCONT); noted(NDFLT); } /* * run a command with the network connection as standard IO */ char * system(int fd, char *cmd) { int pid; int p; static Waitmsg msg; if((pid = fork()) == -1){ perror("con"); return "fork failed"; } else if(pid == 0){ dup(fd, 0); close(ctl); close(fd); if(*cmd) execl("/bin/rc", "rc", "-c", cmd, nil); else execl("/bin/rc", "rc", nil); perror("con"); exits("exec"); } for(p = waitpid(); p >= 0; p = waitpid()){ if(p == pid) return msg.msg; } return "lost child"; } /* * suppress local echo if the remote side is doing it */ int echochange(Biobuf *bp, int cmd) { USED(bp); switch(cmd){ case Will: rawon(); break; case Wont: rawoff(); break; } return 0; } /* * send terminal type to the other side */ int termsub(Biobuf *bp, uchar *sub, int n) { char buf[64]; char *term; char *p = buf; if(n < 1) return 0; if(sub[0] == 1){ *p++ = Iac; *p++ = Sb; *p++ = opt[Term].code; *p++ = 0; term = getenv("TERM"); if(term == 0 || *term == 0) term = "p9win"; strncpy(p, term, sizeof(buf) - (p - buf) - 2); buf[sizeof(buf)-2] = 0; p += strlen(p); *p++ = Iac; *p++ = Se; return iwrite(Bfildes(bp), buf, p-buf); } return 0; } /* * send an x display location to the other side */ int xlocsub(Biobuf *bp, uchar *sub, int n) { char buf[64]; char *term; char *p = buf; if(n < 1) return 0; if(sub[0] == 1){ *p++ = Iac; *p++ = Sb; *p++ = opt[Xloc].code; *p++ = 0; term = getenv("XDISP"); if(term == 0 || *term == 0) term = "unknown"; strncpy(p, term, p - buf - 2); p += strlen(term); *p++ = Iac; *p++ = Se; return iwrite(Bfildes(bp), buf, p-buf); } return 0; } static int islikeatty(int fd) { char buf[64]; if(fd2path(fd, buf, sizeof buf) != 0) return 0; /* might be /mnt/term/dev/cons */ return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0; } /* * create a shared segment. Make is start 2 meg higher than the current * end of process memory. */ void* share(ulong len) { uchar *vastart; vastart = sbrk(0); if(vastart == (void*)-1) return 0; vastart += 2*1024*1024; if(segattach(0, "shared", vastart, len) == (void*)-1) return 0; return vastart; }