551 lines
8.6 KiB
C
551 lines
8.6 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <bio.h>
|
|
#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;
|
|
int returns;
|
|
int stopped;
|
|
|
|
static char *srv;
|
|
|
|
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);
|
|
|
|
static int islikeatty(int);
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fatal("usage: telnet [-Cdnr] [-s srv] net!host[!service]", 0, 0);
|
|
}
|
|
|
|
void
|
|
main(int argc, char *argv[])
|
|
{
|
|
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;
|
|
case 's':
|
|
srv = EARGF(usage());
|
|
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;
|
|
|
|
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);
|
|
if(srv != nil){
|
|
if(rfork(RFPROC | RFNOWAIT | RFNOTEG) != 0)
|
|
exits("");
|
|
}
|
|
else
|
|
fprint(2, "connected to %s on %s\n", name, devdir);
|
|
return data;
|
|
}
|
|
|
|
void
|
|
post(char *srv, int fd)
|
|
{
|
|
int f;
|
|
char buf[32];
|
|
|
|
f = create(srv, OWRITE, 0666);
|
|
if(f < 0)
|
|
sysfatal("create %s: %r", srv);
|
|
snprint(buf, sizeof buf, "%d", fd);
|
|
if(write(f, buf, strlen(buf)) != strlen(buf))
|
|
sysfatal("write %s: %r", srv);
|
|
close(f);
|
|
}
|
|
|
|
/*
|
|
* two processes pass bytes back and forth between the
|
|
* terminal and the network.
|
|
*/
|
|
void
|
|
telnet(int net)
|
|
{
|
|
int pid;
|
|
int p[2];
|
|
char *svc;
|
|
|
|
rawoff();
|
|
svc = nil;
|
|
if (srv) {
|
|
if(pipe(p) < 0)
|
|
sysfatal("pipe: %r");
|
|
if (srv[0] != '/')
|
|
svc = smprint("/srv/%s", srv);
|
|
else
|
|
svc = srv;
|
|
post(svc, p[0]);
|
|
close(p[0]);
|
|
dup(p[1], 0);
|
|
dup(p[1], 1);
|
|
/* pipe is now std in & out */
|
|
}
|
|
ttypid = getpid();
|
|
switch(pid = rfork(RFPROC|RFFDG|RFMEM)){
|
|
case -1:
|
|
perror("con");
|
|
exits("fork");
|
|
case 0:
|
|
rawoff();
|
|
notify(notifyf);
|
|
fromnet(net);
|
|
if (svc)
|
|
remove(svc);
|
|
sendnote(ttypid, "die");
|
|
exits(0);
|
|
default:
|
|
netpid = pid;
|
|
notify(notifyf);
|
|
fromkbd(net);
|
|
if (notkbd)
|
|
while(waitpid() != pid)
|
|
sleep(0);
|
|
if (svc)
|
|
remove(svc);
|
|
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 && !returns){
|
|
++crnls;
|
|
if(freenl == 0)
|
|
break;
|
|
freenl = 0;
|
|
continue;
|
|
}
|
|
break;
|
|
case '\r': /* first cr becomes nl, remainder dropped */
|
|
if(!opt[Binary].local && !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;
|
|
}
|
|
}
|
|
|
|
void
|
|
consctlcmd(char *s)
|
|
{
|
|
if(srv != nil)
|
|
return;
|
|
if(debug)
|
|
fprint(2, "consctl: %s\n", s);
|
|
if(consctl < 0)
|
|
consctl = open("/dev/consctl", OWRITE);
|
|
if(consctl < 0){
|
|
fprint(2, "can't open consctl: %r\n");
|
|
return;
|
|
}
|
|
write(consctl, s, strlen(s));
|
|
}
|
|
|
|
/*
|
|
* turn keyboard raw mode on
|
|
*/
|
|
void
|
|
rawon(void)
|
|
{
|
|
consctlcmd("rawon");
|
|
}
|
|
|
|
/*
|
|
* turn keyboard raw mode off
|
|
*/
|
|
void
|
|
rawoff(void)
|
|
{
|
|
consctlcmd("rawoff");
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
stopped = 1;
|
|
|
|
rawoff();
|
|
fprint(2, ">>> ");
|
|
for(done = 0; !done; ){
|
|
cp = Brdline(bp, '\n');
|
|
if(cp == 0){
|
|
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':
|
|
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':
|
|
returns = !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();
|
|
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;
|
|
}
|