ref: 5aa224c153db83a9fe82848860a062c7c3566cc5
dir: /sys/src/cmd/ssh/ssh.c/
#include "ssh.h" int cooked = 0; /* user wants cooked mode */ int raw = 0; /* console is in raw mode */ int crstrip; int interactive = -1; int usemenu = 1; int isatty(int); int rawhack; int forwardagent = 0; char *buildcmd(int, char**); void fromnet(Conn*); void fromstdin(Conn*); void winchanges(Conn*); static void sendwritemsg(Conn *c, char *buf, int n); Cipher *allcipher[] = { &cipherrc4, &cipherblowfish, &cipher3des, &cipherdes, &ciphernone, &ciphertwiddle, }; Auth *allauth[] = { &authpassword, &authrsa, &authtis, }; char *cipherlist = "blowfish rc4 3des"; char *authlist = "rsa password tis"; Cipher* findcipher(char *name, Cipher **list, int nlist) { int i; for(i=0; i<nlist; i++) if(strcmp(name, list[i]->name) == 0) return list[i]; error("unknown cipher %s", name); return nil; } Auth* findauth(char *name, Auth **list, int nlist) { int i; for(i=0; i<nlist; i++) if(strcmp(name, list[i]->name) == 0) return list[i]; error("unknown auth %s", name); return nil; } void usage(void) { fprint(2, "usage: ssh [-CiImPpRr] [-A authlist] [-c cipherlist] [user@]hostname [cmd [args]]\n"); exits("usage"); } void main(int argc, char **argv) { int i, dowinchange, fd, usepty; char *host, *cmd, *user, *p; char *f[16]; Conn c; Msg *m; fmtinstall('B', mpfmt); fmtinstall('H', encodefmt); atexit(atexitkiller); atexitkill(getpid()); dowinchange = 0; if(getenv("LINES")) dowinchange = 1; usepty = -1; user = nil; ARGBEGIN{ case 'B': /* undocumented, debugging */ doabort = 1; break; case 'D': /* undocumented, debugging */ debuglevel = strtol(EARGF(usage()), nil, 0); break; case 'l': /* deprecated */ case 'u': user = EARGF(usage()); break; case 'a': /* used by Unix scp implementations; we must ignore them. */ case 'x': break; case 'A': authlist = EARGF(usage()); break; case 'C': cooked = 1; break; case 'c': cipherlist = EARGF(usage()); break; case 'f': forwardagent = 1; break; case 'I': interactive = 0; break; case 'i': interactive = 1; break; case 'm': usemenu = 0; break; case 'P': usepty = 0; break; case 'p': usepty = 1; break; case 'R': rawhack = 1; break; case 'r': crstrip = 1; break; default: usage(); }ARGEND if(argc < 1) usage(); host = argv[0]; cmd = nil; if(argc > 1) cmd = buildcmd(argc-1, argv+1); if((p = strchr(host, '@')) != nil){ *p++ = '\0'; user = host; host = p; } if(user == nil) user = getenv("user"); if(user == nil) sysfatal("cannot find user name"); privatefactotum(); if(interactive==-1) interactive = isatty(0); if((fd = dial(netmkaddr(host, "tcp", "ssh"), nil, nil, nil)) < 0) sysfatal("dialing %s: %r", host); memset(&c, 0, sizeof c); c.interactive = interactive; c.fd[0] = c.fd[1] = fd; c.user = user; c.host = host; setaliases(&c, host); c.nokcipher = getfields(cipherlist, f, nelem(f), 1, ", "); c.okcipher = emalloc(sizeof(Cipher*)*c.nokcipher); for(i=0; i<c.nokcipher; i++) c.okcipher[i] = findcipher(f[i], allcipher, nelem(allcipher)); c.nokauth = getfields(authlist, f, nelem(f), 1, ", "); c.okauth = emalloc(sizeof(Auth*)*c.nokauth); for(i=0; i<c.nokauth; i++) c.okauth[i] = findauth(f[i], allauth, nelem(allauth)); sshclienthandshake(&c); if(forwardagent){ if(startagent(&c) < 0) forwardagent = 0; } if(usepty == -1) usepty = cmd==nil; if(usepty) requestpty(&c); if(cmd){ m = allocmsg(&c, SSH_CMSG_EXEC_CMD, 4+strlen(cmd)); putstring(m, cmd); }else m = allocmsg(&c, SSH_CMSG_EXEC_SHELL, 0); sendmsg(m); fromstdin(&c); rfork(RFNOTEG); /* only fromstdin gets notes */ if(dowinchange) winchanges(&c); fromnet(&c); exits(0); } int isatty(int fd) { char buf[64]; buf[0] = '\0'; fd2path(fd, buf, sizeof buf); if(strlen(buf)>=9 && strcmp(buf+strlen(buf)-9, "/dev/cons")==0) return 1; return 0; } char* buildcmd(int argc, char **argv) { int i, len; char *s, *t; len = argc-1; for(i=0; i<argc; i++) len += strlen(argv[i]); s = emalloc(len+1); t = s; for(i=0; i<argc; i++){ if(i) *t++ = ' '; strcpy(t, argv[i]); t += strlen(t); } return s; } void fromnet(Conn *c) { int fd, len; char *s, *es, *r, *w; ulong ex; char buf[64]; Msg *m; for(;;){ m = recvmsg(c, -1); if(m == nil) break; switch(m->type){ default: badmsg(m, 0); case SSH_SMSG_EXITSTATUS: ex = getlong(m); if(ex==0) exits(0); sprint(buf, "%lud", ex); exits(buf); case SSH_MSG_DISCONNECT: s = getstring(m); error("disconnect: %s", s); /* * If we ever add reverse port forwarding, we'll have to * revisit this. It assumes that the agent connections are * the only ones. */ case SSH_SMSG_AGENT_OPEN: if(!forwardagent) error("server tried to use agent forwarding"); handleagentopen(m); break; case SSH_MSG_CHANNEL_INPUT_EOF: if(!forwardagent) error("server tried to use agent forwarding"); handleagentieof(m); break; case SSH_MSG_CHANNEL_OUTPUT_CLOSED: if(!forwardagent) error("server tried to use agent forwarding"); handleagentoclose(m); break; case SSH_MSG_CHANNEL_DATA: if(!forwardagent) error("server tried to use agent forwarding"); handleagentmsg(m); break; case SSH_SMSG_STDOUT_DATA: fd = 1; goto Dataout; case SSH_SMSG_STDERR_DATA: fd = 2; goto Dataout; Dataout: len = getlong(m); s = (char*)getbytes(m, len); if(crstrip){ es = s+len; for(r=w=s; r<es; r++) if(*r != '\r') *w++ = *r; len = w-s; } write(fd, s, len); break; } free(m); } } /* * Lifted from telnet.c, con.c */ static int consctl = -1; static int outfd1=1, outfd2=2; /* changed during system */ static void system(Conn*, char*); /* * turn keyboard raw mode on */ static void rawon(void) { if(raw) return; if(cooked) return; if(consctl < 0) consctl = open("/dev/consctl", OWRITE); if(consctl < 0) return; if(write(consctl, "rawon", 5) != 5) return; raw = 1; } /* * turn keyboard raw mode off */ static void rawoff(void) { if(raw == 0) return; if(consctl < 0) return; if(write(consctl, "rawoff", 6) != 6) return; close(consctl); consctl = -1; raw = 0; } /* * control menu */ #define STDHELP "\t(q)uit, (i)nterrupt, toggle printing (r)eturns, (.)continue, (!cmd)\n" static int menu(Conn *c) { char buf[1024]; long n; int done; int wasraw; wasraw = raw; if(wasraw) rawoff(); buf[0] = '?'; 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(c, buf+1); print("!\n"); done = 1; break; case 'i': buf[0] = 0x1c; sendwritemsg(c, buf, 1); done = 1; break; case '.': case 'q': done = 1; break; case 'r': crstrip = 1-crstrip; done = 1; break; default: fprint(2, STDHELP); break; } if(!done) fprint(2, ">>> "); } if(wasraw) rawon(); else rawoff(); return buf[0]; } static void sendwritemsg(Conn *c, char *buf, int n) { Msg *m; if(n==0) m = allocmsg(c, SSH_CMSG_EOF, 0); else{ m = allocmsg(c, SSH_CMSG_STDIN_DATA, 4+n); putlong(m, n); putbytes(m, buf, n); } sendmsg(m); } /* * run a command with the network connection as standard IO */ static void system(Conn *c, char *cmd) { int pid; int p; int pfd[2]; int n; int wasconsctl; char buf[4096]; if(pipe(pfd) < 0){ perror("pipe"); return; } outfd1 = outfd2 = pfd[1]; wasconsctl = consctl; close(consctl); consctl = -1; switch(pid = fork()){ case -1: perror("con"); return; case 0: close(pfd[1]); dup(pfd[0], 0); dup(pfd[0], 1); close(c->fd[0]); /* same as c->fd[1] */ 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) sendwritemsg(c, buf, n); p = waitpid(); outfd1 = 1; outfd2 = 2; close(pfd[1]); if(p < 0 || p != pid) return; break; } if(wasconsctl >= 0){ consctl = open("/dev/consctl", OWRITE); if(consctl < 0) error("cannot open consctl"); } } static void cookedcatchint(void*, char *msg) { if(strstr(msg, "interrupt")) noted(NCONT); else if(strstr(msg, "kill")) noted(NDFLT); else noted(NCONT); } static int wasintr(void) { char err[64]; rerrstr(err, sizeof err); return strstr(err, "interrupt") != 0; } void fromstdin(Conn *c) { int n; char buf[1024]; int pid; int eofs; switch(pid = rfork(RFMEM|RFPROC|RFNOWAIT)){ case -1: error("fork: %r"); case 0: break; default: atexitkill(pid); return; } atexit(atexitkiller); if(interactive) rawon(); notify(cookedcatchint); eofs = 0; for(;;){ n = read(0, buf, sizeof(buf)); if(n < 0){ if(wasintr()){ if(!raw){ buf[0] = 0x7f; n = 1; }else continue; }else break; } if(n == 0){ if(!c->interactive || ++eofs > 32) break; }else eofs = 0; if(interactive && usemenu && n && memchr(buf, 0x1c, n)) { if(menu(c)=='q'){ sendwritemsg(c, "", 0); exits("quit"); } continue; } if(!raw && n==0){ buf[0] = 0x4; n = 1; } sendwritemsg(c, buf, n); } sendwritemsg(c, "", 0); atexitdont(atexitkiller); exits(nil); } void winchanges(Conn *c) { int nrow, ncol, width, height; int pid; switch(pid = rfork(RFMEM|RFPROC|RFNOWAIT)){ case -1: error("fork: %r"); case 0: break; default: atexitkill(pid); return; } for(;;){ if(readgeom(&nrow, &ncol, &width, &height) < 0) break; sendwindowsize(c, nrow, ncol, width, height); } exits(nil); }