]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/ssh.c
ip/ipconfig: format ipmask with %M instead of %I
[plan9front.git] / sys / src / cmd / ssh.c
index b85e6ec48fd7f60194eb6e83348fb6687c2423ba..92d8d27b5d0340a1c42ec2ca824fed014193f6d9 100644 (file)
@@ -3,6 +3,7 @@
 #include <mp.h>
 #include <libsec.h>
 #include <auth.h>
+#include <authsrv.h>
 
 enum {
        MSG_DISCONNECT = 1,
@@ -24,6 +25,8 @@ enum {
        MSG_USERAUTH_BANNER,
 
        MSG_USERAUTH_PK_OK = 60,
+       MSG_USERAUTH_INFO_REQUEST = 60,
+       MSG_USERAUTH_INFO_RESPONSE = 61,
 
        MSG_GLOBAL_REQUEST = 80,
        MSG_REQUEST_SUCCESS,
@@ -42,26 +45,43 @@ enum {
        MSG_CHANNEL_FAILURE,
 };
 
+
+enum {
+       Overhead = 256,         // enougth for MSG_CHANNEL_DATA header
+       MaxPacket = 1<<15,
+       WinPackets = 8,         // (1<<15) * 8 = 256K
+};
+
+int MaxPwTries = 3; // retry this often for keyboard-interactive
+
 typedef struct
 {
-       int             pid;
        u32int          seq;
        u32int          kex;
+       u32int          chan;
+
+       int             win;
+       int             pkt;
+       int             eof;
+
        Chachastate     cs1;
        Chachastate     cs2;
-       char            *v;
-       char            eof;
 
        uchar           *r;
        uchar           *w;
-       uchar           b[1<<15];
+       uchar           b[Overhead + MaxPacket];
+
+       char            *v;
+       int             pid;
+       Rendez;
 } Oneway;
 
 int nsid;
 uchar sid[256];
+char thumb[2*SHA2_256dlen+1], *thumbfile;
 
 int fd, intr, raw, debug;
-char *user, *status, *host, *cmd;
+char *user, *service, *status, *host, *cmd;
 
 Oneway recv, send;
 void dispatch(void);
@@ -77,7 +97,7 @@ shutdown(void)
 void
 catch(void*, char *msg)
 {
-       if(strstr(msg, "interrupt") != nil){
+       if(strcmp(msg, "interrupt") == 0){
                intr = 1;
                noted(NCONT);
        }
@@ -90,11 +110,9 @@ wasintr(void)
        char err[ERRMAX];
        int r;
 
-       if(intr)
-               return 1;
        memset(err, 0, sizeof(err));
        errstr(err, sizeof(err));
-       r = strstr(err, "interrupt") != nil;
+       r = strcmp(err, "interrupted") == 0;
        errstr(err, sizeof(err));
        return r;
 }
@@ -302,7 +320,8 @@ readall(int fd, uchar *data, int len)
                        if(n < 0 && wasintr()){
                                n = 0;
                                continue;
-                       }
+                       } else if(n == 0)
+                               werrstr("eof");
                        break;
                }
        }
@@ -405,11 +424,6 @@ ssh2rsasig(uchar *data, int len)
        return m;
 }
 
