684 lines
11 KiB
C
684 lines
11 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
|
|
int debug; /* true if debugging */
|
|
int ctl = -1; /* control fd (for break's) */
|
|
int raw; /* true if raw is on */
|
|
int consctl = -1; /* control fd for cons */
|
|
int ttypid; /* pid's if the 2 processes (used to kill them) */
|
|
int outfd = 1; /* local output file descriptor */
|
|
int cooked; /* non-zero forces cooked mode */
|
|
int returns; /* non-zero forces carriage returns not to be filtered out */
|
|
int crtonl; /* non-zero forces carriage returns to be converted to nls coming from net */
|
|
int strip; /* strip off parity bits */
|
|
char firsterr[2*ERRMAX];
|
|
char transerr[2*ERRMAX];
|
|
int limited;
|
|
char *remuser; /* for BSD rlogin authentication */
|
|
int verbose;
|
|
int baud;
|
|
int notkbd;
|
|
int nltocr; /* translate kbd nl to cr and vice versa */
|
|
|
|
static char *srv;
|
|
|
|
#define MAXMSG (2*8192)
|
|
|
|
int dodial(char*, char*, char*);
|
|
void fromkbd(int);
|
|
void fromnet(int);
|
|
long iread(int, void*, int);
|
|
long iwrite(int, void*, int);
|
|
int menu(int);
|
|
void notifyf(void*, char*);
|
|
void pass(int, int, int);
|
|
void rawoff(void);
|
|
void rawon(void);
|
|
void stdcon(int);
|
|
char* system(int, char*);
|
|
void dosystem(int, char*);
|
|
int wasintr(void);
|
|
void punt(char*);
|
|
char* syserr(void);
|
|
void seterr(char*);
|
|
|
|
/* protocols */
|
|
void device(char*, char*);
|
|
void rlogin(char*, char*);
|
|
void simple(char*, char*);
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
punt("usage: con [-CdnrRsTv] [-b baud] [-l [user]] [-c cmd] [-S svc] "
|
|
"net!host[!service]");
|
|
}
|
|
|
|
void
|
|
main(int argc, char *argv[])
|
|
{
|
|
char *dest;
|
|
char *cmd = 0;
|
|
|
|
returns = 1;
|
|
ARGBEGIN{
|
|
case 'b':
|
|
baud = atoi(EARGF(usage()));
|
|
break;
|
|
case 'C':
|
|
cooked = 1;
|
|
break;
|
|
case 'c':
|
|
cmd = EARGF(usage());
|
|
break;
|
|
case 'd':
|
|
debug = 1;
|
|
break;
|
|
case 'l':
|
|
limited = 1;
|
|
if(argv[1] != nil && argv[1][0] != '-')
|
|
remuser = EARGF(usage());
|
|
break;
|
|
case 'n':
|
|
notkbd = 1;
|
|
break;
|
|
case 'r':
|
|
returns = 0;
|
|
break;
|
|
case 's':
|
|
strip = 1;
|
|
break;
|
|
case 'S':
|
|
srv = EARGF(usage());
|
|
break;
|
|
case 'R':
|
|
nltocr = 1;
|
|
break;
|
|
case 'T':
|
|
crtonl = 1;
|
|
break;
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
}ARGEND
|
|
|
|
if(argc != 1){
|
|
if(remuser == 0)
|
|
usage();
|
|
dest = remuser;
|
|
remuser = 0;
|
|
} else
|
|
dest = argv[0];
|
|
if(*dest == '/' && strchr(dest, '!') == 0)
|
|
device(dest, cmd);
|
|
else if(limited){
|
|
simple(dest, cmd); /* doesn't return if dialout succeeds */
|
|
rlogin(dest, cmd); /* doesn't return if dialout succeeds */
|
|
} else {
|
|
rlogin(dest, cmd); /* doesn't return if dialout succeeds */
|
|
simple(dest, cmd); /* doesn't return if dialout succeeds */
|
|
}
|
|
punt(firsterr);
|
|
}
|
|
|
|
/*
|
|
* just dial and use as a byte stream with remote echo
|
|
*/
|
|
void
|
|
simple(char *dest, char *cmd)
|
|
{
|
|
int net;
|
|
|
|
net = dodial(dest, 0, 0);
|
|
if(net < 0)
|
|
return;
|
|
|
|
if(cmd)
|
|
dosystem(net, cmd);
|
|
|
|
if(!cooked)
|
|
rawon();
|
|
stdcon(net);
|
|
exits(0);
|
|
}
|
|
|
|
/*
|
|
* dial, do UCB authentication, use as a byte stream with local echo
|
|
*
|
|
* return if dial failed
|
|
*/
|
|
void
|
|
rlogin(char *dest, char *cmd)
|
|
{
|
|
int net;
|
|
char buf[128];
|
|
char *p;
|
|
char *localuser;
|
|
|
|
/* only useful on TCP */
|
|
if(strchr(dest, '!')
|
|
&& (strncmp(dest, "tcp!", 4)!=0 && strncmp(dest, "net!", 4)!=0))
|
|
return;
|
|
|
|
net = dodial(dest, "tcp", "login");
|
|
if(net < 0)
|
|
return;
|
|
|
|
/*
|
|
* do UCB rlogin authentication
|
|
*/
|
|
localuser = getuser();
|
|
if(remuser == 0){
|
|
if(limited)
|
|
remuser = ":";
|
|
else
|
|
remuser = localuser;
|
|
}
|
|
p = getenv("TERM");
|
|
if(p == 0)
|
|
p = "p9";
|
|
if(write(net, "", 1)<0
|
|
|| write(net, localuser, strlen(localuser)+1)<0
|
|
|| write(net, remuser, strlen(remuser)+1)<0
|
|
|| write(net, p, strlen(p)+1)<0){
|
|
close(net);
|
|
punt("BSD authentication failed");
|
|
}
|
|
if(read(net, buf, 1) != 1)
|
|
punt("BSD authentication failed1");
|
|
if(buf[0] != 0){
|
|
fprint(2, "con: remote error: ");
|
|
while(read(net, buf, 1) == 1){
|
|
write(2, buf, 1);
|
|
if(buf[0] == '\n')
|
|
break;
|
|
}
|
|
exits("read");
|
|
}
|
|
|
|
if(cmd)
|
|
dosystem(net, cmd);
|
|
|
|
if(!cooked)
|
|
rawon();
|
|
nltocr = 1;
|
|
stdcon(net);
|
|
exits(0);
|
|
}
|
|
|
|
/*
|
|
* just open a device and use it as a connection
|
|
*/
|
|
void
|
|
device(char *dest, char *cmd)
|
|
{
|
|
int net;
|
|
char cname[128];
|
|
|
|
net = open(dest, ORDWR);
|
|
if(net < 0) {
|
|
fprint(2, "con: cannot open %s: %r\n", dest);
|
|
exits("open");
|
|
}
|
|
snprint(cname, sizeof cname, "%sctl", dest);
|
|
ctl = open(cname, ORDWR);
|
|
if (baud > 0) {
|
|
if(ctl >= 0){
|
|
/* set speed and use fifos if available */
|
|
fprint(ctl, "b%d i1", baud);
|
|
}
|
|
else
|
|
fprint(2, "con: cannot open %s: %r\n", cname);
|
|
}
|
|
|
|
if(cmd)
|
|
dosystem(net, cmd);
|
|
|
|
if(!cooked)
|
|
rawon();
|
|
stdcon(net);
|
|
exits(0);
|
|
}
|
|
|
|
/*
|
|
* ignore interrupts
|
|
*/
|
|
void
|
|
notifyf(void *a, char *msg)
|
|
{
|
|
USED(a);
|
|
|
|
if(strstr(msg, "yankee"))
|
|
noted(NDFLT);
|
|
if(strstr(msg, "closed pipe")
|
|
|| strcmp(msg, "interrupt") == 0
|
|
|| strcmp(msg, "hangup") == 0)
|
|
noted(NCONT);
|
|
noted(NDFLT);
|
|
}
|
|
|
|
/*
|
|
* turn keyboard raw mode on
|
|
*/
|
|
void
|
|
rawon(void)
|
|
{
|
|
if(debug)
|
|
fprint(2, "rawon\n");
|
|
if(raw)
|
|
return;
|
|
if(consctl < 0)
|
|
consctl = open("/dev/consctl", OWRITE);
|
|
if(consctl < 0){
|
|
// fprint(2, "can't open consctl\n");
|
|
return;
|
|
}
|
|
write(consctl, "rawon", 5);
|
|
raw = 1;
|
|
}
|
|
|
|
/*
|
|
* turn keyboard raw mode off
|
|
*/
|
|
void
|
|
rawoff(void)
|
|
{
|
|
if(debug)
|
|
fprint(2, "rawoff\n");
|
|
if(raw == 0)
|
|
return;
|
|
if(consctl < 0)
|
|
consctl = open("/dev/consctl", OWRITE);
|
|
if(consctl < 0){
|
|
// fprint(2, "can't open consctl\n");
|
|
return;
|
|
}
|
|
write(consctl, "rawoff", 6);
|
|
raw = 0;
|
|
}
|
|
|
|
/*
|
|
* control menu
|
|
*/
|
|
#define STDHELP "\t(b)reak, (q)uit, (i)nterrupt, toggle printing (r)eturns, (.)continue, (!cmd)\n"
|
|
|
|
int
|
|
menu(int net)
|
|
{
|
|
char buf[MAXMSG];
|
|
long n;
|
|
int done;
|
|
int wasraw = raw;
|
|
|
|
if(wasraw)
|
|
rawoff();
|
|
|
|
fprint(2, ">>> ");
|
|
for(done = 0; !done; ){
|
|
n = read(0, buf, sizeof(buf)-1);
|
|
if(n <= 0)
|
|
return -1;
|
|
buf[n] = 0;
|
|
switch(buf[0]){
|
|
case '!':
|
|
print(buf);
|
|
system(net, buf+1);
|
|
print("!\n");
|
|
done = 1;
|
|
break;
|
|
case '.':
|
|
done = 1;
|
|
break;
|
|
case 'q':
|
|
return -1;
|
|
case 'i':
|
|
buf[0] = 0x1c;
|
|
write(net, buf, 1);
|
|
done = 1;
|
|
break;
|
|
case 'b':
|
|
if(ctl >= 0)
|
|
write(ctl, "k", 1);
|
|
done = 1;
|
|
break;
|
|
case 'r':
|
|
returns = 1-returns;
|
|
done = 1;
|
|
break;
|
|
default:
|
|
fprint(2, STDHELP);
|
|
break;
|
|
}
|
|
if(!done)
|
|
fprint(2, ">>> ");
|
|
}
|
|
|
|
if(wasraw)
|
|
rawon();
|
|
else
|
|
rawoff();
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
post(char *srv, int fd)
|
|
{
|
|
int f;
|
|
char buf[32];
|
|
|
|
f = create(srv, OWRITE /* |ORCLOSE */ , 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);
|
|
}
|
|
|
|
/*
|
|
* the real work. two processes pass bytes back and forth between the
|
|
* terminal and the network.
|
|
*/
|
|
void
|
|
stdcon(int net)
|
|
{
|
|
int netpid;
|
|
int p[2];
|
|
char *svc;
|
|
|
|
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(netpid = rfork(RFMEM|RFPROC)){
|
|
case -1:
|
|
perror("con");
|
|
exits("fork");
|
|
case 0:
|
|
notify(notifyf);
|
|
fromnet(net);
|
|
if (svc)
|
|
remove(svc);
|
|
postnote(PNPROC, ttypid, "die yankee dog");
|
|
exits(0);
|
|
default:
|
|
notify(notifyf);
|
|
fromkbd(net);
|
|
if (svc)
|
|
remove(svc);
|
|
if(notkbd)
|
|
for(;;)
|
|
sleep(0);
|
|
postnote(PNPROC, netpid, "die yankee dog");
|
|
exits(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read the keyboard and write it to the network. '^\' gets us into
|
|
* the menu.
|
|
*/
|
|
void
|
|
fromkbd(int net)
|
|
{
|
|
long n;
|
|
char buf[MAXMSG];
|
|
char *p, *ep;
|
|
int eofs;
|
|
|
|
eofs = 0;
|
|
for(;;){
|
|
n = read(0, buf, sizeof(buf));
|
|
if(n < 0){
|
|
if(wasintr()){
|
|
if(!raw){
|
|
buf[0] = 0x7f;
|
|
n = 1;
|
|
} else
|
|
continue;
|
|
} else
|
|
return;
|
|
}
|
|
if(n == 0){
|
|
if(++eofs > 32)
|
|
return;
|
|
} else
|
|
eofs = 0;
|
|
if(n && memchr(buf, 0x1c, n)){
|
|
if(menu(net) < 0)
|
|
return;
|
|
}else{
|
|
if(!raw && n==0){
|
|
buf[0] = 0x4;
|
|
n = 1;
|
|
}
|
|
if(nltocr){
|
|
ep = buf+n;
|
|
for(p = buf; p < ep; p++)
|
|
switch(*p){
|
|
case '\r':
|
|
*p = '\n';
|
|
break;
|
|
case '\n':
|
|
*p = '\r';
|
|
break;
|
|
}
|
|
}
|
|
if(iwrite(net, buf, n) != n)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read from the network and write to the screen.
|
|
* Filter out spurious carriage returns.
|
|
*/
|
|
void
|
|
fromnet(int net)
|
|
{
|
|
long n;
|
|
char buf[MAXMSG];
|
|
char *cp, *ep;
|
|
|
|
for(;;){
|
|
n = iread(net, buf, sizeof(buf));
|
|
if(n < 0)
|
|
return;
|
|
if(n == 0)
|
|
continue;
|
|
|
|
if (strip)
|
|
for (cp=buf; cp<buf+n; cp++)
|
|
*cp &= 0177;
|
|
|
|
if(crtonl) {
|
|
/* convert cr's to nl's */
|
|
for (cp = buf; cp < buf + n; cp++)
|
|
if (*cp == '\r')
|
|
*cp = '\n';
|
|
}
|
|
else if(!returns){
|
|
/* convert cr's to null's */
|
|
cp = buf;
|
|
ep = buf + n;
|
|
while(cp < ep && (cp = memchr(cp, '\r', ep-cp))){
|
|
memmove(cp, cp+1, ep-cp-1);
|
|
ep--;
|
|
n--;
|
|
}
|
|
}
|
|
|
|
if(n > 0 && iwrite(outfd, buf, n) != n){
|
|
if(outfd == 1)
|
|
return;
|
|
outfd = 1;
|
|
if(iwrite(1, buf, n) != n)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* dial and return a data connection
|
|
*/
|
|
int
|
|
dodial(char *dest, char *net, char *service)
|
|
{
|
|
char name[128];
|
|
char devdir[128];
|
|
int data;
|
|
|
|
devdir[0] = 0;
|
|
strcpy(name, netmkaddr(dest, net, service));
|
|
data = dial(name, 0, devdir, &ctl);
|
|
if(data < 0){
|
|
seterr(name);
|
|
return -1;
|
|
}
|
|
fprint(2, "connected to %s on %s\n", name, devdir);
|
|
return data;
|
|
}
|
|
|
|
void
|
|
dosystem(int fd, char *cmd)
|
|
{
|
|
char *p;
|
|
|
|
p = system(fd, cmd);
|
|
if(p){
|
|
print("con: %s terminated with %s\n", cmd, p);
|
|
exits(p);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* run a command with the network connection as standard IO
|
|
*/
|
|
char *
|
|
system(int fd, char *cmd)
|
|
{
|
|
int pid;
|
|
int p;
|
|
static Waitmsg msg;
|
|
int pfd[2];
|
|
int n;
|
|
char buf[4096];
|
|
|
|
if(pipe(pfd) < 0){
|
|
perror("pipe");
|
|
return "pipe failed";
|
|
}
|
|
outfd = pfd[1];
|
|
|
|
close(consctl);
|
|
consctl = -1;
|
|
switch(pid = fork()){
|
|
case -1:
|
|
perror("con");
|
|
return "fork failed";
|
|
case 0:
|
|
close(pfd[1]);
|
|
dup(pfd[0], 0);
|
|
dup(fd, 1);
|
|
close(ctl);
|
|
close(fd);
|
|
close(pfd[0]);
|
|
if(*cmd)
|
|
execl("/bin/rc", "rc", "-c", cmd, nil);
|
|
else
|
|
execl("/bin/rc", "rc", nil);
|
|
perror("con");
|
|
exits("exec");
|
|
break;
|
|
default:
|
|
close(pfd[0]);
|
|
while((n = read(pfd[1], buf, sizeof(buf))) > 0)
|
|
if(write(fd, buf, n) != n)
|
|
break;
|
|
p = waitpid();
|
|
outfd = 1;
|
|
close(pfd[1]);
|
|
if(p < 0 || p != pid)
|
|
return "lost child";
|
|
break;
|
|
}
|
|
return msg.msg;
|
|
}
|
|
|
|
int
|
|
wasintr(void)
|
|
{
|
|
return strcmp(syserr(), "interrupted") == 0;
|
|
}
|
|
|
|
void
|
|
punt(char *msg)
|
|
{
|
|
if(*msg == 0)
|
|
msg = transerr;
|
|
fprint(2, "con: %s\n", msg);
|
|
exits(msg);
|
|
}
|
|
|
|
char*
|
|
syserr(void)
|
|
{
|
|
static char err[ERRMAX];
|
|
errstr(err, sizeof err);
|
|
return err;
|
|
}
|
|
|
|
void
|
|
seterr(char *addr)
|
|
{
|
|
char *se = syserr();
|
|
|
|
if(verbose)
|
|
fprint(2, "'%s' calling %s\n", se, addr);
|
|
if(firsterr[0] && (strstr(se, "translate") ||
|
|
strstr(se, "file does not exist") ||
|
|
strstr(se, "unknown address") ||
|
|
strstr(se, "directory entry not found")))
|
|
return;
|
|
strcpy(firsterr, se);
|
|
}
|
|
|
|
|
|
long
|
|
iread(int f, void *a, int n)
|
|
{
|
|
long m;
|
|
|
|
for(;;){
|
|
m = read(f, a, n);
|
|
if(m >= 0 || !wasintr())
|
|
break;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
long
|
|
iwrite(int f, void *a, int n)
|
|
{
|
|
long m;
|
|
|
|
m = write(f, a, n);
|
|
if(m < 0 && wasintr())
|
|
return n;
|
|
return m;
|
|
}
|