Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#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);
}