summaryrefslogtreecommitdiff
path: root/sys/src/cmd/ssh
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/ssh
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ssh')
-rwxr-xr-xsys/src/cmd/ssh/agent.c450
-rwxr-xr-xsys/src/cmd/ssh/authpasswd.c38
-rwxr-xr-xsys/src/cmd/ssh/authrsa.c113
-rwxr-xr-xsys/src/cmd/ssh/authsrvpasswd.c22
-rwxr-xr-xsys/src/cmd/ssh/authsrvtis.c52
-rwxr-xr-xsys/src/cmd/ssh/authtis.c65
-rwxr-xr-xsys/src/cmd/ssh/cipher3des.c47
-rwxr-xr-xsys/src/cmd/ssh/cipherblowfish.c40
-rwxr-xr-xsys/src/cmd/ssh/cipherdes.c40
-rwxr-xr-xsys/src/cmd/ssh/ciphernone.c28
-rwxr-xr-xsys/src/cmd/ssh/cipherrc4.c45
-rwxr-xr-xsys/src/cmd/ssh/ciphertwiddle.c28
-rwxr-xr-xsys/src/cmd/ssh/cmsg.c376
-rwxr-xr-xsys/src/cmd/ssh/mkfile78
-rwxr-xr-xsys/src/cmd/ssh/msg.c512
-rwxr-xr-xsys/src/cmd/ssh/pubkey.c227
-rwxr-xr-xsys/src/cmd/ssh/scp.c799
-rwxr-xr-xsys/src/cmd/ssh/smsg.c285
-rwxr-xr-xsys/src/cmd/ssh/ssh.c592
-rwxr-xr-xsys/src/cmd/ssh/ssh.h303
-rwxr-xr-xsys/src/cmd/ssh/sshnet.c1110
-rwxr-xr-xsys/src/cmd/ssh/sshserve.c315
-rwxr-xr-xsys/src/cmd/ssh/util.c269
23 files changed, 5834 insertions, 0 deletions
diff --git a/sys/src/cmd/ssh/agent.c b/sys/src/cmd/ssh/agent.c
new file mode 100755
index 000000000..cf0a6c7d6
--- /dev/null
+++ b/sys/src/cmd/ssh/agent.c
@@ -0,0 +1,450 @@
+#include "ssh.h"
+#include <bio.h>
+
+typedef struct Key Key;
+struct Key
+{
+ mpint *mod;
+ mpint *ek;
+ char *comment;
+};
+
+typedef struct Achan Achan;
+struct Achan
+{
+ int open;
+ u32int chan; /* of remote */
+ uchar lbuf[4];
+ uint nlbuf;
+ uint len;
+ uchar *data;
+ int ndata;
+ int needeof;
+ int needclosed;
+};
+
+Achan achan[16];
+
+static char*
+find(char **f, int nf, char *k)
+{
+ int i, len;
+
+ len = strlen(k);
+ for(i=1; i<nf; i++) /* i=1: f[0] is "key" */
+ if(strncmp(f[i], k, len) == 0 && f[i][len] == '=')
+ return f[i]+len+1;
+ return nil;
+}
+
+static int
+listkeys(Key **kp)
+{
+ Biobuf *b;
+ Key *k;
+ int nk;
+ char *p, *f[20];
+ int nf;
+ mpint *mod, *ek;
+
+ *kp = nil;
+ if((b = Bopen("/mnt/factotum/ctl", OREAD)) == nil)
+ return -1;
+
+ k = nil;
+ nk = 0;
+ while((p = Brdline(b, '\n')) != nil){
+ p[Blinelen(b)-1] = '\0';
+ nf = tokenize(p, f, nelem(f));
+ if(nf == 0 || strcmp(f[0], "key") != 0)
+ continue;
+ p = find(f, nf, "proto");
+ if(p == nil || strcmp(p, "rsa") != 0)
+ continue;
+ p = find(f, nf, "n");
+ if(p == nil || (mod = strtomp(p, nil, 16, nil)) == nil)
+ continue;
+ p = find(f, nf, "ek");
+ if(p == nil || (ek = strtomp(p, nil, 16, nil)) == nil){
+ mpfree(mod);
+ continue;
+ }
+ p = find(f, nf, "comment");
+ if(p == nil)
+ p = "";
+ k = erealloc(k, (nk+1)*sizeof(k[0]));
+ k[nk].mod = mod;
+ k[nk].ek = ek;
+ k[nk].comment = emalloc(strlen(p)+1);
+ strcpy(k[nk].comment, p);
+ nk++;
+ }
+ Bterm(b);
+ *kp = k;
+ return nk;
+}
+
+
+static int
+dorsa(mpint *mod, mpint *exp, mpint *chal, uchar chalbuf[32])
+{
+ int afd;
+ AuthRpc *rpc;
+ mpint *m;
+ char buf[4096], *p;
+ mpint *decr, *unpad;
+
+ USED(exp);
+
+ snprint(buf, sizeof buf, "proto=rsa service=ssh role=client");
+ if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){
+ debug(DBG_AUTH, "open /mnt/factotum/rpc: %r\n");
+ return -1;
+ }
+ if((rpc = auth_allocrpc(afd)) == nil){
+ debug(DBG_AUTH, "auth_allocrpc: %r\n");
+ close(afd);
+ return -1;
+ }
+ if(auth_rpc(rpc, "start", buf, strlen(buf)) != ARok){
+ debug(DBG_AUTH, "auth_rpc start failed: %r\n");
+ Die:
+ auth_freerpc(rpc);
+ close(afd);
+ return -1;
+ }
+ m = nil;
+ debug(DBG_AUTH, "trying factotum rsa keys\n");
+ while(auth_rpc(rpc, "read", nil, 0) == ARok){
+ debug(DBG_AUTH, "try %s\n", (char*)rpc->arg);
+ m = strtomp(rpc->arg, nil, 16, nil);
+ if(mpcmp(m, mod) == 0)
+ break;
+ mpfree(m);
+ m = nil;
+ }
+ if(m == nil)
+ goto Die;
+ mpfree(m);
+
+ p = mptoa(chal, 16, nil, 0);
+ if(p == nil){
+ debug(DBG_AUTH, "\tmptoa failed: %r\n");
+ goto Die;
+ }
+ if(auth_rpc(rpc, "write", p, strlen(p)) != ARok){
+ debug(DBG_AUTH, "\tauth_rpc write failed: %r\n");
+ free(p);
+ goto Die;
+ }
+ free(p);
+ if(auth_rpc(rpc, "read", nil, 0) != ARok){
+ debug(DBG_AUTH, "\tauth_rpc read failed: %r\n");
+ goto Die;
+ }
+ decr = strtomp(rpc->arg, nil, 16, nil);
+ if(decr == nil){
+ debug(DBG_AUTH, "\tdecr %s failed\n", rpc->arg);
+ goto Die;
+ }
+ debug(DBG_AUTH, "\tdecrypted %B\n", decr);
+ unpad = rsaunpad(decr);
+ if(unpad == nil){
+ debug(DBG_AUTH, "\tunpad %B failed\n", decr);
+ mpfree(decr);
+ goto Die;
+ }
+ debug(DBG_AUTH, "\tunpadded %B\n", unpad);
+ mpfree(decr);
+ mptoberjust(unpad, chalbuf, 32);
+ mpfree(unpad);
+ auth_freerpc(rpc);
+ close(afd);
+ return 0;
+}
+
+int
+startagent(Conn *c)
+{
+ int ret;
+ Msg *m;
+
+ m = allocmsg(c, SSH_CMSG_AGENT_REQUEST_FORWARDING, 0);
+ sendmsg(m);
+
+ m = recvmsg(c, -1);
+ switch(m->type){
+ case SSH_SMSG_SUCCESS:
+ debug(DBG_AUTH, "agent allocated\n");
+ ret = 0;
+ break;
+ case SSH_SMSG_FAILURE:
+ debug(DBG_AUTH, "agent failed to allocate\n");
+ ret = -1;
+ break;
+ default:
+ badmsg(m, 0);
+ ret = -1;
+ break;
+ }
+ free(m);
+ return ret;
+}
+
+void handlefullmsg(Conn*, Achan*);
+
+void
+handleagentmsg(Msg *m)
+{
+ u32int chan, len;
+ int n;
+ Achan *a;
+
+ assert(m->type == SSH_MSG_CHANNEL_DATA);
+
+ debug(DBG_AUTH, "agent data\n");
+ debug(DBG_AUTH, "\t%.*H\n", (int)(m->ep - m->rp), m->rp);
+ chan = getlong(m);
+ len = getlong(m);
+ if(m->rp+len != m->ep)
+ sysfatal("got bad channel data");
+
+ if(chan >= nelem(achan))
+ error("bad channel in agent request");
+
+ a = &achan[chan];
+
+ while(m->rp < m->ep){
+ if(a->nlbuf < 4){
+ a->lbuf[a->nlbuf++] = getbyte(m);
+ if(a->nlbuf == 4){
+ a->len = (a->lbuf[0]<<24) | (a->lbuf[1]<<16) | (a->lbuf[2]<<8) | a->lbuf[3];
+ a->data = erealloc(a->data, a->len);
+ a->ndata = 0;
+ }
+ continue;
+ }
+ if(a->ndata < a->len){
+ n = a->len - a->ndata;
+ if(n > m->ep - m->rp)
+ n = m->ep - m->rp;
+ memmove(a->data+a->ndata, getbytes(m, n), n);
+ a->ndata += n;
+ }
+ if(a->ndata == a->len){
+ handlefullmsg(m->c, a);
+ a->nlbuf = 0;
+ }
+ }
+}
+
+void
+handlefullmsg(Conn *c, Achan *a)
+{
+ int i;
+ u32int chan, len, n, rt;
+ uchar type;
+ Msg *m, mm;
+ Msg *r;
+ Key *k;
+ int nk;
+ mpint *mod, *ek, *chal;
+ uchar sessid[16];
+ uchar chalbuf[32];
+ uchar digest[16];
+ DigestState *s;
+ static int first;
+
+ assert(a->len == a->ndata);
+
+ chan = a->chan;
+ mm.rp = a->data;
+ mm.ep = a->data+a->ndata;
+ mm.c = c;
+ m = &mm;
+
+ type = getbyte(m);
+
+ if(first == 0){
+ first++;
+ fmtinstall('H', encodefmt);
+ }
+
+ switch(type){
+ default:
+ debug(DBG_AUTH, "unknown msg type\n");
+ Failure:
+ debug(DBG_AUTH, "agent sending failure\n");
+ r = allocmsg(m->c, SSH_MSG_CHANNEL_DATA, 13);
+ putlong(r, chan);
+ putlong(r, 5);
+ putlong(r, 1);
+ putbyte(r, SSH_AGENT_FAILURE);
+ sendmsg(r);
+ return;
+
+ case SSH_AGENTC_REQUEST_RSA_IDENTITIES:
+ debug(DBG_AUTH, "agent request identities\n");
+ nk = listkeys(&k);
+ if(nk < 0)
+ goto Failure;
+ len = 1+4; /* type, nk */
+ for(i=0; i<nk; i++){
+ len += 4;
+ len += 2+(mpsignif(k[i].ek)+7)/8;
+ len += 2+(mpsignif(k[i].mod)+7)/8;
+ len += 4+strlen(k[i].comment);
+ }
+ r = allocmsg(m->c, SSH_MSG_CHANNEL_DATA, 12+len);
+ putlong(r, chan);
+ putlong(r, len+4);
+ putlong(r, len);
+ putbyte(r, SSH_AGENT_RSA_IDENTITIES_ANSWER);
+ putlong(r, nk);
+ for(i=0; i<nk; i++){
+ debug(DBG_AUTH, "\t%B %B %s\n", k[i].ek, k[i].mod, k[i].comment);
+ putlong(r, mpsignif(k[i].mod));
+ putmpint(r, k[i].ek);
+ putmpint(r, k[i].mod);
+ putstring(r, k[i].comment);
+ mpfree(k[i].ek);
+ mpfree(k[i].mod);
+ free(k[i].comment);
+ }
+ free(k);
+ sendmsg(r);
+ break;
+
+ case SSH_AGENTC_RSA_CHALLENGE:
+ n = getlong(m);
+ USED(n); /* number of bits in key; who cares? */
+ ek = getmpint(m);
+ mod = getmpint(m);
+ chal = getmpint(m);
+ memmove(sessid, getbytes(m, 16), 16);
+ rt = getlong(m);
+ debug(DBG_AUTH, "agent challenge %B %B %B %ud (%p %p)\n",
+ ek, mod, chal, rt, m->rp, m->ep);
+ if(rt != 1 || dorsa(mod, ek, chal, chalbuf) < 0){
+ mpfree(ek);
+ mpfree(mod);
+ mpfree(chal);
+ goto Failure;
+ }
+ s = md5(chalbuf, 32, nil, nil);
+ md5(sessid, 16, digest, s);
+ r = allocmsg(m->c, SSH_MSG_CHANNEL_DATA, 12+1+16);
+ putlong(r, chan);
+ putlong(r, 4+16+1);
+ putlong(r, 16+1);
+ putbyte(r, SSH_AGENT_RSA_RESPONSE);
+ putbytes(r, digest, 16);
+ debug(DBG_AUTH, "digest %.16H\n", digest);
+ sendmsg(r);
+ mpfree(ek);
+ mpfree(mod);
+ mpfree(chal);
+ return;
+
+ case SSH_AGENTC_ADD_RSA_IDENTITY:
+ goto Failure;
+/*
+ n = getlong(m);
+ pubmod = getmpint(m);
+ pubexp = getmpint(m);
+ privexp = getmpint(m);
+ pinversemodq = getmpint(m);
+ p = getmpint(m);
+ q = getmpint(m);
+ comment = getstring(m);
+ add to factotum;
+ send SSH_AGENT_SUCCESS or SSH_AGENT_FAILURE;
+*/
+
+ case SSH_AGENTC_REMOVE_RSA_IDENTITY:
+ goto Failure;
+/*
+ n = getlong(m);
+ pubmod = getmpint(m);
+ pubexp = getmpint(m);
+ tell factotum to del key
+ send SSH_AGENT_SUCCESS or SSH_AGENT_FAILURE;
+*/
+ }
+}
+
+void
+handleagentopen(Msg *m)
+{
+ int i;
+ u32int remote;
+
+ assert(m->type == SSH_SMSG_AGENT_OPEN);
+ remote = getlong(m);
+ debug(DBG_AUTH, "agent open %d\n", remote);
+
+ for(i=0; i<nelem(achan); i++)
+ if(achan[i].open == 0 && achan[i].needeof == 0 && achan[i].needclosed == 0)
+ break;
+ if(i == nelem(achan)){
+ m = allocmsg(m->c, SSH_MSG_CHANNEL_OPEN_FAILURE, 4);
+ putlong(m, remote);
+ sendmsg(m);
+ return;
+ }
+
+ debug(DBG_AUTH, "\tremote %d is local %d\n", remote, i);
+ achan[i].open = 1;
+ achan[i].needeof = 1;
+ achan[i].needclosed = 1;
+ achan[i].nlbuf = 0;
+ achan[i].chan = remote;
+ m = allocmsg(m->c, SSH_MSG_CHANNEL_OPEN_CONFIRMATION, 8);
+ putlong(m, remote);
+ putlong(m, i);
+ sendmsg(m);
+}
+
+void
+handleagentieof(Msg *m)
+{
+ u32int local;
+
+ assert(m->type == SSH_MSG_CHANNEL_INPUT_EOF);
+ local = getlong(m);
+ debug(DBG_AUTH, "agent close %d\n", local);
+ if(local < nelem(achan)){
+ debug(DBG_AUTH, "\tlocal %d is remote %d\n", local, achan[local].chan);
+ achan[local].open = 0;
+/*
+ m = allocmsg(m->c, SSH_MSG_CHANNEL_OUTPUT_CLOSED, 4);
+ putlong(m, achan[local].chan);
+ sendmsg(m);
+*/
+ if(achan[local].needeof){
+ achan[local].needeof = 0;
+ m = allocmsg(m->c, SSH_MSG_CHANNEL_INPUT_EOF, 4);
+ putlong(m, achan[local].chan);
+ sendmsg(m);
+ }
+ }
+}
+
+void
+handleagentoclose(Msg *m)
+{
+ u32int local;
+
+ assert(m->type == SSH_MSG_CHANNEL_OUTPUT_CLOSED);
+ local = getlong(m);
+ debug(DBG_AUTH, "agent close %d\n", local);
+ if(local < nelem(achan)){
+ debug(DBG_AUTH, "\tlocal %d is remote %d\n", local, achan[local].chan);
+ if(achan[local].needclosed){
+ achan[local].needclosed = 0;
+ m = allocmsg(m->c, SSH_MSG_CHANNEL_OUTPUT_CLOSED, 4);
+ putlong(m, achan[local].chan);
+ sendmsg(m);
+ }
+ }
+}
diff --git a/sys/src/cmd/ssh/authpasswd.c b/sys/src/cmd/ssh/authpasswd.c
new file mode 100755
index 000000000..2d2b21fd7
--- /dev/null
+++ b/sys/src/cmd/ssh/authpasswd.c
@@ -0,0 +1,38 @@
+#include "ssh.h"
+
+static int
+authpasswordfn(Conn *c)
+{
+ Msg *m;
+ UserPasswd *up;
+
+ up = auth_getuserpasswd(c->interactive ? auth_getkey : nil, "proto=pass service=ssh server=%q user=%q", c->host, c->user);
+ if(up == nil){
+ debug(DBG_AUTH, "getuserpasswd returned nothing (interactive=%d)\n", c->interactive);
+ return -1;
+ }
+
+ debug(DBG_AUTH, "try using password from factotum\n");
+ m = allocmsg(c, SSH_CMSG_AUTH_PASSWORD, 4+strlen(up->passwd));
+ putstring(m, up->passwd);
+ sendmsg(m);
+
+ m = recvmsg(c, -1);
+ switch(m->type){
+ default:
+ badmsg(m, 0);
+ case SSH_SMSG_SUCCESS:
+ free(m);
+ return 0;
+ case SSH_SMSG_FAILURE:
+ free(m);
+ return -1;
+ }
+}
+
+Auth authpassword =
+{
+ SSH_AUTH_PASSWORD,
+ "password",
+ authpasswordfn,
+};
diff --git a/sys/src/cmd/ssh/authrsa.c b/sys/src/cmd/ssh/authrsa.c
new file mode 100755
index 000000000..be6f094cf
--- /dev/null
+++ b/sys/src/cmd/ssh/authrsa.c
@@ -0,0 +1,113 @@
+#include "ssh.h"
+
+static int
+authrsafn(Conn *c)
+{
+ uchar chalbuf[32+SESSIDLEN], response[MD5dlen];
+ char *s, *p;
+ int afd, ret;
+ AuthRpc *rpc;
+ Msg *m;
+ mpint *chal, *decr, *unpad, *mod;
+
+ debug(DBG_AUTH, "rsa!\n");
+
+ if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){
+ debug(DBG_AUTH, "open /mnt/factotum/rpc: %r\n");
+ return -1;
+ }
+ if((rpc = auth_allocrpc(afd)) == nil){
+ debug(DBG_AUTH, "auth_allocrpc: %r\n");
+ close(afd);
+ return -1;
+ }
+ s = "proto=rsa role=client";
+ if(auth_rpc(rpc, "start", s, strlen(s)) != ARok){
+ debug(DBG_AUTH, "auth_rpc start %s failed: %r\n", s);
+ auth_freerpc(rpc);
+ close(afd);
+ return -1;
+ }
+
+ ret = -1;
+ debug(DBG_AUTH, "trying factotum rsa keys\n");
+ while(auth_rpc(rpc, "read", nil, 0) == ARok){
+ debug(DBG_AUTH, "try %s\n", (char*)rpc->arg);
+ mod = strtomp(rpc->arg, nil, 16, nil);
+ m = allocmsg(c, SSH_CMSG_AUTH_RSA, 16+(mpsignif(mod)+7/8));
+ putmpint(m, mod);
+ sendmsg(m);
+ mpfree(mod);
+ m = recvmsg(c, -1);
+ switch(m->type){
+ case SSH_SMSG_FAILURE:
+ debug(DBG_AUTH, "\tnot accepted %s\n", (char*)rpc->arg);
+ free(m);
+ continue;
+ default:
+ badmsg(m, 0);
+ case SSH_SMSG_AUTH_RSA_CHALLENGE:
+ break;
+ }
+ chal = getmpint(m);
+ debug(DBG_AUTH, "\tgot challenge %B\n", chal);
+ free(m);
+ p = mptoa(chal, 16, nil, 0);
+ mpfree(chal);
+ if(p == nil){
+ debug(DBG_AUTH, "\tmptoa failed: %r\n");
+ unpad = mpnew(0);
+ goto Keepgoing;
+ }
+ if(auth_rpc(rpc, "write", p, strlen(p)) != ARok){
+ debug(DBG_AUTH, "\tauth_rpc write failed: %r\n");
+ free(p);
+ unpad = mpnew(0); /* it will fail, we'll go round again */
+ goto Keepgoing;
+ }
+ free(p);
+ if(auth_rpc(rpc, "read", nil, 0) != ARok){
+ debug(DBG_AUTH, "\tauth_rpc read failed: %r\n");
+ unpad = mpnew(0);
+ goto Keepgoing;
+ }
+ decr = strtomp(rpc->arg, nil, 16, nil);
+ debug(DBG_AUTH, "\tdecrypted %B\n", decr);
+ unpad = rsaunpad(decr);
+ debug(DBG_AUTH, "\tunpadded %B\n", unpad);
+ mpfree(decr);
+ Keepgoing:
+ mptoberjust(unpad, chalbuf, 32);
+ mpfree(unpad);
+ debug(DBG_AUTH, "\trjusted %.*H\n", 32, chalbuf);
+ memmove(chalbuf+32, c->sessid, SESSIDLEN);
+ debug(DBG_AUTH, "\tappend sesskey %.*H\n", 32, chalbuf);
+ md5(chalbuf, 32+SESSIDLEN, response, nil);
+ m = allocmsg(c, SSH_CMSG_AUTH_RSA_RESPONSE, MD5dlen);
+ putbytes(m, response, MD5dlen);
+ sendmsg(m);
+
+ m = recvmsg(c, -1);
+ switch(m->type){
+ case SSH_SMSG_FAILURE:
+ free(m);
+ continue;
+ default:
+ badmsg(m, 0);
+ case SSH_SMSG_SUCCESS:
+ break;
+ }
+ ret = 0;
+ break;
+ }
+ auth_freerpc(rpc);
+ close(afd);
+ return ret;
+}
+
+Auth authrsa =
+{
+ SSH_AUTH_RSA,
+ "rsa",
+ authrsafn,
+};
diff --git a/sys/src/cmd/ssh/authsrvpasswd.c b/sys/src/cmd/ssh/authsrvpasswd.c
new file mode 100755
index 000000000..8caec8d2b
--- /dev/null
+++ b/sys/src/cmd/ssh/authsrvpasswd.c
@@ -0,0 +1,22 @@
+#include "ssh.h"
+
+static AuthInfo*
+authsrvpasswordfn(Conn *c, Msg *m)
+{
+ char *pass;
+ AuthInfo *ai;
+
+ pass = getstring(m);
+ ai = auth_userpasswd(c->user, pass);
+ free(m);
+ return ai;
+}
+
+Authsrv authsrvpassword =
+{
+ SSH_AUTH_PASSWORD,
+ "password",
+ SSH_CMSG_AUTH_PASSWORD,
+ authsrvpasswordfn,
+};
+
diff --git a/sys/src/cmd/ssh/authsrvtis.c b/sys/src/cmd/ssh/authsrvtis.c
new file mode 100755
index 000000000..5cd742f08
--- /dev/null
+++ b/sys/src/cmd/ssh/authsrvtis.c
@@ -0,0 +1,52 @@
+#include "ssh.h"
+
+static AuthInfo*
+authsrvtisfn(Conn *conn, Msg *m)
+{
+ char *s;
+ AuthInfo *ai;
+ Chalstate *c;
+
+ free(m);
+ if((c = auth_challenge("proto=p9cr user=%q role=server", conn->user)) == nil){
+ sshlog("auth_challenge failed for %s", conn->user);
+ return nil;
+ }
+ s = smprint("Challenge: %s\nResponse: ", c->chal);
+ if(s == nil){
+ auth_freechal(c);
+ return nil;
+ }
+ m = allocmsg(conn, SSH_SMSG_AUTH_TIS_CHALLENGE, 4+strlen(s));
+ putstring(m, s);
+ sendmsg(m);
+ free(s);
+
+ m = recvmsg(conn, 0);
+ if(m == nil){
+ auth_freechal(c);
+ return nil;
+ }
+ if(m->type != SSH_CMSG_AUTH_TIS_RESPONSE){
+ /*
+ * apparently you can just give up on
+ * this protocol and start a new one.
+ */
+ unrecvmsg(conn, m);
+ return nil;
+ }
+
+ c->resp = getstring(m);
+ c->nresp = strlen(c->resp);
+ ai = auth_response(c);
+ auth_freechal(c);
+ return ai;
+}
+
+Authsrv authsrvtis =
+{
+ SSH_AUTH_TIS,
+ "tis",
+ SSH_CMSG_AUTH_TIS,
+ authsrvtisfn,
+};
diff --git a/sys/src/cmd/ssh/authtis.c b/sys/src/cmd/ssh/authtis.c
new file mode 100755
index 000000000..f922e02af
--- /dev/null
+++ b/sys/src/cmd/ssh/authtis.c
@@ -0,0 +1,65 @@
+#include "ssh.h"
+
+static int
+authtisfn(Conn *c)
+{
+ int fd, n;
+ char *chal, resp[256];
+ Msg *m;
+
+ if(!c->interactive)
+ return -1;
+
+ debug(DBG_AUTH, "try TIS\n");
+ sendmsg(allocmsg(c, SSH_CMSG_AUTH_TIS, 0));
+
+ m = recvmsg(c, -1);
+ switch(m->type){
+ default:
+ badmsg(m, SSH_SMSG_AUTH_TIS_CHALLENGE);
+ case SSH_SMSG_FAILURE:
+ free(m);
+ return -1;
+ case SSH_SMSG_AUTH_TIS_CHALLENGE:
+ break;
+ }
+
+ chal = getstring(m);
+ free(m);
+
+ if((fd = open("/dev/cons", ORDWR)) < 0)
+ error("can't open console");
+
+ fprint(fd, "TIS Authentication\n%s", chal);
+ n = read(fd, resp, sizeof resp-1);
+ if(n < 0)
+ resp[0] = '\0';
+ else
+ resp[n] = '\0';
+
+ if(resp[0] == 0 || resp[0] == '\n')
+ return -1;
+
+ m = allocmsg(c, SSH_CMSG_AUTH_TIS_RESPONSE, 4+strlen(resp));
+ putstring(m, resp);
+ sendmsg(m);
+
+ m = recvmsg(c, -1);
+ switch(m->type){
+ default:
+ badmsg(m, 0);
+ case SSH_SMSG_SUCCESS:
+ free(m);
+ return 0;
+ case SSH_SMSG_FAILURE:
+ free(m);
+ return -1;
+ }
+}
+
+Auth authtis =
+{
+ SSH_AUTH_TIS,
+ "tis",
+ authtisfn,
+};
diff --git a/sys/src/cmd/ssh/cipher3des.c b/sys/src/cmd/ssh/cipher3des.c
new file mode 100755
index 000000000..b7b2b641c
--- /dev/null
+++ b/sys/src/cmd/ssh/cipher3des.c
@@ -0,0 +1,47 @@
+#include "ssh.h"
+
+struct CipherState
+{
+ DESstate enc3des[3];
+ DESstate dec3des[3];
+};
+
+static CipherState*
+init3des(Conn *c, int)
+{
+ int i;
+ CipherState *cs;
+
+ cs = emalloc(sizeof(CipherState));
+ for(i=0; i<3; i++){
+ setupDESstate(&cs->enc3des[i], c->sesskey+8*i, nil);
+ setupDESstate(&cs->dec3des[i], c->sesskey+8*i, nil);
+ }
+ return cs;
+}
+
+static void
+encrypt3des(CipherState *cs, uchar *buf, int nbuf)
+{
+ desCBCencrypt(buf, nbuf, &cs->enc3des[0]);
+ desCBCdecrypt(buf, nbuf, &cs->enc3des[1]);
+ desCBCencrypt(buf, nbuf, &cs->enc3des[2]);
+}
+
+static void
+decrypt3des(CipherState *cs, uchar *buf, int nbuf)
+{
+ desCBCdecrypt(buf, nbuf, &cs->dec3des[2]);
+ desCBCencrypt(buf, nbuf, &cs->dec3des[1]);
+ desCBCdecrypt(buf, nbuf, &cs->dec3des[0]);
+}
+
+Cipher cipher3des =
+{
+ SSH_CIPHER_3DES,
+ "3des",
+ init3des,
+ encrypt3des,
+ decrypt3des,
+};
+
diff --git a/sys/src/cmd/ssh/cipherblowfish.c b/sys/src/cmd/ssh/cipherblowfish.c
new file mode 100755
index 000000000..0659118d8
--- /dev/null
+++ b/sys/src/cmd/ssh/cipherblowfish.c
@@ -0,0 +1,40 @@
+#include "ssh.h"
+
+struct CipherState
+{
+ BFstate enc;
+ BFstate dec;
+};
+
+static CipherState*
+initblowfish(Conn *c, int)
+{
+ CipherState *cs;
+
+ cs = emalloc(sizeof(CipherState));
+ setupBFstate(&cs->enc, c->sesskey, SESSKEYLEN, nil);
+ setupBFstate(&cs->dec, c->sesskey, SESSKEYLEN, nil);
+ return cs;
+}
+
+static void
+encryptblowfish(CipherState *cs, uchar *buf, int nbuf)
+{
+ bfCBCencrypt(buf, nbuf, &cs->enc);
+}
+
+static void
+decryptblowfish(CipherState *cs, uchar *buf, int nbuf)
+{
+ bfCBCdecrypt(buf, nbuf, &cs->dec);
+}
+
+Cipher cipherblowfish =
+{
+ SSH_CIPHER_BLOWFISH,
+ "blowfish",
+ initblowfish,
+ encryptblowfish,
+ decryptblowfish,
+};
+
diff --git a/sys/src/cmd/ssh/cipherdes.c b/sys/src/cmd/ssh/cipherdes.c
new file mode 100755
index 000000000..193d08242
--- /dev/null
+++ b/sys/src/cmd/ssh/cipherdes.c
@@ -0,0 +1,40 @@
+#include "ssh.h"
+
+struct CipherState
+{
+ DESstate enc;
+ DESstate dec;
+};
+
+static CipherState*
+initdes(Conn *c, int)
+{
+ CipherState *cs;
+
+ cs = emalloc(sizeof(CipherState));
+ setupDESstate(&cs->enc, c->sesskey, nil);
+ setupDESstate(&cs->dec, c->sesskey, nil);
+ return cs;
+}
+
+static void
+encryptdes(CipherState *cs, uchar *buf, int nbuf)
+{
+ desCBCencrypt(buf, nbuf, &cs->enc);
+}
+
+static void
+decryptdes(CipherState *cs, uchar *buf, int nbuf)
+{
+ desCBCdecrypt(buf, nbuf, &cs->dec);
+}
+
+Cipher cipherdes =
+{
+ SSH_CIPHER_DES,
+ "des",
+ initdes,
+ encryptdes,
+ decryptdes,
+};
+
diff --git a/sys/src/cmd/ssh/ciphernone.c b/sys/src/cmd/ssh/ciphernone.c
new file mode 100755
index 000000000..a291e984e
--- /dev/null
+++ b/sys/src/cmd/ssh/ciphernone.c
@@ -0,0 +1,28 @@
+#include "ssh.h"
+
+static CipherState*
+initnone(Conn*, int)
+{
+ /* must be non-nil */
+ return (CipherState*)~0;
+}
+
+static void
+encryptnone(CipherState*, uchar*, int)
+{
+}
+
+static void
+decryptnone(CipherState*, uchar*, int)
+{
+}
+
+Cipher ciphernone =
+{
+ SSH_CIPHER_NONE,
+ "none",
+ initnone,
+ encryptnone,
+ decryptnone,
+};
+
diff --git a/sys/src/cmd/ssh/cipherrc4.c b/sys/src/cmd/ssh/cipherrc4.c
new file mode 100755
index 000000000..5b41f0d23
--- /dev/null
+++ b/sys/src/cmd/ssh/cipherrc4.c
@@ -0,0 +1,45 @@
+#include "ssh.h"
+
+struct CipherState
+{
+ RC4state enc;
+ RC4state dec;
+};
+
+static CipherState*
+initrc4(Conn *c, int isserver)
+{
+ CipherState *cs;
+
+ cs = emalloc(sizeof(CipherState));
+ if(isserver){
+ setupRC4state(&cs->enc, c->sesskey, 16);
+ setupRC4state(&cs->dec, c->sesskey+16, 16);
+ }else{
+ setupRC4state(&cs->dec, c->sesskey, 16);
+ setupRC4state(&cs->enc, c->sesskey+16, 16);
+ }
+ return cs;
+}
+
+static void
+encryptrc4(CipherState *cs, uchar *buf, int nbuf)
+{
+ rc4(&cs->enc, buf, nbuf);
+}
+
+static void
+decryptrc4(CipherState *cs, uchar *buf, int nbuf)
+{
+ rc4(&cs->dec, buf, nbuf);
+}
+
+Cipher cipherrc4 =
+{
+ SSH_CIPHER_RC4,
+ "rc4",
+ initrc4,
+ encryptrc4,
+ decryptrc4,
+};
+
diff --git a/sys/src/cmd/ssh/ciphertwiddle.c b/sys/src/cmd/ssh/ciphertwiddle.c
new file mode 100755
index 000000000..4b3088eb1
--- /dev/null
+++ b/sys/src/cmd/ssh/ciphertwiddle.c
@@ -0,0 +1,28 @@
+#include "ssh.h"
+
+static CipherState*
+inittwiddle(Conn *c, int)
+{
+ /* must be non-nil */
+ fprint(2, "twiddle key is %.*H\n", SESSKEYLEN, c->sesskey);
+ return (CipherState*)~0;
+}
+
+static void
+twiddle(CipherState*, uchar *buf, int n)
+{
+ int i;
+
+ for(i=0; i<n; i++)
+ buf[i] ^= 0xFF;
+}
+
+Cipher ciphertwiddle =
+{
+ SSH_CIPHER_TWIDDLE,
+ "twiddle",
+ inittwiddle,
+ twiddle,
+ twiddle,
+};
+
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);
+}
+
diff --git a/sys/src/cmd/ssh/mkfile b/sys/src/cmd/ssh/mkfile
new file mode 100755
index 000000000..6990cea28
--- /dev/null
+++ b/sys/src/cmd/ssh/mkfile
@@ -0,0 +1,78 @@
+</$objtype/mkfile
+
+HFILES=ssh.h
+
+TARG=\
+ scp\
+ ssh\
+ sshnet\
+ sshserve\
+
+AUTHOFILES=\
+ authpasswd.$O\
+ authrsa.$O\
+ authtis.$O\
+
+AUTHSRVOFILES=\
+ authsrvpasswd.$O\
+ authsrvtis.$O\
+
+CIPHEROFILES=\
+ cipher3des.$O\
+ cipherblowfish.$O\
+ cipherdes.$O\
+ ciphernone.$O\
+ cipherrc4.$O\
+ ciphertwiddle.$O\
+
+OFILES=\
+ msg.$O\
+ util.$O\
+
+BIN=/$objtype/bin
+
+UPDATE=\
+ mkfile\
+ agent.c\
+ cmsg.c\
+ smsg.c\
+ pubkey.c\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${AUTHOFILES:%.$O=%.c}\
+ ${AUTHSRVOFILES:%.$O=%.c}\
+ ${CIPHEROFILES:%.$O=%.c}\
+ ${TARG:%=%.c}\
+
+</sys/src/cmd/mkmany
+
+$O.ssh: \
+ $AUTHOFILES\
+ $CIPHEROFILES\
+ agent.$O\
+ cmsg.$O\
+ pubkey.$O\
+
+$O.sshserve: \
+ $AUTHSRVOFILES\
+ $CIPHEROFILES\
+ smsg.$O\
+
+$O.sshnet: \
+ $AUTHOFILES\
+ $CIPHEROFILES\
+ cmsg.$O\
+ pubkey.$O\
+
+$BIN/sshserve:VQ: $BIN/aux/sshserve
+ ;
+
+$BIN/aux/sshserve: $O.sshserve
+ cp $O.sshserve $BIN/aux/sshserve
+
+$BIN/aux/ssh_genkey: $O.ssh_genkey
+ cp $O.ssh_genkey $BIN/aux/ssh_genkey
+
+sshserve.safeinstall:
+ test -e $BIN/aux/sshserve && mv $BIN/aux/sshserve $BIN/aux/_sshserve
+ mk sshserve.install
diff --git a/sys/src/cmd/ssh/msg.c b/sys/src/cmd/ssh/msg.c
new file mode 100755
index 000000000..bdbc63cf5
--- /dev/null
+++ b/sys/src/cmd/ssh/msg.c
@@ -0,0 +1,512 @@
+#include "ssh.h"
+
+static ulong sum32(ulong, void*, int);
+
+char *msgnames[] =
+{
+/* 0 */
+ "SSH_MSG_NONE",
+ "SSH_MSG_DISCONNECT",
+ "SSH_SMSG_PUBLIC_KEY",
+ "SSH_CMSG_SESSION_KEY",
+ "SSH_CMSG_USER",
+ "SSH_CMSG_AUTH_RHOSTS",
+ "SSH_CMSG_AUTH_RSA",
+ "SSH_SMSG_AUTH_RSA_CHALLENGE",
+ "SSH_CMSG_AUTH_RSA_RESPONSE",
+ "SSH_CMSG_AUTH_PASSWORD",
+
+/* 10 */
+ "SSH_CMSG_REQUEST_PTY",
+ "SSH_CMSG_WINDOW_SIZE",
+ "SSH_CMSG_EXEC_SHELL",
+ "SSH_CMSG_EXEC_CMD",
+ "SSH_SMSG_SUCCESS",
+ "SSH_SMSG_FAILURE",
+ "SSH_CMSG_STDIN_DATA",
+ "SSH_SMSG_STDOUT_DATA",
+ "SSH_SMSG_STDERR_DATA",
+ "SSH_CMSG_EOF",
+
+/* 20 */
+ "SSH_SMSG_EXITSTATUS",
+ "SSH_MSG_CHANNEL_OPEN_CONFIRMATION",
+ "SSH_MSG_CHANNEL_OPEN_FAILURE",
+ "SSH_MSG_CHANNEL_DATA",
+ "SSH_MSG_CHANNEL_INPUT_EOF",
+ "SSH_MSG_CHANNEL_OUTPUT_CLOSED",
+ "SSH_MSG_UNIX_DOMAIN_X11_FORWARDING (obsolete)",
+ "SSH_SMSG_X11_OPEN",
+ "SSH_CMSG_PORT_FORWARD_REQUEST",
+ "SSH_MSG_PORT_OPEN",
+
+/* 30 */
+ "SSH_CMSG_AGENT_REQUEST_FORWARDING",
+ "SSH_SMSG_AGENT_OPEN",
+ "SSH_MSG_IGNORE",
+ "SSH_CMSG_EXIT_CONFIRMATION",
+ "SSH_CMSG_X11_REQUEST_FORWARDING",
+ "SSH_CMSG_AUTH_RHOSTS_RSA",
+ "SSH_MSG_DEBUG",
+ "SSH_CMSG_REQUEST_COMPRESSION",
+ "SSH_CMSG_MAX_PACKET_SIZE",
+ "SSH_CMSG_AUTH_TIS",
+
+/* 40 */
+ "SSH_SMSG_AUTH_TIS_CHALLENGE",
+ "SSH_CMSG_AUTH_TIS_RESPONSE",
+ "SSH_CMSG_AUTH_KERBEROS",
+ "SSH_SMSG_AUTH_KERBEROS_RESPONSE",
+ "SSH_CMSG_HAVE_KERBEROS_TGT"
+};
+
+void
+badmsg(Msg *m, int want)
+{
+ char *s, buf[20+ERRMAX];
+
+ if(m==nil){
+ snprint(buf, sizeof buf, "<early eof: %r>");
+ s = buf;
+ }else{
+ snprint(buf, sizeof buf, "<unknown type %d>", m->type);
+ s = buf;
+ if(0 <= m->type && m->type < nelem(msgnames))
+ s = msgnames[m->type];
+ }
+ if(want)
+ error("got %s message expecting %s", s, msgnames[want]);
+ error("got unexpected %s message", s);
+}
+
+Msg*
+allocmsg(Conn *c, int type, int len)
+{
+ uchar *p;
+ Msg *m;
+
+ if(len > 256*1024)
+ abort();
+
+ m = (Msg*)emalloc(sizeof(Msg)+4+8+1+len+4);
+ setmalloctag(m, getcallerpc(&c));
+ p = (uchar*)&m[1];
+ m->c = c;
+ m->bp = p;
+ m->ep = p+len;
+ m->wp = p;
+ m->type = type;
+ return m;
+}
+
+void
+unrecvmsg(Conn *c, Msg *m)
+{
+ debug(DBG_PROTO, "unreceived %s len %ld\n", msgnames[m->type], m->ep - m->rp);
+ free(c->unget);
+ c->unget = m;
+}
+
+static Msg*
+recvmsg0(Conn *c)
+{
+ int pad;
+ uchar *p, buf[4];
+ ulong crc, crc0, len;
+ Msg *m;
+
+ if(c->unget){
+ m = c->unget;
+ c->unget = nil;
+ return m;
+ }
+
+ if(readn(c->fd[0], buf, 4) != 4){
+ werrstr("short net read: %r");
+ return nil;
+ }
+
+ len = LONG(buf);
+ if(len > 256*1024){
+ werrstr("packet size far too big: %.8lux", len);
+ return nil;
+ }
+
+ pad = 8 - len%8;
+
+ m = (Msg*)emalloc(sizeof(Msg)+pad+len);
+ setmalloctag(m, getcallerpc(&c));
+ m->c = c;
+ m->bp = (uchar*)&m[1];
+ m->ep = m->bp + pad+len-4; /* -4: don't include crc */
+ m->rp = m->bp;
+
+ if(readn(c->fd[0], m->bp, pad+len) != pad+len){
+ werrstr("short net read: %r");
+ free(m);
+ return nil;
+ }
+
+ if(c->cipher)
+ c->cipher->decrypt(c->cstate, m->bp, len+pad);
+
+ crc = sum32(0, m->bp, pad+len-4);
+ p = m->bp + pad+len-4;
+ crc0 = LONG(p);
+ if(crc != crc0){
+ werrstr("bad crc %#lux != %#lux (packet length %lud)", crc, crc0, len);
+ free(m);
+ return nil;
+ }
+
+ m->rp += pad;
+ m->type = *m->rp++;
+
+ return m;
+}
+
+Msg*
+recvmsg(Conn *c, int type)
+{
+ Msg *m;
+
+ while((m = recvmsg0(c)) != nil){
+ debug(DBG_PROTO, "received %s len %ld\n", msgnames[m->type], m->ep - m->rp);
+ if(m->type != SSH_MSG_DEBUG && m->type != SSH_MSG_IGNORE)
+ break;
+ if(m->type == SSH_MSG_DEBUG)
+ debug(DBG_PROTO, "remote DEBUG: %s\n", getstring(m));
+ free(m);
+ }
+ if(type == 0){
+ /* no checking */
+ }else if(type == -1){
+ /* must not be nil */
+ if(m == nil)
+ error(Ehangup);
+ }else{
+ /* must be given type */
+ if(m==nil || m->type!=type)
+ badmsg(m, type);
+ }
+ setmalloctag(m, getcallerpc(&c));
+ return m;
+}
+
+int
+sendmsg(Msg *m)
+{
+ int i, pad;
+ uchar *p;
+ ulong datalen, len, crc;
+ Conn *c;
+
+ datalen = m->wp - m->bp;
+ len = datalen + 5;
+ pad = 8 - len%8;
+
+ debug(DBG_PROTO, "sending %s len %lud\n", msgnames[m->type], datalen);
+
+ p = m->bp;
+ memmove(m->bp+4+pad+1, m->bp, datalen); /* slide data to correct position */
+
+ PLONG(p, len);
+ p += 4;
+
+ if(m->c->cstate){
+ for(i=0; i<pad; i++)
+ *p++ = fastrand();
+ }else{
+ memset(p, 0, pad);
+ p += pad;
+ }
+
+ *p++ = m->type;
+
+ /* data already in position */
+ p += datalen;
+
+ crc = sum32(0, m->bp+4, pad+1+datalen);
+ PLONG(p, crc);
+ p += 4;
+
+ c = m->c;
+ qlock(c);
+ if(c->cstate)
+ c->cipher->encrypt(c->cstate, m->bp+4, len+pad);
+
+ if(write(c->fd[1], m->bp, p - m->bp) != p-m->bp){
+ qunlock(c);
+ free(m);
+ return -1;
+ }
+ qunlock(c);
+ free(m);
+ return 0;
+}
+
+uchar
+getbyte(Msg *m)
+{
+ if(m->rp >= m->ep)
+ error(Edecode);
+ return *m->rp++;
+}
+
+ushort
+getshort(Msg *m)
+{
+ ushort x;
+
+ if(m->rp+2 > m->ep)
+ error(Edecode);
+
+ x = SHORT(m->rp);
+ m->rp += 2;
+ return x;
+}
+
+ulong
+getlong(Msg *m)
+{
+ ulong x;
+
+ if(m->rp+4 > m->ep)
+ error(Edecode);
+
+ x = LONG(m->rp);
+ m->rp += 4;
+ return x;
+}
+
+char*
+getstring(Msg *m)
+{
+ char *p;
+ ulong len;
+
+ /* overwrites length to make room for NUL */
+ len = getlong(m);
+ if(m->rp+len > m->ep)
+ error(Edecode);
+ p = (char*)m->rp-1;
+ memmove(p, m->rp, len);
+ p[len] = '\0';
+ return p;
+}
+
+void*
+getbytes(Msg *m, int n)
+{
+ uchar *p;
+
+ if(m->rp+n > m->ep)
+ error(Edecode);
+ p = m->rp;
+ m->rp += n;
+ return p;
+}
+
+mpint*
+getmpint(Msg *m)
+{
+ int n;
+
+ n = (getshort(m)+7)/8; /* getshort returns # bits */
+ return betomp(getbytes(m, n), n, nil);
+}
+
+RSApub*
+getRSApub(Msg *m)
+{
+ RSApub *key;
+
+ getlong(m);
+ key = rsapuballoc();
+ if(key == nil)
+ error(Ememory);
+ key->ek = getmpint(m);
+ key->n = getmpint(m);
+ setmalloctag(key, getcallerpc(&m));
+ return key;
+}
+
+void
+putbyte(Msg *m, uchar x)
+{
+ if(m->wp >= m->ep)
+ error(Eencode);
+ *m->wp++ = x;
+}
+
+void
+putshort(Msg *m, ushort x)
+{
+ if(m->wp+2 > m->ep)
+ error(Eencode);
+ PSHORT(m->wp, x);
+ m->wp += 2;
+}
+
+void
+putlong(Msg *m, ulong x)
+{
+ if(m->wp+4 > m->ep)
+ error(Eencode);
+ PLONG(m->wp, x);
+ m->wp += 4;
+}
+
+void
+putstring(Msg *m, char *s)
+{
+ int len;
+
+ len = strlen(s);
+ putlong(m, len);
+ putbytes(m, s, len);
+}
+
+void
+putbytes(Msg *m, void *a, long n)
+{
+ if(m->wp+n > m->ep)
+ error(Eencode);
+ memmove(m->wp, a, n);
+ m->wp += n;
+}
+
+void
+putmpint(Msg *m, mpint *b)
+{
+ int bits, n;
+
+ bits = mpsignif(b);
+ putshort(m, bits);
+ n = (bits+7)/8;
+ if(m->wp+n > m->ep)
+ error(Eencode);
+ mptobe(b, m->wp, n, nil);
+ m->wp += n;
+}
+
+void
+putRSApub(Msg *m, RSApub *key)
+{
+ putlong(m, mpsignif(key->n));
+ putmpint(m, key->ek);
+ putmpint(m, key->n);
+}
+
+static ulong crctab[256];
+
+static void
+initsum32(void)
+{
+ ulong crc, poly;
+ int i, j;
+
+ poly = 0xEDB88320;
+ for(i = 0; i < 256; i++){
+ crc = i;
+ for(j = 0; j < 8; j++){
+ if(crc & 1)
+ crc = (crc >> 1) ^ poly;
+ else
+ crc >>= 1;
+ }
+ crctab[i] = crc;
+ }
+}
+
+static ulong
+sum32(ulong lcrc, void *buf, int n)
+{
+ static int first=1;
+ uchar *s = buf;
+ ulong crc = lcrc;
+
+ if(first){
+ first=0;
+ initsum32();
+ }
+ while(n-- > 0)
+ crc = crctab[(crc^*s++)&0xff] ^ (crc>>8);
+ return crc;
+}
+
+mpint*
+rsapad(mpint *b, int n)
+{
+ int i, pad, nbuf;
+ uchar buf[2560];
+ mpint *c;
+
+ if(n > sizeof buf)
+ error("buffer too small in rsapad");
+
+ nbuf = (mpsignif(b)+7)/8;
+ pad = n - nbuf;
+ assert(pad >= 3);
+ mptobe(b, buf, nbuf, nil);
+ memmove(buf+pad, buf, nbuf);
+
+ buf[0] = 0;
+ buf[1] = 2;
+ for(i=2; i<pad-1; i++)
+ buf[i]=1+fastrand()%255;
+ buf[pad-1] = 0;
+ c = betomp(buf, n, nil);
+ memset(buf, 0, sizeof buf);
+ return c;
+}
+
+mpint*
+rsaunpad(mpint *b)
+{
+ int i, n;
+ uchar buf[2560];
+
+ n = (mpsignif(b)+7)/8;
+ if(n > sizeof buf)
+ error("buffer too small in rsaunpad");
+ mptobe(b, buf, n, nil);
+
+ /* the initial zero has been eaten by the betomp -> mptobe sequence */
+ if(buf[0] != 2)
+ error("bad data in rsaunpad");
+ for(i=1; i<n; i++)
+ if(buf[i]==0)
+ break;
+ return betomp(buf+i, n-i, nil);
+}
+
+void
+mptoberjust(mpint *b, uchar *buf, int len)
+{
+ int n;
+
+ n = mptobe(b, buf, len, nil);
+ assert(n >= 0);
+ if(n < len){
+ len -= n;
+ memmove(buf+len, buf, n);
+ memset(buf, 0, len);
+ }
+}
+
+mpint*
+rsaencryptbuf(RSApub *key, uchar *buf, int nbuf)
+{
+ int n;
+ mpint *a, *b, *c;
+
+ n = (mpsignif(key->n)+7)/8;
+ a = betomp(buf, nbuf, nil);
+ b = rsapad(a, n);
+ mpfree(a);
+ c = rsaencrypt(key, b, nil);
+ mpfree(b);
+ return c;
+}
+
diff --git a/sys/src/cmd/ssh/pubkey.c b/sys/src/cmd/ssh/pubkey.c
new file mode 100755
index 000000000..0c170b843
--- /dev/null
+++ b/sys/src/cmd/ssh/pubkey.c
@@ -0,0 +1,227 @@
+#include "ssh.h"
+#include <bio.h>
+#include <ctype.h>
+
+static int
+parsepubkey(char *s, RSApub *key, char **sp, int base)
+{
+ int n;
+ char *host, *p, *z;
+
+ z = nil;
+ n = strtoul(s, &p, 10);
+ host = nil;
+ if(n < 256 || !isspace(*p)){ /* maybe this is a host name */
+ host = s;
+ s = strpbrk(s, " \t");
+ if(s == nil)
+ return -1;
+ z = s;
+ *s++ = '\0';
+ s += strspn(s, " \t");
+
+ n = strtoul(s, &p, 10);
+ if(n < 256 || !isspace(*p)){
+ if(z)
+ *z = ' ';
+ return -1;
+ }
+ }
+
+ if((key->ek = strtomp(p, &p, base, nil)) == nil
+ || (key->n = strtomp(p, &p, base, nil)) == nil
+ || (*p != '\0' && !isspace(*p))
+ || mpsignif(key->n) < 256){ /* 256 is just a sanity check */
+ mpfree(key->ek);
+ mpfree(key->n);
+ key->ek = nil;
+ key->n = nil;
+ if(z)
+ *z = ' ';
+ return -1;
+ }
+ if(host == nil){
+ if(*p != '\0'){
+ p += strspn(p, " \t");
+ if(*p != '\0'){
+ host = emalloc(strlen(p)+1);
+ strcpy(host, p);
+ }
+ }
+ free(s);
+ }
+ *sp = host;
+ return 0;
+}
+
+RSApub*
+readpublickey(Biobuf *b, char **sp)
+{
+ char *s;
+ RSApub *key;
+
+ key = rsapuballoc();
+ if(key == nil)
+ return nil;
+
+ for(;;){
+ if((s = Brdstr(b, '\n', 1)) == nil){
+ rsapubfree(key);
+ return nil;
+ }
+ if(s[0]=='#'){
+ free(s);
+ continue;
+ }
+ if(parsepubkey(s, key, sp, 10)==0
+ || parsepubkey(s, key, sp, 16)==0)
+ return key;
+ fprint(2, "warning: skipping line '%s'; cannot parse\n", s);
+ free(s);
+ }
+}
+
+static int
+match(char *pattern, char *aliases)
+{
+ char *s, *snext;
+ char *a, *anext, *ae;
+
+ for(s=pattern; s && *s; s=snext){
+ if((snext=strchr(s, ',')) != nil)
+ *snext++ = '\0';
+ for(a=aliases; a && *a; a=anext){
+ if((anext=strchr(a, ',')) != nil){
+ ae = anext;
+ anext++;
+ }else
+ ae = a+strlen(a);
+ if(ae-a == strlen(s) && memcmp(s, a, ae-a)==0)
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int
+findkey(char *keyfile, char *host, RSApub *key)
+{
+ char *h;
+ Biobuf *b;
+ RSApub *k;
+
+ if((b = Bopen(keyfile, OREAD)) == nil)
+ return NoKeyFile;
+
+ for(;;){
+ if((k = readpublickey(b, &h)) == nil){
+ Bterm(b);
+ return NoKey;
+ }
+ if(match(h, host) != 0){
+ free(h);
+ rsapubfree(k);
+ continue;
+ }
+ if(mpcmp(k->n, key->n) != 0 || mpcmp(k->ek, key->ek) != 0){
+ free(h);
+ rsapubfree(k);
+ Bterm(b);
+ return KeyWrong;
+ }
+ free(h);
+ rsapubfree(k);
+ Bterm(b);
+ return KeyOk;
+ }
+}
+
+int
+replacekey(char *keyfile, char *host, RSApub *hostkey)
+{
+ char *h, *nkey, *p;
+ Biobuf *br, *bw;
+ Dir *d, nd;
+ RSApub *k;
+
+ nkey = smprint("%s.new", keyfile);
+ if(nkey == nil)
+ return -1;
+
+ if((br = Bopen(keyfile, OREAD)) == nil){
+ free(nkey);
+ return -1;
+ }
+ if((bw = Bopen(nkey, OWRITE)) == nil){
+ Bterm(br);
+ free(nkey);
+ return -1;
+ }
+
+ while((k = readpublickey(br, &h)) != nil){
+ if(match(h, host) != 0){
+ Bprint(bw, "%s %d %.10B %.10B\n",
+ h, mpsignif(k->n), k->ek, k->n);
+ }
+ free(h);
+ rsapubfree(k);
+ }
+ Bprint(bw, "%s %d %.10B %.10B\n", host, mpsignif(hostkey->n), hostkey->ek, hostkey->n);
+ Bterm(bw);
+ Bterm(br);
+
+ d = dirstat(nkey);
+ if(d == nil){
+ fprint(2, "new key file disappeared?\n");
+ free(nkey);
+ return -1;
+ }
+
+ p = strrchr(d->name, '.');
+ if(p==nil || strcmp(p, ".new")!=0){
+ fprint(2, "new key file changed names? %s to %s\n", nkey, d->name);
+ free(d);
+ free(nkey);
+ return -1;
+ }
+
+ *p = '\0';
+ nulldir(&nd);
+ nd.name = d->name;
+ if(remove(keyfile) < 0){
+ fprint(2, "error removing %s: %r\n", keyfile);
+ free(d);
+ free(nkey);
+ return -1;
+ }
+ if(dirwstat(nkey, &nd) < 0){
+ fprint(2, "error renaming %s to %s: %r\n", nkey, d->name);
+ free(nkey);
+ free(d);
+ return -1;
+ }
+ free(d);
+ free(nkey);
+ return 0;
+}
+
+int
+appendkey(char *keyfile, char *host, RSApub *key)
+{
+ int fd;
+
+ if((fd = open(keyfile, OWRITE)) < 0){
+ fd = create(keyfile, OWRITE, 0666);
+ if(fd < 0){
+ fprint(2, "cannot open nor create %s: %r\n", keyfile);
+ return -1;
+ }
+ }
+ if(seek(fd, 0, 2) < 0
+ || fprint(fd, "%s %d %.10B %.10B\n", host, mpsignif(key->n), key->ek, key->n) < 0){
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
diff --git a/sys/src/cmd/ssh/scp.c b/sys/src/cmd/ssh/scp.c
new file mode 100755
index 000000000..bab66754a
--- /dev/null
+++ b/sys/src/cmd/ssh/scp.c
@@ -0,0 +1,799 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+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;
+}
+
+#define OK 0x00
+#define ERROR 0x01
+#define FATAL 0x02
+
+char *progname;
+
+int dflag;
+int fflag;
+int iflag;
+int pflag;
+int rflag;
+int tflag;
+int vflag;
+
+int remote;
+
+char *exitflag = nil;
+
+void scperror(int, char*, ...);
+void mustbedir(char*);
+void receive(char*);
+char *fileaftercolon(char*);
+void destislocal(char *cmd, int argc, char *argv[], char *dest);
+void destisremote(char *cmd, int argc, char *argv[], char *host, char *dest);
+int remotessh(char *host, char *cmd);
+void send(char*);
+void senddir(char*, int, Dir*);
+int getresponse(void);
+
+char theuser[32];
+
+char ssh[] = "/bin/ssh";
+
+int remotefd0;
+int remotefd1;
+
+int
+runcommand(char *cmd)
+{
+ Waitmsg *w;
+ int pid;
+ char *argv[4];
+
+ if (cmd == nil)
+ return -1;
+ switch(pid = fork()){
+ case -1:
+ return -1;
+ case 0:
+ argv[0] = "rc";
+ argv[1] = "-c";
+ argv[2] = cmd;
+ argv[3] = nil;
+ exec("/bin/rc", argv);
+ exits("exec failed");
+ }
+ for(;;){
+ w = wait();
+ if(w == nil)
+ return -1;
+ if(w->pid == pid)
+ break;
+ free(w);
+ }
+ if(w->msg[0]){
+ free(w);
+ return -1;
+ }
+ free(w);
+ return 1;
+}
+
+void
+vprint(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+ static char *name;
+
+ if(vflag == 0)
+ return;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+
+ if(name == nil){
+ name = sysname();
+ if(name == nil)
+ name = "<unknown>";
+ }
+ fprint(2, "%s: %s\n", name, buf);
+}
+
+void
+usage(void)
+{
+ fprint(2, "Usage: scp [-Iidfprtv] source ... destination\n");
+ exits("usage");
+}
+
+
+#pragma varargck type "F" int
+#pragma varargck type "V" char*
+static int flag;
+
+/* flag: if integer flag, take following char *value */
+int
+flagfmt(Fmt *f)
+{
+ flag = va_arg(f->args, int);
+ return 0;
+}
+
+/* flag: if previous integer flag, take char *value */
+int
+valfmt(Fmt *f)
+{
+ char *value;
+
+ value = va_arg(f->args, char*);
+ if(flag)
+ return fmtprint(f, " %s", value);
+ return 0;
+}
+
+void
+sendokresponse(void)
+{
+ char ok = OK;
+
+ write(remotefd1, &ok, 1);
+}
+
+void
+main(int argc, char *argv[])
+{
+ int i, fd;
+ char cmd[32];
+ char *p;
+
+ progname = argv[0];
+ fmtinstall('F', flagfmt);
+ fmtinstall('V', valfmt);
+ iflag = -1;
+
+ ARGBEGIN {
+ case 'I':
+ iflag = 0;
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'd':
+ dflag++;
+ break;
+ case 'f':
+ fflag++;
+ remote++;
+ break;
+ case 'p':
+ pflag++;
+ break;
+ case 'r':
+ rflag++;
+ break;
+ case 't':
+ tflag++;
+ remote++;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ scperror(1, "unknown option %c", ARGC());
+ } ARGEND
+
+ if(iflag == -1)
+ iflag = isatty(0);
+
+ remotefd0 = 0;
+ remotefd1 = 1;
+
+ if(fflag){
+ getresponse();
+ for(i=0; i<argc; i++)
+ send(argv[i]);
+ exits(0);
+ }
+ if(tflag){
+ if(argc != 1)
+ usage();
+ receive(argv[0]);
+ exits(0);
+ }
+
+ if (argc < 2)
+ usage();
+ if (argc > 2)
+ dflag = 1;
+
+ i = 0;
+ fd = open("/dev/user", OREAD);
+ if(fd >= 0){
+ i = read(fd, theuser, sizeof theuser - 1);
+ close(fd);
+ }
+ if(i <= 0)
+ scperror(1, "can't read /dev/user: %r");
+
+ remotefd0 = -1;
+ remotefd1 = -1;
+
+ snprint(cmd, sizeof cmd, "scp%F%V%F%V%F%V%F%V",
+ dflag, "-d",
+ pflag, "-p",
+ rflag, "-r",
+ vflag, "-v");
+
+ p = fileaftercolon(argv[argc-1]);
+ if(p != nil) /* send to remote machine. */
+ destisremote(cmd, argc-1, argv, argv[argc-1], p);
+ else{
+ if(dflag)
+ mustbedir(argv[argc-1]);
+ destislocal(cmd, argc-1, argv, argv[argc-1]);
+ }
+
+ exits(exitflag);
+}
+
+void
+destislocal(char *cmd, int argc, char *argv[], char *dst)
+{
+ int i;
+ char *src;
+ char buf[4096];
+
+ for(i = 0; i<argc; i++){
+ src = fileaftercolon(argv[i]);
+ if(src == nil){
+ /* local file; no network */
+ snprint(buf, sizeof buf, "exec cp%F%V%F%V %s %s",
+ rflag, "-r",
+ pflag, "-p",
+ argv[i], dst);
+ vprint("remotetolocal: %s", buf);
+ if(runcommand(buf) < 0)
+ exitflag = "local cp exec";
+ }else{
+ /* remote file; use network */
+ snprint(buf, sizeof buf, "%s -f %s", cmd, src);
+ if(remotessh(argv[i], buf) < 0)
+ exitflag = "remote ssh exec";
+ else{
+ receive(dst);
+ close(remotefd0);
+ remotefd0 = -1;
+ remotefd1 = -1;
+ }
+ }
+ }
+}
+
+void
+destisremote(char *cmd, int argc, char *argv[], char *host, char *dest)
+{
+ int i;
+ char *src;
+ char buf[4096];
+
+ for(i = 0; i < argc; i++){
+ vprint("remote destination: send %s to %s:%s", argv[i], host, dest);
+ /* destination is remote, but source may be local */
+ src = fileaftercolon(argv[i]);
+ if(src != nil){
+ /* remote to remote */
+ snprint(buf, sizeof buf, "exec %s%F%V%F%V %s %s %s '%s:%s'",
+ ssh,
+ iflag, " -i",
+ vflag, "-v",
+ argv[i], cmd, src,
+ host, dest);
+ vprint("localtoremote: %s", buf);
+ runcommand(buf);
+ }else{
+ /* local to remote */
+ if(remotefd0 == -1){
+ snprint(buf, sizeof buf, "%s -t %s", cmd, dest);
+ if(remotessh(host, buf) < 0)
+ exits("remotessh");
+ if(getresponse() < 0)
+ exits("bad response");
+ }
+ send(argv[i]);
+ }
+ }
+}
+
+void
+readhdr(char *p, int n)
+{
+ int i;
+
+ for(i=0; i<n; i++){
+ if(read(remotefd0, &p[i], 1) != 1)
+ break;
+ if(p[i] == '\n'){
+ p[i] = '\0';
+ return;
+ }
+ }
+ /* if at beginning, this is regular EOF */
+ if(i == 0)
+ exits(nil);
+ scperror(1, "read error on receive header: %r");
+}
+
+Dir *
+receivedir(char *dir, int exists, Dir *d, int settimes, ulong atime, ulong mtime, ulong mode)
+{
+ Dir nd;
+ int setmodes;
+ int fd;
+
+ setmodes = pflag;
+ if(exists){
+ if(!(d->qid.type & QTDIR)) {
+ scperror(0, "%s: protocol botch: directory requrest for non-directory", dir);
+ return d;
+ }
+ }else{
+ /* create it writeable; will fix later */
+ setmodes = 1;
+ fd = create(dir, OREAD, DMDIR|mode|0700);
+ if (fd < 0){
+ scperror(0, "%s: can't create: %r", dir);
+ return d;
+ }
+ d = dirfstat(fd);
+ close(fd);
+ if(d == nil){
+ scperror(0, "%s: can't stat: %r", dir);
+ return d;
+ }
+ }
+ receive(dir);
+ if(settimes || setmodes){
+ nulldir(&nd);
+ if(settimes){
+ nd.atime = atime;
+ nd.mtime = mtime;
+ d->atime = nd.atime;
+ d->mtime = nd.mtime;
+ }
+ if(setmodes){
+ nd.mode = DMDIR | (mode & 0777);
+ d->mode = nd.mode;
+ }
+ if(dirwstat(dir, &nd) < 0){
+ scperror(0, "can't wstat %s: %r", dir);
+ free(d);
+ return nil;
+ }
+ }
+ return d;
+}
+
+void
+receive(char *dest)
+{
+ int isdir, settimes, mode;
+ int exists, n, i, fd, m;
+ int errors;
+ ulong atime, mtime, size;
+ char buf[8192], *p;
+ char name[1024];
+ Dir *d;
+ Dir nd;
+
+ mtime = 0L;
+ atime = 0L;
+ settimes = 0;
+ isdir = 0;
+ if ((d = dirstat(dest)) && (d->qid.type & QTDIR)) {
+ isdir = 1;
+ }
+ if(dflag && !isdir)
+ scperror(1, "%s: not a directory: %r", dest);
+
+ sendokresponse();
+
+ for (;;) {
+ readhdr(buf, sizeof buf);
+
+ switch(buf[0]){
+ case ERROR:
+ case FATAL:
+ if(!remote)
+ fprint(2, "%s\n", buf+1);
+ exitflag = "bad receive";
+ if(buf[0] == FATAL)
+ exits(exitflag);
+ continue;
+
+ case 'E':
+ sendokresponse();
+ return;
+
+ case 'T':
+ settimes = 1;
+ p = buf + 1;
+ mtime = strtol(p, &p, 10);
+ if(*p++ != ' '){
+ Badtime:
+ scperror(1, "bad time format: %s", buf+1);
+ }
+ strtol(p, &p, 10);
+ if(*p++ != ' ')
+ goto Badtime;
+ atime = strtol(p, &p, 10);
+ if(*p++ != ' ')
+ goto Badtime;
+ strtol(p, &p, 10);
+ if(*p++ != 0)
+ goto Badtime;
+
+ sendokresponse();
+ continue;
+
+ case 'D':
+ case 'C':
+ p = buf + 1;
+ mode = strtol(p, &p, 8);
+ if (*p++ != ' '){
+ Badmode:
+ scperror(1, "bad mode/size format: %s", buf+1);
+ }
+ size = strtoll(p, &p, 10);
+ if(*p++ != ' ')
+ goto Badmode;
+
+ if(isdir){
+ if(dest[0] == '\0')
+ snprint(name, sizeof name, "%s", p);
+ else
+ snprint(name, sizeof name, "%s/%s", dest, p);
+ }else
+ snprint(name, sizeof name, "%s", dest);
+ if(strlen(name) > sizeof name-UTFmax)
+ scperror(1, "file name too long: %s", dest);
+
+ exists = 1;
+ free(d);
+ if((d = dirstat(name)) == nil)
+ exists = 0;
+
+ if(buf[0] == 'D'){
+ vprint("receive directory %s", name);
+ d = receivedir(name, exists, d, settimes, atime, mtime, mode);
+ settimes = 0;
+ continue;
+ }
+
+ vprint("receive file %s by %s", name, getuser());
+ fd = create(name, OWRITE, mode);
+ if(fd < 0){
+ scperror(0, "can't create %s: %r", name);
+ continue;
+ }
+ sendokresponse();
+
+ /*
+ * Committed to receive size bytes
+ */
+ errors = 0;
+ for(i = 0; i < size; i += m){
+ n = sizeof buf;
+ if(n > size - i)
+ n = size - i;
+ m = readn(remotefd0, buf, n);
+ if(m <= 0)
+ scperror(1, "read error on connection: %r");
+ if(errors == 0){
+ n = write(fd, buf, m);
+ if(n != m)
+ errors = 1;
+ }
+ }
+
+ /* if file exists, modes could be wrong */
+ if(errors)
+ scperror(0, "%s: write error: %r", name);
+ else if(settimes || (exists && (d->mode&0777) != (mode&0777))){
+ nulldir(&nd);
+ if(settimes){
+ settimes = 0;
+ nd.atime = atime;
+ nd.mtime = mtime;
+ }
+ if(exists && (d->mode&0777) != (mode&0777))
+ nd.mode = (d->mode & ~0777) | (mode&0777);
+ if(dirwstat(name, &nd) < 0)
+ scperror(0, "can't wstat %s: %r", name);
+ }
+ free(d);
+ d = nil;
+ close(fd);
+ getresponse();
+ if(errors)
+ exits("write error");
+ sendokresponse();
+ break;
+
+ default:
+ scperror(0, "unrecognized header type char %c", buf[0]);
+ scperror(1, "input line: %s", buf);
+ }
+ }
+}
+
+/*
+ * Lastelem is called when we have a Dir with the final element, but if the file
+ * has been bound, we want the original name that was used rather than
+ * the contents of the stat buffer, so do this lexically.
+ */
+char*
+lastelem(char *file)
+{
+ char *elem;
+
+ elem = strrchr(file, '/');
+ if(elem == nil)
+ return file;
+ return elem+1;
+}
+
+void
+send(char *file)
+{
+ Dir *d;
+ ulong i;
+ int m, n, fd;
+ char buf[8192];
+
+ if((fd = open(file, OREAD)) < 0){
+ scperror(0, "can't open %s: %r", file);
+ return;
+ }
+ if((d = dirfstat(fd)) == nil){
+ scperror(0, "can't fstat %s: %r", file);
+ goto Return;
+ }
+
+ if(d->qid.type & QTDIR){
+ if(rflag)
+ senddir(file, fd, d);
+ else
+ scperror(0, "%s: is a directory", file);
+ goto Return;
+ }
+
+ if(pflag){
+ fprint(remotefd1, "T%lud 0 %lud 0\n", d->mtime, d->atime);
+ if(getresponse() < 0)
+ goto Return;
+ }
+
+ fprint(remotefd1, "C%.4luo %lld %s\n", d->mode&0777, d->length, lastelem(file));
+ if(getresponse() < 0)
+ goto Return;
+
+ /*
+ * We are now committed to send d.length bytes, regardless
+ */
+ for(i=0; i<d->length; i+=m){
+ n = sizeof buf;
+ if(n > d->length - i)
+ n = d->length - i;
+ m = readn(fd, buf, n);
+ if(m <= 0)
+ break;
+ write(remotefd1, buf, m);
+ }
+
+ if(i == d->length)
+ sendokresponse();
+ else{
+ /* continue to send gibberish up to d.length */
+ for(; i<d->length; i+=n){
+ n = sizeof buf;
+ if(n > d->length - i)
+ n = d->length - i;
+ write(remotefd1, buf, n);
+ }
+ scperror(0, "%s: %r", file);
+ }
+
+ getresponse();
+
+ Return:
+ free(d);
+ close(fd);
+}
+
+int
+getresponse(void)
+{
+ uchar first, byte, buf[256];
+ int i;
+
+ if (read(remotefd0, &first, 1) != 1)
+ scperror(1, "lost connection");
+
+ if(first == 0)
+ return 0;
+
+ i = 0;
+ if(first > FATAL){
+ fprint(2, "scp: unexpected response character 0x%.2ux\n", first);
+ buf[i++] = first;
+ }
+
+ /* read error message up to newline */
+ for(;;){
+ if(read(remotefd0, &byte, 1) != 1)
+ scperror(1, "response: dropped connection");
+ if(byte == '\n')
+ break;
+ if(i < sizeof buf)
+ buf[i++] = byte;
+ }
+
+ exitflag = "bad response";
+ if(!remote)
+ fprint(2, "%.*s\n", utfnlen((char*)buf, i), (char*)buf);
+
+ if (first == ERROR)
+ return -1;
+ exits(exitflag);
+ return 0; /* not reached */
+}
+
+void
+senddir(char *name, int fd, Dir *dirp)
+{
+ Dir *d, *dir;
+ int n;
+ char file[256];
+
+ if(pflag){
+ fprint(remotefd1, "T%lud 0 %lud 0\n", dirp->mtime, dirp->atime);
+ if(getresponse() < 0)
+ return;
+ }
+
+ vprint("directory %s mode: D%.4lo %d %.1024s", name, dirp->mode&0777, 0, lastelem(name));
+
+ fprint(remotefd1, "D%.4lo %d %.1024s\n", dirp->mode&0777, 0, dirp->name);
+ if(getresponse() < 0)
+ return;
+
+ n = dirreadall(fd, &dir);
+ for(d = dir; d < &dir[n]; d++){
+ /* shouldn't happen with plan 9, but worth checking anyway */
+ if(strcmp(d->name, ".")==0 || strcmp(d->name, "..")==0)
+ continue;
+ if(snprint(file, sizeof file, "%s/%s", name, d->name) > sizeof file-UTFmax){
+ scperror(0, "%.20s.../%s: name too long; skipping file", file, d->name);
+ continue;
+ }
+ send(file);
+ }
+ free(dir);
+ fprint(remotefd1, "E\n");
+ getresponse();
+}
+
+int
+remotessh(char *host, char *cmd)
+{
+ int i, p[2];
+ char *arg[32];
+
+ vprint("remotessh: %s: %s", host, cmd);
+
+ if(pipe(p) < 0)
+ scperror(1, "pipe: %r");
+
+ switch(fork()){
+ case -1:
+ scperror(1, "fork: %r");
+
+ case 0:
+ /* child */
+ close(p[0]);
+ dup(p[1], 0);
+ dup(p[1], 1);
+ for (i = 3; i < 100; i++)
+ close(i);
+
+ i = 0;
+ arg[i++] = ssh;
+ arg[i++] = "-x";
+ arg[i++] = "-a";
+ arg[i++] = "-m";
+ if(iflag)
+ arg[i++] = "-i";
+ if(vflag)
+ arg[i++] = "-v";
+ arg[i++] = host;
+ arg[i++] = cmd;
+ arg[i] = nil;
+
+ exec(ssh, arg);
+ exits("exec failed");
+
+ default:
+ /* parent */
+ close(p[1]);
+ remotefd0 = p[0];
+ remotefd1 = p[0];
+ }
+ return 0;
+}
+
+void
+scperror(int exit, char *fmt, ...)
+{
+ char buf[2048];
+ va_list arg;
+
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+
+ fprint(remotefd1, "%cscp: %s\n", ERROR, buf);
+
+ if (!remote)
+ fprint(2, "scp: %s\n", buf);
+ exitflag = buf;
+ if(exit)
+ exits(exitflag);
+}
+
+char *
+fileaftercolon(char *file)
+{
+ char *c, *s;
+
+ c = utfrune(file, ':');
+ if(c == nil)
+ return nil;
+
+ /* colon must be in middle of name to be a separator */
+ if(c == file)
+ return nil;
+
+ /* does slash occur before colon? */
+ s = utfrune(file, '/');
+ if(s != nil && s < c)
+ return nil;
+
+ *c++ = '\0';
+ if(*c == '\0')
+ return ".";
+ return c;
+}
+
+void
+mustbedir(char *file)
+{
+ Dir *d;
+
+ if((d = dirstat(file)) == nil){
+ scperror(1, "%s: %r", file);
+ return;
+ }
+ if(!(d->qid.type & QTDIR))
+ scperror(1, "%s: Not a directory", file);
+ free(d);
+}
diff --git a/sys/src/cmd/ssh/smsg.c b/sys/src/cmd/ssh/smsg.c
new file mode 100755
index 000000000..ec42ae209
--- /dev/null
+++ b/sys/src/cmd/ssh/smsg.c
@@ -0,0 +1,285 @@
+#include "ssh.h"
+#include <bio.h>
+
+static void
+send_ssh_smsg_public_key(Conn *c)
+{
+ int i;
+ Msg *m;
+
+ m = allocmsg(c, SSH_SMSG_PUBLIC_KEY, 2048);
+ putbytes(m, c->cookie, COOKIELEN);
+ putRSApub(m, c->serverkey);
+ putRSApub(m, c->hostkey);
+ putlong(m, c->flags);
+
+ for(i=0; i<c->nokcipher; i++)
+ c->ciphermask |= 1<<c->okcipher[i]->id;
+ putlong(m, c->ciphermask);
+ for(i=0; i<c->nokauthsrv; i++)
+ c->authmask |= 1<<c->okauthsrv[i]->id;
+ putlong(m, c->authmask);
+
+ sendmsg(m);
+}
+
+static mpint*
+rpcdecrypt(AuthRpc *rpc, mpint *b)
+{
+ mpint *a;
+ char *p;
+
+ p = mptoa(b, 16, nil, 0);
+ if(auth_rpc(rpc, "write", p, strlen(p)) != ARok)
+ sysfatal("factotum rsa write: %r");
+ free(p);
+ if(auth_rpc(rpc, "read", nil, 0) != ARok)
+ sysfatal("factotum rsa read: %r");
+ a = strtomp(rpc->arg, nil, 16, nil);
+ mpfree(b);
+ return a;
+}
+
+static void
+recv_ssh_cmsg_session_key(Conn *c, AuthRpc *rpc)
+{
+ int i, id, n, serverkeylen, hostkeylen;
+ mpint *a, *b;
+ uchar *buf;
+ Msg *m;
+ RSApriv *ksmall, *kbig;
+
+ m = recvmsg(c, SSH_CMSG_SESSION_KEY);
+ id = getbyte(m);
+ c->cipher = nil;
+ for(i=0; i<c->nokcipher; i++)
+ if(c->okcipher[i]->id == id)
+ c->cipher = c->okcipher[i];
+ if(c->cipher == nil)
+ sysfatal("invalid cipher selected");
+
+ if(memcmp(getbytes(m, COOKIELEN), c->cookie, COOKIELEN) != 0)
+ sysfatal("bad cookie");
+
+ serverkeylen = mpsignif(c->serverkey->n);
+ hostkeylen = mpsignif(c->hostkey->n);
+ ksmall = kbig = nil;
+ if(serverkeylen+128 <= hostkeylen){
+ ksmall = c->serverpriv;
+ kbig = nil;
+ }else if(hostkeylen+128 <= serverkeylen){
+ ksmall = nil;
+ kbig = c->serverpriv;
+ }else
+ sysfatal("server session and host keys do not differ by at least 128 bits");
+
+ b = getmpint(m);
+
+ debug(DBG_CRYPTO, "encrypted with kbig is %B\n", b);
+ if(kbig){
+ a = rsadecrypt(kbig, b, nil);
+ mpfree(b);
+ b = a;
+ }else
+ b = rpcdecrypt(rpc, b);
+ a = rsaunpad(b);
+ mpfree(b);
+ b = a;
+
+ debug(DBG_CRYPTO, "encrypted with ksmall is %B\n", b);
+ if(ksmall){
+ a = rsadecrypt(ksmall, b, nil);
+ mpfree(b);
+ b = a;
+ }else
+ b = rpcdecrypt(rpc, b);
+ a = rsaunpad(b);
+ mpfree(b);
+ b = a;
+
+ debug(DBG_CRYPTO, "munged is %B\n", b);
+
+ n = (mpsignif(b)+7)/8;
+ if(n > SESSKEYLEN)
+ sysfatal("client sent short session key");
+
+ buf = emalloc(SESSKEYLEN);
+ mptoberjust(b, buf, SESSKEYLEN);
+ mpfree(b);
+
+ for(i=0; i<SESSIDLEN; i++)
+ buf[i] ^= c->sessid[i];
+
+ memmove(c->sesskey, buf, SESSKEYLEN);
+
+ debug(DBG_CRYPTO, "unmunged is %.*H\n", SESSKEYLEN, buf);
+
+ c->flags = getlong(m);
+ free(m);
+}
+
+static AuthInfo*
+responselogin(char *user, char *resp)
+{
+ Chalstate *c;
+ AuthInfo *ai;
+
+ if((c = auth_challenge("proto=p9cr user=%q role=server", user)) == nil){
+ sshlog("auth_challenge failed for %s", user);
+ return nil;
+ }
+ c->resp = resp;
+ c->nresp = strlen(resp);
+ ai = auth_response(c);
+ auth_freechal(c);
+ return ai;
+}
+
+static AuthInfo*
+authusername(Conn *c)
+{
+ char *p;
+ AuthInfo *ai;
+
+ /*
+ * hack for sam users: 'name numbers' gets tried as securid login.
+ */
+ if(p = strchr(c->user, ' ')){
+ *p++ = '\0';
+ if((ai=responselogin(c->user, p)) != nil)
+ return ai;
+ *--p = ' ';
+ sshlog("bad response: %s", c->user);
+ }
+ return nil;
+}
+
+static void
+authsrvuser(Conn *c)
+{
+ int i;
+ char *ns, *user;
+ AuthInfo *ai;
+ Msg *m;
+
+ m = recvmsg(c, SSH_CMSG_USER);
+ user = getstring(m);
+ c->user = emalloc(strlen(user)+1);
+ strcpy(c->user, user);
+ free(m);
+
+ ai = authusername(c);
+ while(ai == nil){
+ /*
+ * clumsy: if the client aborted the auth_tis early
+ * we don't send a new failure. we check this by
+ * looking at c->unget, which is only used in that
+ * case.
+ */
+ if(c->unget != nil)
+ goto skipfailure;
+ sendmsg(allocmsg(c, SSH_SMSG_FAILURE, 0));
+ skipfailure:
+ m = recvmsg(c, -1);
+ for(i=0; i<c->nokauthsrv; i++)
+ if(c->okauthsrv[i]->firstmsg == m->type){
+ ai = (*c->okauthsrv[i]->fn)(c, m);
+ break;
+ }
+ if(i==c->nokauthsrv)
+ badmsg(m, 0);
+ }
+ sendmsg(allocmsg(c, SSH_SMSG_SUCCESS, 0));
+
+ if(noworld(ai->cuid))
+ ns = "/lib/namespace.noworld";
+ else
+ ns = nil;
+ if(auth_chuid(ai, ns) < 0){
+ sshlog("auth_chuid to %s: %r", ai->cuid);
+ sysfatal("auth_chuid: %r");
+ }
+ sshlog("logged in as %s", ai->cuid);
+ auth_freeAI(ai);
+}
+
+void
+sshserverhandshake(Conn *c)
+{
+ char *p, buf[128];
+ Biobuf *b;
+ Attr *a;
+ int i, afd;
+ mpint *m;
+ AuthRpc *rpc;
+ RSApub *key;
+
+ /*
+ * BUG: should use `attr' to get the key attributes
+ * after the read, but that's not implemented yet.
+ */
+ if((b = Bopen("/mnt/factotum/ctl", OREAD)) == nil)
+ sysfatal("open /mnt/factotum/ctl: %r");
+ while((p = Brdline(b, '\n')) != nil){
+ p[Blinelen(b)-1] = '\0';
+ if(strstr(p, " proto=rsa ") && strstr(p, " service=sshserve "))
+ break;
+ }
+ if(p == nil)
+ sysfatal("no sshserve keys found in /mnt/factotum/ctl");
+ a = _parseattr(p);
+ Bterm(b);
+ key = emalloc(sizeof(*key));
+ if((p = _strfindattr(a, "n")) == nil)
+ sysfatal("no n in sshserve key");
+ if((key->n = strtomp(p, &p, 16, nil)) == nil || *p != 0)
+ sysfatal("bad n in sshserve key");
+ if((p = _strfindattr(a, "ek")) == nil)
+ sysfatal("no ek in sshserve key");
+ if((key->ek = strtomp(p, &p, 16, nil)) == nil || *p != 0)
+ sysfatal("bad ek in sshserve key");
+ _freeattr(a);
+
+ if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0)
+ sysfatal("open /mnt/factotum/rpc: %r");
+ if((rpc = auth_allocrpc(afd)) == nil)
+ sysfatal("auth_allocrpc: %r");
+ p = "proto=rsa role=client service=sshserve";
+ if(auth_rpc(rpc, "start", p, strlen(p)) != ARok)
+ sysfatal("auth_rpc start %s: %r", p);
+ if(auth_rpc(rpc, "read", nil, 0) != ARok)
+ sysfatal("auth_rpc read: %r");
+ m = strtomp(rpc->arg, nil, 16, nil);
+ if(mpcmp(m, key->n) != 0)
+ sysfatal("key in /mnt/factotum/ctl does not match rpc key");
+ mpfree(m);
+ c->hostkey = key;
+
+ /* send id string */
+ fprint(c->fd[0], "SSH-1.5-Plan9\n");
+
+ /* receive id string */
+ if(readstrnl(c->fd[0], buf, sizeof buf) < 0)
+ sysfatal("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 != '-')
+ sysfatal("protocol mismatch; got %s, need SSH-1.x for x>=5", buf);
+
+ for(i=0; i<COOKIELEN; i++)
+ c->cookie[i] = fastrand();
+ calcsessid(c);
+ send_ssh_smsg_public_key(c);
+ recv_ssh_cmsg_session_key(c, rpc);
+ auth_freerpc(rpc);
+ close(afd);
+
+ c->cstate = (*c->cipher->init)(c, 1); /* turns on encryption */
+ sendmsg(allocmsg(c, SSH_SMSG_SUCCESS, 0));
+
+ authsrvuser(c);
+}
diff --git a/sys/src/cmd/ssh/ssh.c b/sys/src/cmd/ssh/ssh.c
new file mode 100755
index 000000000..83a2751e6
--- /dev/null
+++ b/sys/src/cmd/ssh/ssh.c
@@ -0,0 +1,592 @@
+#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);
+}
diff --git a/sys/src/cmd/ssh/ssh.h b/sys/src/cmd/ssh/ssh.h
new file mode 100755
index 000000000..1204806f7
--- /dev/null
+++ b/sys/src/cmd/ssh/ssh.h
@@ -0,0 +1,303 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <auth.h>
+#include <libsec.h>
+
+enum /* internal debugging flags */
+{
+ DBG= 1<<0,
+ DBG_CRYPTO= 1<<1,
+ DBG_PACKET= 1<<2,
+ DBG_AUTH= 1<<3,
+ DBG_PROC= 1<<4,
+ DBG_PROTO= 1<<5,
+ DBG_IO= 1<<6,
+ DBG_SCP= 1<<7,
+};
+
+enum /* protocol packet types */
+{
+/* 0 */
+ SSH_MSG_NONE=0,
+ SSH_MSG_DISCONNECT,
+ SSH_SMSG_PUBLIC_KEY,
+ SSH_CMSG_SESSION_KEY,
+ SSH_CMSG_USER,
+ SSH_CMSG_AUTH_RHOSTS,
+ SSH_CMSG_AUTH_RSA,
+ SSH_SMSG_AUTH_RSA_CHALLENGE,
+ SSH_CMSG_AUTH_RSA_RESPONSE,
+ SSH_CMSG_AUTH_PASSWORD,
+
+/* 10 */
+ SSH_CMSG_REQUEST_PTY,
+ SSH_CMSG_WINDOW_SIZE,
+ SSH_CMSG_EXEC_SHELL,
+ SSH_CMSG_EXEC_CMD,
+ SSH_SMSG_SUCCESS,
+ SSH_SMSG_FAILURE,
+ SSH_CMSG_STDIN_DATA,
+ SSH_SMSG_STDOUT_DATA,
+ SSH_SMSG_STDERR_DATA,
+ SSH_CMSG_EOF,
+
+/* 20 */
+ SSH_SMSG_EXITSTATUS,
+ SSH_MSG_CHANNEL_OPEN_CONFIRMATION,
+ SSH_MSG_CHANNEL_OPEN_FAILURE,
+ SSH_MSG_CHANNEL_DATA,
+ SSH_MSG_CHANNEL_INPUT_EOF,
+ SSH_MSG_CHANNEL_OUTPUT_CLOSED,
+ SSH_MSG_UNIX_DOMAIN_X11_FORWARDING, /* obsolete */
+ SSH_SMSG_X11_OPEN,
+ SSH_CMSG_PORT_FORWARD_REQUEST,
+ SSH_MSG_PORT_OPEN,
+
+/* 30 */
+ SSH_CMSG_AGENT_REQUEST_FORWARDING,
+ SSH_SMSG_AGENT_OPEN,
+ SSH_MSG_IGNORE,
+ SSH_CMSG_EXIT_CONFIRMATION,
+ SSH_CMSG_X11_REQUEST_FORWARDING,
+ SSH_CMSG_AUTH_RHOSTS_RSA,
+ SSH_MSG_DEBUG,
+ SSH_CMSG_REQUEST_COMPRESSION,
+ SSH_CMSG_MAX_PACKET_SIZE,
+ SSH_CMSG_AUTH_TIS,
+
+/* 40 */
+ SSH_SMSG_AUTH_TIS_CHALLENGE,
+ SSH_CMSG_AUTH_TIS_RESPONSE,
+ SSH_CMSG_AUTH_KERBEROS,
+ SSH_SMSG_AUTH_KERBEROS_RESPONSE,
+ SSH_CMSG_HAVE_KERBEROS_TGT,
+};
+
+enum /* protocol flags */
+{
+ SSH_PROTOFLAG_SCREEN_NUMBER=1<<0,
+ SSH_PROTOFLAG_HOST_IN_FWD_OPEN=1<<1,
+};
+
+enum /* agent protocol packet types */
+{
+ SSH_AGENTC_NONE = 0,
+ SSH_AGENTC_REQUEST_RSA_IDENTITIES,
+ SSH_AGENT_RSA_IDENTITIES_ANSWER,
+ SSH_AGENTC_RSA_CHALLENGE,
+ SSH_AGENT_RSA_RESPONSE,
+ SSH_AGENT_FAILURE,
+ SSH_AGENT_SUCCESS,
+ SSH_AGENTC_ADD_RSA_IDENTITY,
+ SSH_AGENTC_REMOVE_RSA_IDENTITY,
+};
+
+enum /* protocol constants */
+{
+ SSH_MAX_DATA = 256*1024,
+ SSH_MAX_MSG = SSH_MAX_DATA+4,
+
+ SESSKEYLEN = 32,
+ SESSIDLEN = 16,
+
+ COOKIELEN = 8,
+};
+
+enum /* crypto ids */
+{
+ SSH_CIPHER_NONE = 0,
+ SSH_CIPHER_IDEA,
+ SSH_CIPHER_DES,
+ SSH_CIPHER_3DES,
+ SSH_CIPHER_TSS,
+ SSH_CIPHER_RC4,
+ SSH_CIPHER_BLOWFISH,
+ SSH_CIPHER_TWIDDLE, /* for debugging */
+};
+
+enum /* auth method ids */
+{
+ SSH_AUTH_RHOSTS = 1,
+ SSH_AUTH_RSA = 2,
+ SSH_AUTH_PASSWORD = 3,
+ SSH_AUTH_RHOSTS_RSA = 4,
+ SSH_AUTH_TIS = 5,
+ SSH_AUTH_USER_RSA = 6,
+};
+
+typedef struct Auth Auth;
+typedef struct Authsrv Authsrv;
+typedef struct Cipher Cipher;
+typedef struct CipherState CipherState;
+typedef struct Conn Conn;
+typedef struct Msg Msg;
+
+#pragma incomplete CipherState
+
+struct Auth
+{
+ int id;
+ char *name;
+ int (*fn)(Conn*);
+};
+
+struct Authsrv
+{
+ int id;
+ char *name;
+ int firstmsg;
+ AuthInfo *(*fn)(Conn*, Msg*);
+};
+
+struct Cipher
+{
+ int id;
+ char *name;
+ CipherState *(*init)(Conn*, int isserver);
+ void (*encrypt)(CipherState*, uchar*, int);
+ void (*decrypt)(CipherState*, uchar*, int);
+};
+
+struct Conn
+{
+ QLock;
+ int fd[2];
+ CipherState *cstate;
+ uchar cookie[COOKIELEN];
+ uchar sessid[SESSIDLEN];
+ uchar sesskey[SESSKEYLEN];
+ RSApub *serverkey;
+ RSApub *hostkey;
+ ulong flags;
+ ulong ciphermask;
+ Cipher *cipher; /* chosen cipher */
+ Cipher **okcipher; /* list of acceptable ciphers */
+ int nokcipher;
+ ulong authmask;
+ Auth **okauth;
+ int nokauth;
+ char *user;
+ char *host;
+ char *aliases;
+ int interactive;
+ Msg *unget;
+
+ RSApriv *serverpriv; /* server only */
+ RSApriv *hostpriv;
+ Authsrv **okauthsrv;
+ int nokauthsrv;
+};
+
+struct Msg
+{
+ Conn *c;
+ uchar type;
+ ulong len; /* output: #bytes before pos, input: #bytes after pos */
+ uchar *bp; /* beginning of allocated space */
+ uchar *rp; /* read pointer */
+ uchar *wp; /* write pointer */
+ uchar *ep; /* end of allocated space */
+ Msg *link; /* for sshnet */
+};
+
+#define LONG(p) (((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|((p)[3]))
+#define PLONG(p, l) \
+ (((p)[0]=(l)>>24),((p)[1]=(l)>>16),\
+ ((p)[2]=(l)>>8),((p)[3]=(l)))
+#define SHORT(p) (((p)[0]<<8)|(p)[1])
+#define PSHORT(p,l) \
+ (((p)[0]=(l)>>8),((p)[1]=(l)))
+
+extern char Edecode[];
+extern char Eencode[];
+extern char Ememory[];
+extern char Ehangup[];
+extern int doabort;
+extern int debuglevel;
+
+extern Auth authpassword;
+extern Auth authrsa;
+extern Auth authtis;
+
+extern Authsrv authsrvpassword;
+extern Authsrv authsrvtis;
+
+extern Cipher cipher3des;
+extern Cipher cipherblowfish;
+extern Cipher cipherdes;
+extern Cipher cipherrc4;
+extern Cipher ciphernone;
+extern Cipher ciphertwiddle;
+
+/* msg.c */
+Msg* allocmsg(Conn*, int, int);
+void badmsg(Msg*, int);
+Msg* recvmsg(Conn*, int);
+void unrecvmsg(Conn*, Msg*);
+int sendmsg(Msg*);
+uchar getbyte(Msg*);
+ushort getshort(Msg*);
+ulong getlong(Msg*);
+char* getstring(Msg*);
+void* getbytes(Msg*, int);
+mpint* getmpint(Msg*);
+RSApub* getRSApub(Msg*);
+void putbyte(Msg*, uchar);
+void putshort(Msg*, ushort);
+void putlong(Msg*, ulong);
+void putstring(Msg*, char*);
+void putbytes(Msg*, void*, long);
+void putmpint(Msg*, mpint*);
+void putRSApub(Msg*, RSApub*);
+mpint* rsapad(mpint*, int);
+mpint* rsaunpad(mpint*);
+void mptoberjust(mpint*, uchar*, int);
+mpint* rsaencryptbuf(RSApub*, uchar*, int);
+
+/* cmsg.c */
+void sshclienthandshake(Conn*);
+void requestpty(Conn*);
+int readgeom(int*, int*, int*, int*);
+void sendwindowsize(Conn*, int, int, int, int);
+int rawhack;
+
+/* smsg.c */
+void sshserverhandshake(Conn*);
+
+/* pubkey.c */
+enum
+{
+ KeyOk,
+ KeyWrong,
+ NoKey,
+ NoKeyFile,
+};
+int appendkey(char*, char*, RSApub*);
+int findkey(char*, char*, RSApub*);
+int replacekey(char*, char*, RSApub*);
+
+/* agent.c */
+int startagent(Conn*);
+void handleagentmsg(Msg*);
+void handleagentopen(Msg*);
+void handleagentieof(Msg*);
+void handleagentoclose(Msg*);
+
+/* util.c */
+void debug(int, char*, ...);
+void* emalloc(long);
+void* erealloc(void*, long);
+void error(char*, ...);
+RSApriv* readsecretkey(char*);
+int readstrnl(int, char*, int);
+void atexitkill(int);
+void atexitkiller(void);
+void calcsessid(Conn*);
+void sshlog(char*, ...);
+void setaliases(Conn*, char*);
+void privatefactotum(void);
+
+#pragma varargck argpos debug 2
+#pragma varargck argpos error 1
+#pragma varargck argpos sshlog 2
diff --git a/sys/src/cmd/ssh/sshnet.c b/sys/src/cmd/ssh/sshnet.c
new file mode 100755
index 000000000..1de834c72
--- /dev/null
+++ b/sys/src/cmd/ssh/sshnet.c
@@ -0,0 +1,1110 @@
+/*
+ * SSH network file system.
+ * Presents remote TCP stack as /net-style file system.
+ */
+
+#include "ssh.h"
+#include <bio.h>
+#include <ndb.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+
+int rawhack = 1;
+Conn *conn;
+char *remoteip = "<remote>";
+char *mtpt;
+
+Cipher *allcipher[] = {
+ &cipherrc4,
+ &cipherblowfish,
+ &cipher3des,
+ &cipherdes,
+ &ciphernone,
+ &ciphertwiddle,
+};
+
+Auth *allauth[] = {
+ &authpassword,
+ &authrsa,
+ &authtis,
+};
+
+char *cipherlist = "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: sshnet [-A authlist] [-c cipherlist] [-m mtpt] [user@]hostname\n");
+ exits("usage");
+}
+
+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;
+}
+
+enum
+{
+ Qroot,
+ Qcs,
+ Qtcp,
+ Qclone,
+ Qn,
+ Qctl,
+ Qdata,
+ Qlocal,
+ Qremote,
+ Qstatus,
+};
+
+#define PATH(type, n) ((type)|((n)<<8))
+#define TYPE(path) ((int)(path) & 0xFF)
+#define NUM(path) ((uint)(path)>>8)
+
+Channel *sshmsgchan; /* chan(Msg*) */
+Channel *fsreqchan; /* chan(Req*) */
+Channel *fsreqwaitchan; /* chan(nil) */
+Channel *fsclunkchan; /* chan(Fid*) */
+Channel *fsclunkwaitchan; /* chan(nil) */
+ulong time0;
+
+enum
+{
+ Closed,
+ Dialing,
+ Established,
+ Teardown,
+};
+
+char *statestr[] = {
+ "Closed",
+ "Dialing",
+ "Established",
+ "Teardown",
+};
+
+typedef struct Client Client;
+struct Client
+{
+ int ref;
+ int state;
+ int num;
+ int servernum;
+ char *connect;
+ Req *rq;
+ Req **erq;
+ Msg *mq;
+ Msg **emq;
+};
+
+int nclient;
+Client **client;
+
+int
+newclient(void)
+{
+ int i;
+ Client *c;
+
+ for(i=0; i<nclient; i++)
+ if(client[i]->ref==0 && client[i]->state == Closed)
+ return i;
+
+ if(nclient%16 == 0)
+ client = erealloc9p(client, (nclient+16)*sizeof(client[0]));
+
+ c = emalloc9p(sizeof(Client));
+ memset(c, 0, sizeof(*c));
+ c->num = nclient;
+ client[nclient++] = c;
+ return c->num;
+}
+
+void
+queuereq(Client *c, Req *r)
+{
+ if(c->rq==nil)
+ c->erq = &c->rq;
+ *c->erq = r;
+ r->aux = nil;
+ c->erq = (Req**)&r->aux;
+}
+
+void
+queuemsg(Client *c, Msg *m)
+{
+ if(c->mq==nil)
+ c->emq = &c->mq;
+ *c->emq = m;
+ m->link = nil;
+ c->emq = (Msg**)&m->link;
+}
+
+void
+matchmsgs(Client *c)
+{
+ Req *r;
+ Msg *m;
+ int n, rm;
+
+ while(c->rq && c->mq){
+ r = c->rq;
+ c->rq = r->aux;
+
+ rm = 0;
+ m = c->mq;
+ n = r->ifcall.count;
+ if(n >= m->ep - m->rp){
+ n = m->ep - m->rp;
+ c->mq = m->link;
+ rm = 1;
+ }
+ memmove(r->ofcall.data, m->rp, n);
+ if(rm)
+ free(m);
+ else
+ m->rp += n;
+ r->ofcall.count = n;
+ respond(r, nil);
+ }
+}
+
+Req*
+findreq(Client *c, Req *r)
+{
+ Req **l;
+
+ for(l=&c->rq; *l; l=(Req**)&(*l)->aux){
+ if(*l == r){
+ *l = r->aux;
+ if(*l == nil)
+ c->erq = l;
+ return r;
+ }
+ }
+ return nil;
+}
+
+void
+dialedclient(Client *c)
+{
+ Req *r;
+
+ if(r=c->rq){
+ if(r->aux != nil)
+ sysfatal("more than one outstanding dial request (BUG)");
+ if(c->state == Established)
+ respond(r, nil);
+ else
+ respond(r, "connect failed");
+ }
+ c->rq = nil;
+}
+
+void
+teardownclient(Client *c)
+{
+ Msg *m;
+
+ c->state = Teardown;
+ m = allocmsg(conn, SSH_MSG_CHANNEL_INPUT_EOF, 4);
+ putlong(m, c->servernum);
+ sendmsg(m);
+}
+
+void
+hangupclient(Client *c)
+{
+ Req *r, *next;
+ Msg *m, *mnext;
+
+ c->state = Closed;
+ for(m=c->mq; m; m=mnext){
+ mnext = m->link;
+ free(m);
+ }
+ c->mq = nil;
+ for(r=c->rq; r; r=next){
+ next = r->aux;
+ respond(r, "hangup on network connection");
+ }
+ c->rq = nil;
+}
+
+void
+closeclient(Client *c)
+{
+ Msg *m, *next;
+
+ if(--c->ref)
+ return;
+
+ if(c->rq != nil)
+ sysfatal("ref count reached zero with requests pending (BUG)");
+
+ for(m=c->mq; m; m=next){
+ next = m->link;
+ free(m);
+ }
+ c->mq = nil;
+
+ if(c->state != Closed)
+ teardownclient(c);
+}
+
+
+void
+sshreadproc(void *a)
+{
+ Conn *c;
+ Msg *m;
+
+ c = a;
+ for(;;){
+ m = recvmsg(c, -1);
+ if(m == nil)
+ sysfatal("eof on ssh connection");
+ sendp(sshmsgchan, m);
+ }
+}
+
+typedef struct Tab Tab;
+struct Tab
+{
+ char *name;
+ ulong mode;
+};
+
+Tab tab[] =
+{
+ "/", DMDIR|0555,
+ "cs", 0666,
+ "tcp", DMDIR|0555,
+ "clone", 0666,
+ nil, DMDIR|0555,
+ "ctl", 0666,
+ "data", 0666,
+ "local", 0444,
+ "remote", 0444,
+ "status", 0444,
+};
+
+static void
+fillstat(Dir *d, uvlong path)
+{
+ Tab *t;
+
+ memset(d, 0, sizeof(*d));
+ d->uid = estrdup9p("ssh");
+ d->gid = estrdup9p("ssh");
+ d->qid.path = path;
+ d->atime = d->mtime = time0;
+ t = &tab[TYPE(path)];
+ if(t->name)
+ d->name = estrdup9p(t->name);
+ else{
+ d->name = smprint("%ud", NUM(path));
+ if(d->name == nil)
+ sysfatal("out of memory");
+ }
+ d->qid.type = t->mode>>24;
+ d->mode = t->mode;
+}
+
+static void
+fsattach(Req *r)
+{
+ if(r->ifcall.aname && r->ifcall.aname[0]){
+ respond(r, "invalid attach specifier");
+ return;
+ }
+ r->fid->qid.path = PATH(Qroot, 0);
+ r->fid->qid.type = QTDIR;
+ r->fid->qid.vers = 0;
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+ fillstat(&r->d, r->fid->qid.path);
+ respond(r, nil);
+}
+
+static int
+rootgen(int i, Dir *d, void*)
+{
+ i += Qroot+1;
+ if(i <= Qtcp){
+ fillstat(d, i);
+ return 0;
+ }
+ return -1;
+}
+
+static int
+tcpgen(int i, Dir *d, void*)
+{
+ i += Qtcp+1;
+ if(i < Qn){
+ fillstat(d, i);
+ return 0;
+ }
+ i -= Qn;
+ if(i < nclient){
+ fillstat(d, PATH(Qn, i));
+ return 0;
+ }
+ return -1;
+}
+
+static int
+clientgen(int i, Dir *d, void *aux)
+{
+ Client *c;
+
+ c = aux;
+ i += Qn+1;
+ if(i <= Qstatus){
+ fillstat(d, PATH(i, c->num));
+ return 0;
+ }
+ return -1;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+ int i, n;
+ char buf[32];
+ ulong path;
+
+ path = fid->qid.path;
+ if(!(fid->qid.type&QTDIR))
+ return "walk in non-directory";
+
+ if(strcmp(name, "..") == 0){
+ switch(TYPE(path)){
+ case Qn:
+ qid->path = PATH(Qtcp, NUM(path));
+ qid->type = tab[Qtcp].mode>>24;
+ return nil;
+ case Qtcp:
+ qid->path = PATH(Qroot, 0);
+ qid->type = tab[Qroot].mode>>24;
+ return nil;
+ case Qroot:
+ return nil;
+ default:
+ return "bug in fswalk1";
+ }
+ }
+
+ i = TYPE(path)+1;
+ for(; i<nelem(tab); i++){
+ if(i==Qn){
+ n = atoi(name);
+ snprint(buf, sizeof buf, "%d", n);
+ if(n < nclient && strcmp(buf, name) == 0){
+ qid->path = PATH(i, n);
+ qid->type = tab[i].mode>>24;
+ return nil;
+ }
+ break;
+ }
+ if(strcmp(name, tab[i].name) == 0){
+ qid->path = PATH(i, NUM(path));
+ qid->type = tab[i].mode>>24;
+ return nil;
+ }
+ if(tab[i].mode&DMDIR)
+ break;
+ }
+ return "directory entry not found";
+}
+
+typedef struct Cs Cs;
+struct Cs
+{
+ char *resp;
+ int isnew;
+};
+
+static int
+ndbfindport(char *p)
+{
+ char *s, *port;
+ int n;
+ static Ndb *db;
+
+ if(*p == '\0')
+ return -1;
+
+ n = strtol(p, &s, 0);
+ if(*s == '\0')
+ return n;
+
+ if(db == nil){
+ db = ndbopen("/lib/ndb/common");
+ if(db == nil)
+ return -1;
+ }
+
+ port = ndbgetvalue(db, nil, "tcp", p, "port", nil);
+ if(port == nil)
+ return -1;
+ n = atoi(port);
+ free(port);
+
+ return n;
+}
+
+static void
+csread(Req *r)
+{
+ Cs *cs;
+
+ cs = r->fid->aux;
+ if(cs->resp==nil){
+ respond(r, "cs read without write");
+ return;
+ }
+ if(r->ifcall.offset==0){
+ if(!cs->isnew){
+ r->ofcall.count = 0;
+ respond(r, nil);
+ return;
+ }
+ cs->isnew = 0;
+ }
+ readstr(r, cs->resp);
+ respond(r, nil);
+}
+
+static void
+cswrite(Req *r)
+{
+ int port, nf;
+ char err[ERRMAX], *f[4], *s, *ns;
+ Cs *cs;
+
+ cs = r->fid->aux;
+ s = emalloc(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = '\0';
+
+ nf = getfields(s, f, nelem(f), 0, "!");
+ if(nf != 3){
+ free(s);
+ respond(r, "can't translate");
+ return;
+ }
+ if(strcmp(f[0], "tcp") != 0 && strcmp(f[0], "net") != 0){
+ free(s);
+ respond(r, "unknown protocol");
+ return;
+ }
+ port = ndbfindport(f[2]);
+ if(port <= 0){
+ free(s);
+ respond(r, "no translation found");
+ return;
+ }
+
+ ns = smprint("%s/tcp/clone %s!%d", mtpt, f[1], port);
+ if(ns == nil){
+ free(s);
+ rerrstr(err, sizeof err);
+ respond(r, err);
+ return;
+ }
+ free(s);
+ free(cs->resp);
+ cs->resp = ns;
+ cs->isnew = 1;
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+}
+
+static void
+ctlread(Req *r, Client *c)
+{
+ char buf[32];
+
+ sprint(buf, "%d", c->num);
+ readstr(r, buf);
+ respond(r, nil);
+}
+
+static void
+ctlwrite(Req *r, Client *c)
+{
+ char *f[3], *s;
+ int nf;
+ Msg *m;
+
+ s = emalloc(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = '\0';
+
+ nf = tokenize(s, f, 3);
+ if(nf == 0){
+ free(s);
+ respond(r, nil);
+ return;
+ }
+
+ if(strcmp(f[0], "hangup") == 0){
+ if(c->state != Established)
+ goto Badarg;
+ if(nf != 1)
+ goto Badarg;
+ queuereq(c, r);
+ teardownclient(c);
+ }else if(strcmp(f[0], "connect") == 0){
+ if(c->state != Closed)
+ goto Badarg;
+ if(nf != 2)
+ goto Badarg;
+ c->connect = estrdup9p(f[1]);
+ nf = getfields(f[1], f, nelem(f), 0, "!");
+ if(nf != 2){
+ free(c->connect);
+ c->connect = nil;
+ goto Badarg;
+ }
+ c->state = Dialing;
+ m = allocmsg(conn, SSH_MSG_PORT_OPEN, 4+4+strlen(f[0])+4+4+strlen("localhost"));
+ putlong(m, c->num);
+ putstring(m, f[0]);
+ putlong(m, ndbfindport(f[1]));
+ putstring(m, "localhost");
+ queuereq(c, r);
+ sendmsg(m);
+ }else{
+ Badarg:
+ respond(r, "bad or inappropriate tcp control message");
+ }
+ free(s);
+}
+
+static void
+dataread(Req *r, Client *c)
+{
+ if(c->state != Established){
+ respond(r, "not connected");
+ return;
+ }
+ queuereq(c, r);
+ matchmsgs(c);
+}
+
+static void
+datawrite(Req *r, Client *c)
+{
+ Msg *m;
+
+ if(c->state != Established){
+ respond(r, "not connected");
+ return;
+ }
+ if(r->ifcall.count){
+ m = allocmsg(conn, SSH_MSG_CHANNEL_DATA, 4+4+r->ifcall.count);
+ putlong(m, c->servernum);
+ putlong(m, r->ifcall.count);
+ putbytes(m, r->ifcall.data, r->ifcall.count);
+ sendmsg(m);
+ }
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+}
+
+static void
+localread(Req *r)
+{
+ char buf[128];
+
+ snprint(buf, sizeof buf, "%s!%d\n", remoteip, 0);
+ readstr(r, buf);
+ respond(r, nil);
+}
+
+static void
+remoteread(Req *r, Client *c)
+{
+ char *s;
+ char buf[128];
+
+ s = c->connect;
+ if(s == nil)
+ s = "::!0";
+ snprint(buf, sizeof buf, "%s\n", s);
+ readstr(r, buf);
+ respond(r, nil);
+}
+
+static void
+statusread(Req *r, Client *c)
+{
+ char buf[64];
+ char *s;
+
+ snprint(buf, sizeof buf, "%s!%d", remoteip, 0);
+ s = statestr[c->state];
+ readstr(r, s);
+ respond(r, nil);
+}
+
+static void
+fsread(Req *r)
+{
+ char e[ERRMAX];
+ ulong path;
+
+ path = r->fid->qid.path;
+ switch(TYPE(path)){
+ default:
+ snprint(e, sizeof e, "bug in fsread path=%lux", path);
+ respond(r, e);
+ break;
+
+ case Qroot:
+ dirread9p(r, rootgen, nil);
+ respond(r, nil);
+ break;
+
+ case Qcs:
+ csread(r);
+ break;
+
+ case Qtcp:
+ dirread9p(r, tcpgen, nil);
+ respond(r, nil);
+ break;
+
+ case Qn:
+ dirread9p(r, clientgen, client[NUM(path)]);
+ respond(r, nil);
+ break;
+
+ case Qctl:
+ ctlread(r, client[NUM(path)]);
+ break;
+
+ case Qdata:
+ dataread(r, client[NUM(path)]);
+ break;
+
+ case Qlocal:
+ localread(r);
+ break;
+
+ case Qremote:
+ remoteread(r, client[NUM(path)]);
+ break;
+
+ case Qstatus:
+ statusread(r, client[NUM(path)]);
+ break;
+ }
+}
+
+static void
+fswrite(Req *r)
+{
+ ulong path;
+ char e[ERRMAX];
+
+ path = r->fid->qid.path;
+ switch(TYPE(path)){
+ default:
+ snprint(e, sizeof e, "bug in fswrite path=%lux", path);
+ respond(r, e);
+ break;
+
+ case Qcs:
+ cswrite(r);
+ break;
+
+ case Qctl:
+ ctlwrite(r, client[NUM(path)]);
+ break;
+
+ case Qdata:
+ datawrite(r, client[NUM(path)]);
+ break;
+ }
+}
+
+static void
+fsopen(Req *r)
+{
+ static int need[4] = { 4, 2, 6, 1 };
+ ulong path;
+ int n;
+ Tab *t;
+ Cs *cs;
+
+ /*
+ * lib9p already handles the blatantly obvious.
+ * we just have to enforce the permissions we have set.
+ */
+ path = r->fid->qid.path;
+ t = &tab[TYPE(path)];
+ n = need[r->ifcall.mode&3];
+ if((n&t->mode) != n){
+ respond(r, "permission denied");
+ return;
+ }
+
+ switch(TYPE(path)){
+ case Qcs:
+ cs = emalloc(sizeof(Cs));
+ r->fid->aux = cs;
+ respond(r, nil);
+ break;
+ case Qclone:
+ n = newclient();
+ path = PATH(Qctl, n);
+ r->fid->qid.path = path;
+ r->ofcall.qid.path = path;
+ if(chatty9p)
+ fprint(2, "open clone => path=%lux\n", path);
+ t = &tab[Qctl];
+ /* fall through */
+ default:
+ if(t-tab >= Qn)
+ client[NUM(path)]->ref++;
+ respond(r, nil);
+ break;
+ }
+}
+
+static void
+fsflush(Req *r)
+{
+ int i;
+
+ for(i=0; i<nclient; i++)
+ if(findreq(client[i], r->oldreq))
+ respond(r->oldreq, "interrupted");
+ respond(r, nil);
+}
+
+static void
+handlemsg(Msg *m)
+{
+ int chan, n;
+ Client *c;
+
+ switch(m->type){
+ case SSH_MSG_DISCONNECT:
+ case SSH_CMSG_EXIT_CONFIRMATION:
+ sysfatal("disconnect");
+
+ case SSH_CMSG_STDIN_DATA:
+ case SSH_CMSG_EOF:
+ case SSH_CMSG_WINDOW_SIZE:
+ /* don't care */
+ free(m);
+ break;
+
+ case SSH_MSG_CHANNEL_DATA:
+ chan = getlong(m);
+ n = getlong(m);
+ if(m->rp+n != m->ep)
+ sysfatal("got bad channel data");
+ if(chan<nclient && (c=client[chan])->state==Established){
+ queuemsg(c, m);
+ matchmsgs(c);
+ }else
+ free(m);
+ break;
+
+ case SSH_MSG_CHANNEL_INPUT_EOF:
+ chan = getlong(m);
+ free(m);
+ if(chan<nclient){
+ c = client[chan];
+ chan = c->servernum;
+ hangupclient(c);
+ m = allocmsg(conn, SSH_MSG_CHANNEL_OUTPUT_CLOSED, 4);
+ putlong(m, chan);
+ sendmsg(m);
+ }
+ break;
+
+ case SSH_MSG_CHANNEL_OUTPUT_CLOSED:
+ chan = getlong(m);
+ if(chan<nclient)
+ hangupclient(client[chan]);
+ free(m);
+ break;
+
+ case SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
+ chan = getlong(m);
+ c = nil;
+ if(chan>=nclient || (c=client[chan])->state != Dialing){
+ if(c)
+ fprint(2, "cstate %d\n", c->state);
+ sysfatal("got unexpected open confirmation for %d", chan);
+ }
+ c->servernum = getlong(m);
+ c->state = Established;
+ dialedclient(c);
+ free(m);
+ break;
+
+ case SSH_MSG_CHANNEL_OPEN_FAILURE:
+ chan = getlong(m);
+ c = nil;
+ if(chan>=nclient || (c=client[chan])->state != Dialing)
+ sysfatal("got unexpected open failure");
+ if(m->rp+4 <= m->ep)
+ c->servernum = getlong(m);
+ c->state = Closed;
+ dialedclient(c);
+ free(m);
+ break;
+ }
+}
+
+void
+fsnetproc(void*)
+{
+ ulong path;
+ Alt a[4];
+ Cs *cs;
+ Fid *fid;
+ Req *r;
+ Msg *m;
+
+ threadsetname("fsthread");
+
+ a[0].op = CHANRCV;
+ a[0].c = fsclunkchan;
+ a[0].v = &fid;
+ a[1].op = CHANRCV;
+ a[1].c = fsreqchan;
+ a[1].v = &r;
+ a[2].op = CHANRCV;
+ a[2].c = sshmsgchan;
+ a[2].v = &m;
+ a[3].op = CHANEND;
+
+ for(;;){
+ switch(alt(a)){
+ case 0:
+ path = fid->qid.path;
+ switch(TYPE(path)){
+ case Qcs:
+ cs = fid->aux;
+ if(cs){
+ free(cs->resp);
+ free(cs);
+ }
+ break;
+ }
+ if(fid->omode != -1 && TYPE(path) >= Qn)
+ closeclient(client[NUM(path)]);
+ sendp(fsclunkwaitchan, nil);
+ break;
+ case 1:
+ switch(r->ifcall.type){
+ case Tattach:
+ fsattach(r);
+ break;
+ case Topen:
+ fsopen(r);
+ break;
+ case Tread:
+ fsread(r);
+ break;
+ case Twrite:
+ fswrite(r);
+ break;
+ case Tstat:
+ fsstat(r);
+ break;
+ case Tflush:
+ fsflush(r);
+ break;
+ default:
+ respond(r, "bug in fsthread");
+ break;
+ }
+ sendp(fsreqwaitchan, 0);
+ break;
+ case 2:
+ handlemsg(m);
+ break;
+ }
+ }
+}
+
+static void
+fssend(Req *r)
+{
+ sendp(fsreqchan, r);
+ recvp(fsreqwaitchan); /* avoids need to deal with spurious flushes */
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+ sendp(fsclunkchan, fid);
+ recvp(fsclunkwaitchan);
+}
+
+void
+takedown(Srv*)
+{
+ threadexitsall("done");
+}
+
+Srv fs =
+{
+.attach= fssend,
+.destroyfid= fsdestroyfid,
+.walk1= fswalk1,
+.open= fssend,
+.read= fssend,
+.write= fssend,
+.stat= fssend,
+.flush= fssend,
+.end= takedown,
+};
+
+void
+threadmain(int argc, char **argv)
+{
+ int i, fd;
+ char *host, *user, *p, *service;
+ char *f[16];
+ Msg *m;
+ static Conn c;
+
+ fmtinstall('B', mpfmt);
+ fmtinstall('H', encodefmt);
+
+ mtpt = "/net";
+ service = nil;
+ user = nil;
+ ARGBEGIN{
+ case 'B': /* undocumented, debugging */
+ doabort = 1;
+ break;
+ case 'D': /* undocumented, debugging */
+ debuglevel = strtol(EARGF(usage()), nil, 0);
+ break;
+ case '9': /* undocumented, debugging */
+ chatty9p++;
+ break;
+
+ case 'A':
+ authlist = EARGF(usage());
+ break;
+ case 'c':
+ cipherlist = EARGF(usage());
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ case 's':
+ service = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ host = argv[0];
+
+ 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((fd = dial(netmkaddr(host, "tcp", "ssh"), nil, nil, nil)) < 0)
+ sysfatal("dialing %s: %r", host);
+
+ c.interactive = isatty(0);
+ 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);
+
+ requestpty(&c); /* turns on TCP_NODELAY on other side */
+ m = allocmsg(&c, SSH_CMSG_EXEC_SHELL, 0);
+ sendmsg(m);
+
+ time0 = time(0);
+ sshmsgchan = chancreate(sizeof(Msg*), 16);
+ fsreqchan = chancreate(sizeof(Req*), 0);
+ fsreqwaitchan = chancreate(sizeof(void*), 0);
+ fsclunkchan = chancreate(sizeof(Fid*), 0);
+ fsclunkwaitchan = chancreate(sizeof(void*), 0);
+
+ conn = &c;
+ procrfork(sshreadproc, &c, 8192, RFNAMEG|RFNOTEG);
+ procrfork(fsnetproc, nil, 8192, RFNAMEG|RFNOTEG);
+
+ threadpostmountsrv(&fs, service, mtpt, MREPL);
+ exits(0);
+}
+
diff --git a/sys/src/cmd/ssh/sshserve.c b/sys/src/cmd/ssh/sshserve.c
new file mode 100755
index 000000000..87b0536c5
--- /dev/null
+++ b/sys/src/cmd/ssh/sshserve.c
@@ -0,0 +1,315 @@
+#include "ssh.h"
+
+char *cipherlist = "blowfish rc4 3des";
+char *authlist = "tis";
+
+void fromnet(Conn*);
+void startcmd(Conn*, char*, int*, int*);
+int maxmsg = 256*1024;
+
+Cipher *allcipher[] = {
+ &cipherrc4,
+ &cipherblowfish,
+ &cipher3des,
+ &cipherdes,
+ &ciphernone,
+ &ciphertwiddle,
+};
+
+Authsrv *allauthsrv[] = {
+ &authsrvpassword,
+ &authsrvtis,
+};
+
+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;
+}
+
+Authsrv*
+findauthsrv(char *name, Authsrv **list, int nlist)
+{
+ int i;
+
+ for(i=0; i<nlist; i++)
+ if(strcmp(name, list[i]->name) == 0)
+ return list[i];
+ error("unknown authsrv %s", name);
+ return nil;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: sshserve [-A authlist] [-c cipherlist] client-ip-address\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *f[16];
+ int i;
+ Conn c;
+
+ fmtinstall('B', mpfmt);
+ fmtinstall('H', encodefmt);
+ atexit(atexitkiller);
+ atexitkill(getpid());
+
+ memset(&c, 0, sizeof c);
+
+ ARGBEGIN{
+ case 'D':
+ debuglevel = atoi(EARGF(usage()));
+ break;
+ case 'A':
+ authlist = EARGF(usage());
+ break;
+ case 'c':
+ cipherlist = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+ c.host = argv[0];
+
+ sshlog("connect from %s", c.host);
+
+ /* limit of 768 bits in remote host key? */
+ c.serverpriv = rsagen(768, 6, 0);
+ if(c.serverpriv == nil)
+ sysfatal("rsagen failed: %r");
+ c.serverkey = &c.serverpriv->pub;
+
+ 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.nokauthsrv = getfields(authlist, f, nelem(f), 1, ", ");
+ c.okauthsrv = emalloc(sizeof(Authsrv*)*c.nokauthsrv);
+ for(i=0; i<c.nokauthsrv; i++)
+ c.okauthsrv[i] = findauthsrv(f[i], allauthsrv, nelem(allauthsrv));
+
+ sshserverhandshake(&c);
+
+ fromnet(&c);
+}
+
+void
+fromnet(Conn *c)
+{
+ int infd, kidpid, n;
+ char *cmd;
+ Msg *m;
+
+ infd = kidpid = -1;
+ for(;;){
+ m = recvmsg(c, -1);
+ if(m == nil)
+ exits(nil);
+ switch(m->type){
+ default:
+ //badmsg(m, 0);
+ sendmsg(allocmsg(c, SSH_SMSG_FAILURE, 0));
+ break;
+
+ case SSH_MSG_DISCONNECT:
+ sysfatal("client disconnected");
+
+ case SSH_CMSG_REQUEST_PTY:
+ sendmsg(allocmsg(c, SSH_SMSG_SUCCESS, 0));
+ break;
+
+ case SSH_CMSG_X11_REQUEST_FORWARDING:
+ sendmsg(allocmsg(c, SSH_SMSG_FAILURE, 0));
+ break;
+
+ case SSH_CMSG_MAX_PACKET_SIZE:
+ maxmsg = getlong(m);
+ sendmsg(allocmsg(c, SSH_SMSG_SUCCESS, 0));
+ break;
+
+ case SSH_CMSG_REQUEST_COMPRESSION:
+ sendmsg(allocmsg(c, SSH_SMSG_FAILURE, 0));
+ break;
+
+ case SSH_CMSG_EXEC_SHELL:
+ startcmd(c, nil, &kidpid, &infd);
+ goto InteractiveMode;
+
+ case SSH_CMSG_EXEC_CMD:
+ cmd = getstring(m);
+ startcmd(c, cmd, &kidpid, &infd);
+ goto InteractiveMode;
+ }
+ free(m);
+ }
+
+InteractiveMode:
+ for(;;){
+ free(m);
+ m = recvmsg(c, -1);
+ if(m == nil)
+ exits(nil);
+ switch(m->type){
+ default:
+ badmsg(m, 0);
+
+ case SSH_MSG_DISCONNECT:
+ postnote(PNGROUP, kidpid, "hangup");
+ sysfatal("client disconnected");
+
+ case SSH_CMSG_STDIN_DATA:
+ if(infd != 0){
+ n = getlong(m);
+ write(infd, getbytes(m, n), n);
+ }
+ break;
+
+ case SSH_CMSG_EOF:
+ close(infd);
+ infd = -1;
+ break;
+
+ case SSH_CMSG_EXIT_CONFIRMATION:
+ /* sent by some clients as dying breath */
+ exits(nil);
+
+ case SSH_CMSG_WINDOW_SIZE:
+ /* we don't care */
+ break;
+ }
+ }
+}
+
+void
+copyout(Conn *c, int fd, int mtype)
+{
+ char buf[8192];
+ int n, max, pid;
+ Msg *m;
+
+ max = sizeof buf;
+ if(max > maxmsg - 32) /* 32 is an overestimate of packet overhead */
+ max = maxmsg - 32;
+ if(max <= 0)
+ sysfatal("maximum message size too small");
+
+ switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ break;
+ default:
+ atexitkill(pid);
+ return;
+ }
+
+ while((n = read(fd, buf, max)) > 0){
+ m = allocmsg(c, mtype, 4+n);
+ putlong(m, n);
+ putbytes(m, buf, n);
+ sendmsg(m);
+ }
+ exits(nil);
+}
+
+void
+startcmd(Conn *c, char *cmd, int *kidpid, int *kidin)
+{
+ int i, pid, kpid;
+ int pfd[3][2];
+ char *dir;
+ char *sysname, *tz;
+ Msg *m;
+ Waitmsg *w;
+
+ for(i=0; i<3; i++)
+ if(pipe(pfd[i]) < 0)
+ sysfatal("pipe: %r");
+
+ sysname = getenv("sysname");
+ tz = getenv("timezone");
+
+ switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ switch(kpid = rfork(RFPROC|RFNOTEG|RFENVG|RFFDG)){
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ for(i=0; i<3; i++){
+ if(dup(pfd[i][1], i) < 0)
+ sysfatal("dup: %r");
+ close(pfd[i][0]);
+ close(pfd[i][1]);
+ }
+ putenv("user", c->user);
+ if(sysname)
+ putenv("sysname", sysname);
+ if(tz)
+ putenv("tz", tz);
+
+ dir = smprint("/usr/%s", c->user);
+ if(dir == nil || chdir(dir) < 0)
+ chdir("/");
+ if(cmd){
+ putenv("service", "rx");
+ execl("/bin/rc", "rc", "-lc", cmd, nil);
+ sysfatal("cannot exec /bin/rc: %r");
+ }else{
+ putenv("service", "con");
+ execl("/bin/ip/telnetd", "telnetd", "-tn", nil);
+ sysfatal("cannot exec /bin/ip/telnetd: %r");
+ }
+ default:
+ *kidpid = kpid;
+ rendezvous(kidpid, 0);
+ for(;;){
+ if((w = wait()) == nil)
+ sysfatal("wait: %r");
+ if(w->pid == kpid)
+ break;
+ free(w);
+ }
+ if(w->msg[0]){
+ m = allocmsg(c, SSH_MSG_DISCONNECT, 4+strlen(w->msg));
+ putstring(m, w->msg);
+ sendmsg(m);
+ }else{
+ m = allocmsg(c, SSH_SMSG_EXITSTATUS, 4);
+ putlong(m, 0);
+ sendmsg(m);
+ }
+ for(i=0; i<3; i++)
+ close(pfd[i][0]);
+ free(w);
+ exits(nil);
+ break;
+ }
+ default:
+ atexitkill(pid);
+ rendezvous(kidpid, 0);
+ break;
+ }
+
+ for(i=0; i<3; i++)
+ close(pfd[i][1]);
+
+ copyout(c, pfd[1][0], SSH_SMSG_STDOUT_DATA);
+ copyout(c, pfd[2][0], SSH_SMSG_STDERR_DATA);
+ *kidin = pfd[0][0];
+}
diff --git a/sys/src/cmd/ssh/util.c b/sys/src/cmd/ssh/util.c
new file mode 100755
index 000000000..ff737884a
--- /dev/null
+++ b/sys/src/cmd/ssh/util.c
@@ -0,0 +1,269 @@
+#include "ssh.h"
+#include <bio.h>
+#include <ndb.h>
+
+char Edecode[] = "error decoding input packet";
+char Eencode[] = "out of space encoding output packet (BUG)";
+char Ehangup[] = "hungup connection";
+char Ememory[] = "out of memory";
+
+int debuglevel;
+int doabort;
+
+void
+error(char *fmt, ...)
+{
+ va_list arg;
+ char buf[2048];
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ fprint(2, "%s: %s\n", argv0, buf);
+ if(doabort)
+ abort();
+ exits(buf);
+}
+
+void
+debug(int level, char *fmt, ...)
+{
+ va_list arg;
+
+ if((level&debuglevel) == 0)
+ return;
+ va_start(arg, fmt);
+ vfprint(2, fmt, arg);
+ va_end(arg);
+}
+
+void*
+emalloc(long n)
+{
+ void *a;
+
+ a = mallocz(n, 1);
+ if(a == nil)
+ error(Ememory);
+ setmalloctag(a, getcallerpc(&n));
+ return a;
+}
+
+void*
+erealloc(void *v, long n)
+{
+ v = realloc(v, n);
+ if(v == nil)
+ error(Ememory);
+ setrealloctag(v, getcallerpc(&v));
+ return v;
+}
+
+
+static int killpid[32];
+static int nkillpid;
+void
+atexitkiller(void)
+{
+ int i, pid;
+
+ pid = getpid();
+ debug(DBG, "atexitkiller: nkillpid=%d mypid=%d\n", nkillpid, pid);
+ for(i=0; i<nkillpid; i++)
+ if(pid != killpid[i]){
+ debug(DBG, "killing %d\n", killpid[i]);
+ postnote(PNPROC, killpid[i], "kill");
+ }
+}
+void
+atexitkill(int pid)
+{
+ killpid[nkillpid++] = pid;
+}
+
+int
+readstrnl(int fd, char *buf, int nbuf)
+{
+ int i;
+
+ for(i=0; i<nbuf; i++){
+ switch(read(fd, buf+i, 1)){
+ case -1:
+ return -1;
+ case 0:
+ werrstr("unexpected EOF");
+ return -1;
+ default:
+ if(buf[i]=='\n'){
+ buf[i] = '\0';
+ return 0;
+ }
+ break;
+ }
+ }
+ werrstr("line too long");
+ return -1;
+}
+
+void
+calcsessid(Conn *c)
+{
+ int n;
+ uchar buf[1024];
+
+ n = mptobe(c->hostkey->n, buf, sizeof buf, nil);
+ n += mptobe(c->serverkey->n, buf+n, sizeof buf-n, nil);
+ memmove(buf+n, c->cookie, COOKIELEN);
+ n += COOKIELEN;
+ md5(buf, n, c->sessid, nil);
+}
+
+void
+sshlog(char *f, ...)
+{
+ char *s;
+ va_list arg;
+ Fmt fmt;
+ static int pid;
+
+ if(pid == 0)
+ pid = getpid();
+
+ va_start(arg, f);
+ va_end(arg);
+
+ if(fmtstrinit(&fmt) < 0)
+ sysfatal("fmtstrinit: %r");
+
+ fmtprint(&fmt, "[%d] ", pid);
+ fmtvprint(&fmt, f, arg);
+
+ s = fmtstrflush(&fmt);
+ if(s == nil)
+ sysfatal("fmtstrflush: %r");
+ syslog(0, "ssh", "%s", s);
+ free(s);
+}
+
+/*
+ * this is far too smart.
+ */
+static int
+pstrcmp(const void *a, const void *b)
+{
+ return strcmp(*(char**)a, *(char**)b);
+}
+
+static char*
+trim(char *s)
+{
+ char *t;
+ int i, last, n, nf;
+ char **f;
+ char *p;
+
+ t = emalloc(strlen(s)+1);
+ t[0] = '\0';
+ n = 1;
+ for(p=s; *p; p++)
+ if(*p == ' ')
+ n++;
+ f = emalloc((n+1)*sizeof(f[0]));
+ nf = tokenize(s, f, n+1);
+ qsort(f, nf, sizeof(f[0]), pstrcmp);
+ last=-1;
+ for(i=0; i<nf; i++){
+ if(last==-1 || strcmp(f[last], f[i])!=0){
+ if(last >= 0)
+ strcat(t, ",");
+ strcat(t, f[i]);
+ last = i;
+ }
+ }
+ return t;
+}
+
+static void
+usetuple(Conn *c, Ndbtuple *t, int scanentries)
+{
+ int first;
+ Ndbtuple *l, *e;
+ char *s;
+
+ first=1;
+ s = c->host;
+ for(l=t; first||l!=t; l=l->line, first=0){
+ if(scanentries){
+ for(e=l; e; e=e->entry){
+ if(strcmp(e->val, c->host) != 0 &&
+ (strcmp(e->attr, "ip")==0 || strcmp(e->attr, "dom")==0 || strcmp(e->attr, "sys")==0)){
+ s = smprint("%s %s", s, e->val);
+ if(s == nil)
+ error("out of memory");
+ }
+ }
+ }
+ if(strcmp(l->val, c->host) != 0 &&
+ (strcmp(l->attr, "ip")==0 || strcmp(l->attr, "dom")==0 || strcmp(l->attr, "sys")==0)){
+ s = smprint("%s %s", s, l->val);
+ if(s == nil)
+ error("out of memory");
+ }
+ }
+ s = trim(s);
+ c->aliases = s;
+}
+
+void
+setaliases(Conn *c, char *name)
+{
+ char *p, *net;
+ char *attr[2];
+ Ndbtuple *t;
+
+ net = "/net";
+ if(name[0]=='/'){
+ p = strchr(name+1, '/');
+ if(p){
+ net = emalloc(p-name+1);
+ memmove(net, name, p-name);
+ }
+ }
+ if(p = strchr(name, '!'))
+ name = p+1;
+
+ c->host = emalloc(strlen(name)+1);
+ strcpy(c->host, name);
+
+ c->aliases = c->host;
+ attr[0] = "dom";
+ attr[1] = "ip";
+ t = csipinfo(nil, ipattr(name), name, attr, 2);
+ if(t != nil){
+ usetuple(c, t, 0);
+ ndbfree(t);
+ }else{
+ t = dnsquery(net, name, "ip");
+ if(t != nil){
+ usetuple(c, t, 1);
+ ndbfree(t);
+ }
+ }
+}
+
+void
+privatefactotum(void)
+{
+ char *user;
+ Dir *d;
+
+ if((user=getuser()) && (d=dirstat("/mnt/factotum/rpc")) && strcmp(user, d->uid)!=0){
+ /* grab the terminal's factotum */
+ rfork(RFNAMEG); /* was RFNOTEG, which makes little sense */
+ if(access("/mnt/term/mnt/factotum", AEXIST) >= 0){
+// fprint(2, "binding terminal's factotum\n");
+ if(bind("/mnt/term/mnt/factotum", "/mnt/factotum", MREPL) < 0)
+ sysfatal("cannot find factotum");
+ }
+ }
+}