#include "ssh.h" static void recv_ssh_smsg_public_key(Conn *c) { Msg *m; m = recvmsg(c, SSH_SMSG_PUBLIC_KEY); memmove(c->cookie, getbytes(m, COOKIELEN), COOKIELEN); c->serverkey = getRSApub(m); c->hostkey = getRSApub(m); c->flags = getlong(m); c->ciphermask = getlong(m); c->authmask = getlong(m); free(m); } static void send_ssh_cmsg_session_key(Conn *c) { int i, n, buflen, serverkeylen, hostkeylen; mpint *b; uchar *buf; Msg *m; RSApub *ksmall, *kbig; m = allocmsg(c, SSH_CMSG_SESSION_KEY, 2048); putbyte(m, c->cipher->id); putbytes(m, c->cookie, COOKIELEN); serverkeylen = mpsignif(c->serverkey->n); hostkeylen = mpsignif(c->hostkey->n); ksmall = kbig = nil; if(serverkeylen+128 <= hostkeylen){ ksmall = c->serverkey; kbig = c->hostkey; }else if(hostkeylen+128 <= serverkeylen){ ksmall = c->hostkey; kbig = c->serverkey; }else error("server session and host keys do not differ by at least 128 bits"); buflen = (mpsignif(kbig->n)+7)/8; buf = emalloc(buflen); debug(DBG_CRYPTO, "session key is %.*H\n", SESSKEYLEN, c->sesskey); memmove(buf, c->sesskey, SESSKEYLEN); for(i = 0; i < SESSIDLEN; i++) buf[i] ^= c->sessid[i]; debug(DBG_CRYPTO, "munged session key is %.*H\n", SESSKEYLEN, buf); b = rsaencryptbuf(ksmall, buf, SESSKEYLEN); n = (mpsignif(ksmall->n)+7) / 8; mptoberjust(b, buf, n); mpfree(b); debug(DBG_CRYPTO, "encrypted with ksmall is %.*H\n", n, buf); b = rsaencryptbuf(kbig, buf, n); putmpint(m, b); debug(DBG_CRYPTO, "encrypted with kbig is %B\n", b); mpfree(b); memset(buf, 0, buflen); free(buf); putlong(m, c->flags); sendmsg(m); } static int authuser(Conn *c) { int i; Msg *m; m = allocmsg(c, SSH_CMSG_USER, 4+strlen(c->user)); putstring(m, c->user); sendmsg(m); m = recvmsg(c, -1); switch(m->type){ case SSH_SMSG_SUCCESS: free(m); return 0; case SSH_SMSG_FAILURE: free(m); break; default: badmsg(m, 0); } for(i=0; inokauth; i++){ debug(DBG_AUTH, "authmask %#lux, consider %s (%#x)\n", c->authmask, c->okauth[i]->name, 1<okauth[i]->id); if(c->authmask & (1<okauth[i]->id)) if((*c->okauth[i]->fn)(c) == 0) return 0; } debug(DBG_AUTH, "no auth methods worked; (authmask=%#lux)\n", c->authmask); return -1; } static char ask(Conn *c, char *answers, char *question) { int fd; char buf[256]; if(!c->interactive) return answers[0]; if((fd = open("/dev/cons", ORDWR)) < 0) return answers[0]; fprint(fd, "%s", question); if(read(fd, buf, 256) <= 0 || buf[0]=='\n'){ close(fd); return answers[0]; } close(fd); return buf[0]; } static void checkkey(Conn *c) { char *home, *keyfile; debug(DBG_CRYPTO, "checking key %B %B\n", c->hostkey->n, c->hostkey->ek); switch(findkey("/sys/lib/ssh/keyring", c->aliases, c->hostkey)){ default: abort(); case KeyOk: return; case KeyWrong: fprint(2, "server presented public key different than expected\n"); fprint(2, "(expected key in /sys/lib/ssh/keyring). will not continue.\n"); error("bad server key"); case NoKey: case NoKeyFile: break; } home = getenv("home"); if(home == nil){ fprint(2, "server %s not on keyring; will not continue.\n", c->host); error("bad server key"); } keyfile = smprint("%s/lib/keyring", home); if(keyfile == nil) error("out of memory"); switch(findkey(keyfile, c->aliases, c->hostkey)){ default: abort(); case KeyOk: return; case KeyWrong: fprint(2, "server %s presented public key different than expected\n", c->host); fprint(2, "(expected key in %s). will not continue.\n", keyfile); fprint(2, "this could be a man-in-the-middle attack.\n"); switch(ask(c, "eri", "replace key in keyfile (r), continue without replacing key (c), or exit (e) [e]")){ case 'e': error("bad key"); case 'r': if(replacekey(keyfile, c->aliases, c->hostkey) < 0) error("replacekey: %r"); break; case 'c': break; } return; case NoKey: case NoKeyFile: fprint(2, "server %s not on keyring.\n", c->host); switch(ask(c, "eac", "add key to keyfile (a), continue without adding key (c), or exit (e) [e]")){ case 'e': error("bad key"); case 'a': if(appendkey(keyfile, c->aliases, c->hostkey) < 0) error("appendkey: %r"); break; case 'c': break; } return; } } void sshclienthandshake(Conn *c) { char buf[128], *p; int i; Msg *m; /* receive id string */ if(readstrnl(c->fd[0], buf, sizeof buf) < 0) error("reading server version: %r"); /* id string is "SSH-m.n-comment". We need m=1, n>=5. */ if(strncmp(buf, "SSH-", 4) != 0 || strtol(buf+4, &p, 10) != 1 || *p != '.' || strtol(p+1, &p, 10) < 5 || *p != '-') error("protocol mismatch; got %s, need SSH-1.x for x>=5", buf); /* send id string */ fprint(c->fd[1], "SSH-1.5-Plan 9\n"); recv_ssh_smsg_public_key(c); checkkey(c); for(i=0; isesskey[i] = fastrand(); c->cipher = nil; for(i=0; inokcipher; i++) if((1<okcipher[i]->id) & c->ciphermask){ c->cipher = c->okcipher[i]; break; } if(c->cipher == nil) error("can't agree on ciphers: remote side supports %#lux", c->ciphermask); calcsessid(c); send_ssh_cmsg_session_key(c); c->cstate = (*c->cipher->init)(c, 0); /* turns on encryption */ m = recvmsg(c, SSH_SMSG_SUCCESS); free(m); if(authuser(c) < 0) error("client authentication failed"); } static int intgetenv(char *name, int def) { char *s; int n, val; val = def; if((s = getenv(name))!=nil){ if((n=atoi(s)) > 0) val = n; free(s); } return val; } /* * assumes that if you care, you're running under vt * and therefore these are set. */ int readgeom(int *nrow, int *ncol, int *width, int *height) { static int fd = -1; char buf[64]; if(fd < 0 && (fd = open("/dev/wctl", OREAD)) < 0) return -1; /* wait for event, but don't care what it says */ if(read(fd, buf, sizeof buf) < 0) return -1; *nrow = intgetenv("LINES", 24); *ncol = intgetenv("COLS", 80); *width = intgetenv("XPIXELS", 640); *height = intgetenv("YPIXELS", 480); return 0; } void sendwindowsize(Conn *c, int nrow, int ncol, int width, int height) { Msg *m; m = allocmsg(c, SSH_CMSG_WINDOW_SIZE, 4*4); putlong(m, nrow); putlong(m, ncol); putlong(m, width); putlong(m, height); sendmsg(m); } /* * In each option line, the first byte is the option number * and the second is either a boolean bit or actually an * ASCII code. */ static uchar ptyopt[] = { 0x01, 0x7F, /* interrupt = DEL */ 0x02, 0x11, /* quit = ^Q */ 0x03, 0x08, /* backspace = ^H */ 0x04, 0x15, /* line kill = ^U */ 0x05, 0x04, /* EOF = ^D */ 0x20, 0x00, /* don't strip high bit */ 0x48, 0x01, /* give us CRs */ 0x00, /* end options */ }; static uchar rawptyopt[] = { 30, 0, /* ignpar */ 31, 0, /* parmrk */ 32, 0, /* inpck */ 33, 0, /* istrip */ 34, 0, /* inlcr */ 35, 0, /* igncr */ 36, 0, /* icnrl */ 37, 0, /* iuclc */ 38, 0, /* ixon */ 39, 1, /* ixany */ 40, 0, /* ixoff */ 41, 0, /* imaxbel */ 50, 0, /* isig: intr, quit, susp processing */ 51, 0, /* icanon: erase and kill processing */ 52, 0, /* xcase */ 53, 0, /* echo */ 57, 0, /* noflsh */ 58, 0, /* tostop */ 59, 0, /* iexten: impl defined control chars */ 70, 0, /* opost */ 0x00, }; void requestpty(Conn *c) { char *term; int nrow, ncol, width, height; Msg *m; m = allocmsg(c, SSH_CMSG_REQUEST_PTY, 1024); if((term = getenv("TERM")) == nil) term = "9term"; putstring(m, term); readgeom(&nrow, &ncol, &width, &height); putlong(m, nrow); /* characters */ putlong(m, ncol); putlong(m, width); /* pixels */ putlong(m, height); if(rawhack) putbytes(m, rawptyopt, sizeof rawptyopt); else putbytes(m, ptyopt, sizeof ptyopt); sendmsg(m); m = recvmsg(c, 0); switch(m->type){ case SSH_SMSG_SUCCESS: debug(DBG_IO, "PTY allocated\n"); break; case SSH_SMSG_FAILURE: debug(DBG_IO, "PTY allocation failed\n"); break; default: badmsg(m, 0); } free(m); }