diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/ssh/cmsg.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ssh/cmsg.c')
-rwxr-xr-x | sys/src/cmd/ssh/cmsg.c | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/sys/src/cmd/ssh/cmsg.c b/sys/src/cmd/ssh/cmsg.c new file mode 100755 index 000000000..f72f2f33b --- /dev/null +++ b/sys/src/cmd/ssh/cmsg.c @@ -0,0 +1,376 @@ +#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; i<c->nokauth; i++){ + debug(DBG_AUTH, "authmask %#lux, consider %s (%#x)\n", + c->authmask, c->okauth[i]->name, 1<<c->okauth[i]->id); + if(c->authmask & (1<<c->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; i<SESSKEYLEN; i++) + c->sesskey[i] = fastrand(); + c->cipher = nil; + for(i=0; i<c->nokcipher; i++) + if((1<<c->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); +} + |