-/* libsec */
-extern mpint* pkcs1padbuf(uchar *buf, int len, mpint *modulus, int blocktype);
-extern int asn1encodedigest(DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*),
-       uchar *digest, uchar *buf, int len);
-
 mpint*
 pkcs1digest(uchar *data, int len, RSApub *pub)
 {
@@ -473,7 +487,6 @@ void
 kex(int gotkexinit)
 {
        static char kexalgs[] = "curve25519-sha256,curve25519-sha256@libssh.org";
-       static char hostkeyalgs[] = "ssh-rsa";
        static char cipheralgs[] = "chacha20-poly1305@openssh.com";
        static char zipalgs[] = "none";
        static char macalgs[] = "";
@@ -481,7 +494,7 @@ kex(int gotkexinit)
 
        uchar cookie[16], x[32], yc[32], z[32], k[32+1], h[SHA2_256dlen], *ys, *ks, *sig;
        uchar k12[2*ChachaKeylen];
-       int nk, nys, nks, nsig;
+       int i, nk, nys, nks, nsig;
        DigestState *ds;
        mpint *S, *K;
        RSApub *pub;
@@ -493,7 +506,7 @@ kex(int gotkexinit)
        sendpkt("b[ssssssssssbu", MSG_KEXINIT,
                cookie, sizeof(cookie),
                kexalgs, sizeof(kexalgs)-1,
-               hostkeyalgs, sizeof(hostkeyalgs)-1,
+               sshrsa, sizeof(sshrsa)-1,
                cipheralgs, sizeof(cipheralgs)-1,
                cipheralgs, sizeof(cipheralgs)-1,
                macalgs, sizeof(macalgs)-1,
@@ -558,6 +571,30 @@ Next1:     switch(recvpkt()){
        ds = hashstr(yc, 32, ds);
        ds = hashstr(ys, 32, ds);
 
+       if(thumb[0] == 0){
+               Thumbprint *ok;
+
+               sha2_256(ks, nks, h, nil);
+               i = enc64(thumb, sizeof(thumb), h, sizeof(h));
+               while(i > 0 && thumb[i-1] == '=')
+                       i--;
+               thumb[i] = '\0';
+
+               if(debug)
+                       fprint(2, "host fingerprint: %s\n", thumb);
+
+               ok = initThumbprints(thumbfile, nil, "ssh");
+               if(ok == nil || !okThumbprint(h, sizeof(h), ok)){
+                       if(ok != nil) werrstr("unknown host");
+                       fprint(2, "%s: %r\n", argv0);
+                       fprint(2, "verify hostkey: %s %.*[\n", sshrsa, nks, ks);
+                       fprint(2, "add thumbprint after verification:\n");
+                       fprint(2, "\techo 'ssh sha256=%s server=%s' >> %q\n", thumb, host, thumbfile);
+                       sysfatal("checking hostkey failed: %r");
+               }
+               freeThumbprints(ok);
+       }
+
        if((pub = ssh2rsapub(ks, nks)) == nil)
                sysfatal("bad server public key");
        if((S = ssh2rsasig(sig, nsig)) == nil)
@@ -604,11 +641,62 @@ Next2:    switch(recvpkt()){
        setupChachastate(&recv.cs2, k12+0*ChachaKeylen, ChachaKeylen, nil, 64/8, 20);
 }
 
+static char *authnext;
+
+int
+authok(char *meth)
+{
+       int ok = authnext == nil || strstr(authnext, meth) != nil;
+if(debug)
+       fprint(2, "userauth %s %s\n", meth, ok ? "ok" : "skipped");
+       return ok;
+}
+
+int
+authfailure(char *meth)
+{
+       char *s;
+       int n, partial;
+
+       if(unpack(recv.r, recv.w-recv.r, "_sb", &s, &n, &partial) < 0)
+               sysfatal("bad auth failure response");
+       free(authnext);
+       authnext = smprint("%.*s", n, s);
+if(debug)
+       fprint(2, "userauth %s failed: partial=%d, next=%s\n", meth, partial, authnext);
+       return partial != 0 || !authok(meth);
+}
+
+int
+noneauth(void)
+{
+       static char authmeth[] = "none";
+
+       if(!authok(authmeth))
+               return -1;
+
+       sendpkt("bsss", MSG_USERAUTH_REQUEST,
+               user, strlen(user),
+               service, strlen(service),
+               authmeth, sizeof(authmeth)-1);
+
+Next0: switch(recvpkt()){
+       default:
+               dispatch();
+               goto Next0;
+       case MSG_USERAUTH_FAILURE:
+               werrstr("authentication needed");
+               authfailure(authmeth);
+               return -1;
+       case MSG_USERAUTH_SUCCESS:
+               return 0;
+       }
+}
+
 int
-auth(char *username, char *servicename)
+pubkeyauth(void)
 {
-       static char sshuserauth[] = "ssh-userauth";
-       static char publickey[] = "publickey";
+       static char authmeth[] = "publickey";
 
        uchar pk[4096], sig[4096];
        int npk, nsig;
@@ -619,14 +707,8 @@ auth(char *username, char *servicename)
        AuthRpc *rpc;
        RSApub *pub;
 
-       sendpkt("bs", MSG_SERVICE_REQUEST, sshuserauth, sizeof(sshuserauth)-1);
-Next0: switch(recvpkt()){
-       default:
-               dispatch();
-               goto Next0;
-       case MSG_SERVICE_ACCEPT:
-               break;
-       }
+       if(!authok(authmeth))
+               return -1;
 
        if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0)
                return -1;
@@ -657,9 +739,9 @@ Next0:      switch(recvpkt()){
                npk = rsapub2ssh(pub, pk, sizeof(pk));
 
                sendpkt("bsssbss", MSG_USERAUTH_REQUEST,
-                       username, strlen(username),
-                       servicename, strlen(servicename),
-                       publickey, sizeof(publickey)-1,
+                       user, strlen(user),
+                       service, strlen(service),
+                       authmeth, sizeof(authmeth)-1,
                        0,
                        sshrsa, sizeof(sshrsa)-1,
                        pk, npk);
@@ -668,6 +750,8 @@ Next1:              switch(recvpkt()){
                        dispatch();
                        goto Next1;
                case MSG_USERAUTH_FAILURE:
+                       if(authfailure(authmeth))
+                               goto Failed;
                        continue;
                case MSG_USERAUTH_SUCCESS:
                case MSG_USERAUTH_PK_OK:
@@ -678,9 +762,9 @@ Next1:              switch(recvpkt()){
                n = pack(send.b, sizeof(send.b), "sbsssbss",
                        sid, nsid,
                        MSG_USERAUTH_REQUEST,
-                       username, strlen(username),
-                       servicename, strlen(servicename),
-                       publickey, sizeof(publickey)-1,
+                       user, strlen(user),
+                       service, strlen(service),
+                       authmeth, sizeof(authmeth)-1,
                        1,
                        sshrsa, sizeof(sshrsa)-1,
                        pk, npk);
@@ -699,9 +783,9 @@ Next1:              switch(recvpkt()){
 
                /* send final userauth request with the signature */
                sendpkt("bsssbsss", MSG_USERAUTH_REQUEST,
-                       username, strlen(username),
-                       servicename, strlen(servicename),
-                       publickey, sizeof(publickey)-1,
+                       user, strlen(user),
+                       service, strlen(service),
+                       authmeth, sizeof(authmeth)-1,
                        1,
                        sshrsa, sizeof(sshrsa)-1,
                        pk, npk,
@@ -711,6 +795,8 @@ Next2:              switch(recvpkt()){
                        dispatch();
                        goto Next2;
                case MSG_USERAUTH_FAILURE:
+                       if(authfailure(authmeth))
+                               goto Failed;
                        continue;
                case MSG_USERAUTH_SUCCESS:
                        break;
@@ -720,12 +806,152 @@ Next2:           switch(recvpkt()){
                close(afd);
                return 0;
        }
+Failed:
        rsapubfree(pub);
        auth_freerpc(rpc);
        close(afd);
        return -1;      
 }
 
+int
+passauth(void)
+{
+       static char authmeth[] = "password";
+       UserPasswd *up;
+
+       if(!authok(authmeth))
+               return -1;
+
+       up = auth_getuserpasswd(auth_getkey, "proto=pass service=ssh user=%q server=%q thumb=%q",
+               user, host, thumb);
+       if(up == nil)
+               return -1;
+
+       sendpkt("bsssbs", MSG_USERAUTH_REQUEST,
+               user, strlen(user),
+               service, strlen(service),
+               authmeth, sizeof(authmeth)-1,
+               0,
+               up->passwd, strlen(up->passwd));
+
+       memset(up->passwd, 0, strlen(up->passwd));
+       free(up);
+
+Next0: switch(recvpkt()){
+       default:
+               dispatch();
+               goto Next0;
+       case MSG_USERAUTH_FAILURE:
+               werrstr("wrong password");
+               authfailure(authmeth);
+               return -1;
+       case MSG_USERAUTH_SUCCESS:
+               return 0;
+       }
+}
+
+int
+kbintauth(void)
+{
+       static char authmeth[] = "keyboard-interactive";
+       int tries;
+
+       char *name, *inst, *s, *a;
+       int fd, i, n, m;
+       int nquest, echo;
+       uchar *ans, *answ;
+       tries = 0;
+
+       if(!authok(authmeth))
+               return -1;
+
+Loop:
+       if(++tries > MaxPwTries)
+               return -1;
+               
+       sendpkt("bsssss", MSG_USERAUTH_REQUEST,
+               user, strlen(user),
+               service, strlen(service),
+               authmeth, sizeof(authmeth)-1,
+               "", 0,
+               "", 0);
+
+Next0: switch(recvpkt()){
+       default:
+               dispatch();
+               goto Next0;
+       case MSG_USERAUTH_FAILURE:
+               werrstr("keyboard-interactive failed");
+               if(authfailure(authmeth))
+                       return -1;
+               goto Loop;
+       case MSG_USERAUTH_SUCCESS:
+               return 0;
+       case MSG_USERAUTH_INFO_REQUEST:
+               break;
+       }
+Retry:
+       if((fd = open("/dev/cons", OWRITE)) < 0)
+               return -1;
+
+       if(unpack(recv.r, recv.w-recv.r, "_ss.", &name, &n, &inst, &m, &recv.r) < 0)
+               sysfatal("bad info request: name, inst");
+
+       while(n > 0 && strchr("\r\n\t ", name[n-1]) != nil)
+               n--;
+       while(m > 0 && strchr("\r\n\t ", inst[m-1]) != nil)
+               m--;
+
+       if(n > 0)
+               fprint(fd, "%.*s\n", n, name);
+       if(m > 0)
+               fprint(fd, "%.*s\n", m, inst);
+
+       /* lang, nprompt */
+       if(unpack(recv.r, recv.w-recv.r, "su.", &s, &n, &nquest, &recv.r) < 0)
+               sysfatal("bad info request: lang, #quest");
+
+       ans = answ = nil;
+       for(i = 0; i < nquest; i++){
+               if(unpack(recv.r, recv.w-recv.r, "sb.", &s, &n, &echo, &recv.r) < 0)
+                       sysfatal("bad info request: question [%d]", i);
+
+               while(n > 0 && strchr("\r\n\t :", s[n-1]) != nil)
+                       n--;
+               s[n] = '\0';
+
+               if((a = readcons(s, nil, !echo)) == nil)
+                       sysfatal("readcons: %r");
+
+               n = answ - ans;
+               m = strlen(a)+4;
+               if((s = realloc(ans, n + m)) == nil)
+                       sysfatal("realloc: %r");
+               ans = (uchar*)s;
+               answ = ans+n;
+               answ += pack(answ, m, "s", a, m-4);
+       }
+
+       sendpkt("bu[", MSG_USERAUTH_INFO_RESPONSE, i, ans, answ - ans);
+       free(ans);
+       close(fd);
+
+Next1: switch(recvpkt()){
+       default:
+               dispatch();
+               goto Next1;
+       case MSG_USERAUTH_INFO_REQUEST:
+               goto Retry;
+       case MSG_USERAUTH_FAILURE:
+               werrstr("keyboard-interactive failed");
+               if(authfailure(authmeth))
+                       return -1;
+               goto Loop;
+       case MSG_USERAUTH_SUCCESS:
+               return 0;
+       }
+}
+
 void
 dispatch(void)
 {
@@ -735,8 +961,14 @@ dispatch(void)
 
        switch(recv.r[0]){
        case MSG_IGNORE:
+               return;
        case MSG_GLOBAL_REQUEST:
-       case MSG_CHANNEL_WINDOW_ADJUST:
+               if(unpack(recv.r, recv.w-recv.r, "_sb", &s, &n, &b) < 0)
+                       break;
+               if(debug)
+                       fprint(2, "%s: global request: %.*s\n", argv0, n, s);
+               if(b != 0)
+                       sendpkt("b", MSG_REQUEST_FAILURE);
                return;
        case MSG_DISCONNECT:
                if(unpack(recv.r, recv.w-recv.r, "_us", &c, &s, &n) < 0)
@@ -756,38 +988,58 @@ dispatch(void)
        case MSG_CHANNEL_DATA:
                if(unpack(recv.r, recv.w-recv.r, "_us", &c, &s, &n) < 0)
                        break;
-               if(c != 0)
+               if(c != recv.chan)
                        break;
                if(write(1, s, n) != n)
                        sysfatal("write out: %r");
        Winadjust:
-               sendpkt("buu", MSG_CHANNEL_WINDOW_ADJUST, c, n);
+               recv.win -= n;
+               if(recv.win < recv.pkt){
+                       n = WinPackets*recv.pkt;
+                       recv.win += n;
+                       sendpkt("buu", MSG_CHANNEL_WINDOW_ADJUST, send.chan, n);
+               }
                return;
        case MSG_CHANNEL_EXTENDED_DATA:
                if(unpack(recv.r, recv.w-recv.r, "_uus", &c, &b, &s, &n) < 0)
                        break;
-               if(c != 0)
+               if(c != recv.chan)
                        break;
                if(b == 1) write(2, s, n);
                goto Winadjust;
+       case MSG_CHANNEL_WINDOW_ADJUST:
+               if(unpack(recv.r, recv.w-recv.r, "_uu", &c, &n) < 0)
+                       break;
+               if(c != recv.chan)
+                       break;
+               send.win += n;
+               if(send.win >= send.pkt)
+                       rwakeup(&send);
+               return;
        case MSG_CHANNEL_REQUEST:
                if(unpack(recv.r, recv.w-recv.r, "_usb.", &c, &s, &n, &b, &p) < 0)
                        break;
-               if(c != 0)
+               if(c != recv.chan)
                        break;
                if(n == 11 && memcmp(s, "exit-signal", n) == 0){
                        if(unpack(p, recv.w-p, "s", &s, &n) < 0)
                                break;
                        if(n != 0 && status == nil)
                                status = smprint("%.*s", n, s);
+                       c = MSG_CHANNEL_SUCCESS;
                } else if(n == 11 && memcmp(s, "exit-status", n) == 0){
                        if(unpack(p, recv.w-p, "u", &n) < 0)
                                break;
                        if(n != 0 && status == nil)
                                status = smprint("%d", n);
-               } else if(debug) {
-                       fprint(2, "%s: channel request: %.*s\n", argv0, n, s);
+                       c = MSG_CHANNEL_SUCCESS;
+               } else {
+                       if(debug)
+                               fprint(2, "%s: channel request: %.*s\n", argv0, n, s);
+                       c = MSG_CHANNEL_FAILURE;
                }
+               if(b != 0)
+                       sendpkt("bu", c, recv.chan);
                return;
        case MSG_CHANNEL_EOF:
                recv.eof = 1;
@@ -824,19 +1076,35 @@ static struct {
        int     ypixels;
        int     lines;
        int     cols;
-} tty = {
-       "dumb",
-       0,
-       0,
-       0,
-       0,
-};
+} tty;
+
+void
+getdim(void)
+{
+       char *s;
+
+       if(s = getenv("XPIXELS")){
+               tty.xpixels = atoi(s);
+               free(s);
+       }
+       if(s = getenv("YPIXELS")){
+               tty.ypixels = atoi(s);
+               free(s);
+       }
+       if(s = getenv("LINES")){
+               tty.lines = atoi(s);
+               free(s);
+       }
+       if(s = getenv("COLS")){
+               tty.cols = atoi(s);
+               free(s);
+       }
+}
 
 void
 rawon(void)
 {
        int ctl;
-       char *s;
 
        close(0);
        if(open("/dev/cons", OREAD) != 0)
@@ -845,40 +1113,44 @@ rawon(void)
        if(open("/dev/cons", OWRITE) != 1)
                sysfatal("open: %r");
        dup(1, 2);
-       if((ctl = open("/dev/consctl", OWRITE)) >= 0)
+       if((ctl = open("/dev/consctl", OWRITE)) >= 0){
                write(ctl, "rawon", 5);
-       if(s = getenv("TERM")){
-               tty.term = s;
-               if(s = getenv("XPIXELS")){
-                       tty.xpixels = atoi(s);
-                       free(s);
-               }
-               if(s = getenv("YPIXELS")){
-                       tty.ypixels = atoi(s);
-                       free(s);
-               }
-               if(s = getenv("LINES")){
-                       tty.lines = atoi(s);
-                       free(s);
-               }
-               if(s = getenv("COLS")){
-                       tty.cols = atoi(s);
-                       free(s);
-               }
+               write(ctl, "winchon", 7);       /* vt(1): interrupt note on window change */
+       }
+       getdim();
+}
+
+#pragma           varargck    type  "k"   char*
+
+kfmt(Fmt *f)
+{
+       char *s, *p;
+       int n;
+
+       s = va_arg(f->args, char*);
+       n = fmtstrcpy(f, "'");
+       while((p = strchr(s, '\'')) != nil){
+               *p = '\0';
+               n += fmtstrcpy(f, s);
+               *p = '\'';
+               n += fmtstrcpy(f, "'\\''");
+               s = p+1;
        }
+       n += fmtstrcpy(f, s);
+       n += fmtstrcpy(f, "'");
+       return n;
 }
 
 void
 usage(void)
 {
-       fprint(2, "usage: %s [-dR] [-u user] [user@]host [cmd]\n", argv0);
+       fprint(2, "usage: %s [-dR] [-t thumbfile] [-T tries] [-u user] [-h] [user@]host [cmd args...]\n", argv0);
        exits("usage");
 }
 
 void
 main(int argc, char *argv[])
 {
-       static char buf[8*1024];
        static QLock sl;
        int b, n, c;
        char *s;
@@ -886,10 +1158,13 @@ main(int argc, char *argv[])
        quotefmtinstall();
        fmtinstall('B', mpfmt);
        fmtinstall('H', encodefmt);
+       fmtinstall('[', encodefmt);
+       fmtinstall('k', kfmt);
 
-       s = getenv("TERM");
-       raw = s != nil && strcmp(s, "dumb") != 0;
-       free(s);
+       tty.term = getenv("TERM");
+       if(tty.term == nil)
+               tty.term = "";
+       raw = *tty.term != 0;
 
        ARGBEGIN {
        case 'd':
@@ -898,15 +1173,30 @@ main(int argc, char *argv[])
        case 'R':
                raw = 0;
                break;
+       case 'r':
+               raw = 2; /* bloody */
+               break;
        case 'u':
                user = EARGF(usage());
                break;
+       case 'h':
+               host = EARGF(usage());
+               break;
+       case 't':
+               thumbfile = EARGF(usage());
+               break;
+       case 'T':
+               MaxPwTries = strtol(EARGF(usage()), &s, 0);
+               if(*s != 0) usage();
+               break;
        } ARGEND;
 
-       if(argc == 0)
-               usage();
+       if(host == nil){
+               if(argc == 0)
+                       usage();
+               host = *argv++;
+       }
 
-       host = *argv++;
        if(user == nil){
                s = strchr(host, '@');
                if(s != nil){
@@ -915,17 +1205,18 @@ main(int argc, char *argv[])
                        host = s;
                }
        }
+
        for(cmd = nil; *argv != nil; argv++){
-               if(cmd == nil)
+               if(cmd == nil){
                        cmd = strdup(*argv);
-               else {
-                       s = smprint("%s %q", cmd, *argv);
+                       if(raw == 1)
+                               raw = 0;
+               }else{
+                       s = smprint("%s %k", cmd, *argv);
                        free(cmd);
                        cmd = s;
                }
        }
-       if(cmd != nil)
-               raw = 0;
 
        if((fd = dial(netmkaddr(host, nil, "ssh"), nil, nil, nil)) < 0)
                sysfatal("dial: %r");
@@ -939,23 +1230,43 @@ main(int argc, char *argv[])
                sysfatal("bad server version: %s", recv.v);
        recv.v = strdup(recv.v);
 
-       kex(0);
+       send.l = recv.l = &sl;
+
        if(user == nil)
                user = getuser();
-       if(auth(user, "ssh-connection") < 0)
+       if(thumbfile == nil)
+               thumbfile = smprint("%s/lib/sshthumbs", getenv("home"));
+
+       kex(0);
+
+       sendpkt("bs", MSG_SERVICE_REQUEST, "ssh-userauth", 12);
+Next0: switch(recvpkt()){
+       default:
+               dispatch();
+               goto Next0;
+       case MSG_SERVICE_ACCEPT:
+               break;
+       }
+
+       service = "ssh-connection";
+       if(noneauth() < 0 && pubkeyauth() < 0 && passauth() < 0 && kbintauth() < 0)
                sysfatal("auth: %r");
 
+       recv.pkt = MaxPacket;
+       recv.win = WinPackets*recv.pkt;
+       recv.chan = 0;
+
        /* open hailing frequencies */
        sendpkt("bsuuu", MSG_CHANNEL_OPEN,
                "session", 7,
-               0,
-               sizeof(buf),
-               sizeof(buf));
+               recv.chan,
+               recv.win,
+               recv.pkt);
 
-Next0: switch(recvpkt()){
+Next1: switch(recvpkt()){
        default:
                dispatch();
-               goto Next0;
+               goto Next1;
        case MSG_CHANNEL_OPEN_FAILURE:
                if(unpack(recv.r, recv.w-recv.r, "_uus", &c, &b, &s, &n) < 0)
                        n = strlen(s = "???");
@@ -964,6 +1275,11 @@ Next0:    switch(recvpkt()){
                break;
        }
 
+       if(unpack(recv.r, recv.w-recv.r, "_uuuu", &recv.chan, &send.chan, &send.win, &send.pkt) < 0)
+               sysfatal("bad channel open confirmation");
+       if(send.pkt <= 0 || send.pkt > MaxPacket)
+               send.pkt = MaxPacket;
+
        notify(catch);
        atexit(shutdown);
 
@@ -975,7 +1291,7 @@ Next0:     switch(recvpkt()){
        /* parent reads and dispatches packets */
        if(n > 0) {
                send.pid = n;
-               while((send.eof|recv.eof) == 0){
+               while(recv.eof == 0){
                        recvpkt();
                        qlock(&sl);                                     
                        dispatch();
@@ -991,7 +1307,7 @@ Next0:     switch(recvpkt()){
        if(raw) {
                rawon();
                sendpkt("busbsuuuus", MSG_CHANNEL_REQUEST,
-                       0,
+                       send.chan,
                        "pty-req", 7,
                        0,
                        tty.term, strlen(tty.term),
@@ -1003,26 +1319,44 @@ Next0:  switch(recvpkt()){
        }
        if(cmd == nil){
                sendpkt("busb", MSG_CHANNEL_REQUEST,
-                       0,
+                       send.chan,
                        "shell", 5,
                        0);
-       } else {
+       } else if(*cmd == '#') {
                sendpkt("busbs", MSG_CHANNEL_REQUEST,
+                       send.chan,
+                       "subsystem", 9,
                        0,
+                       cmd+1, strlen(cmd)-1);
+       } else {
+               sendpkt("busbs", MSG_CHANNEL_REQUEST,
+                       send.chan,
                        "exec", 4,
                        0,
                        cmd, strlen(cmd));
        }
        for(;;){
+               static uchar buf[MaxPacket];
                qunlock(&sl);
-               n = read(0, buf, sizeof(buf));
+               n = read(0, buf, send.pkt);
                qlock(&sl);
                if(send.eof)
                        break;
-               if(n < 0 && wasintr()){
+               if(n < 0 && wasintr())
+                       intr = 1;
+               if(intr){
                        if(!raw) break;
-                       sendpkt("busbs", MSG_CHANNEL_REQUEST,
+                       getdim();
+                       sendpkt("busbuuuu", MSG_CHANNEL_REQUEST,
+                               send.chan,
+                               "window-change", 13,
                                0,
+                               tty.cols,
+                               tty.lines,
+                               tty.xpixels,
+                               tty.ypixels);
+                       sendpkt("busbs", MSG_CHANNEL_REQUEST,
+                               send.chan,
                                "signal", 6,
                                0,
                                "INT", 3);
@@ -1031,12 +1365,15 @@ Next0:  switch(recvpkt()){
                }
                if(n <= 0)
                        break;
+               send.win -= n;
+               while(send.win < 0)
+                       rsleep(&send);
                sendpkt("bus", MSG_CHANNEL_DATA,
-                       0,
+                       send.chan,
                        buf, n);
        }
        if(send.eof++ == 0)
-               sendpkt("bu", raw ? MSG_CHANNEL_CLOSE : MSG_CHANNEL_EOF, 0);
+               sendpkt("bu", raw ? MSG_CHANNEL_CLOSE : MSG_CHANNEL_EOF, send.chan);
        qunlock(&sl);
 
        exits(nil);