summaryrefslogtreecommitdiff
path: root/appl/cmd
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2009-03-30 08:58:09 +0000
committerCharles.Forsyth <devnull@localhost>2009-03-30 08:58:09 +0000
commit9e85ae9b75acdbe245243784d8d5b1b4de1b24f9 (patch)
tree5dfaf8c8881bbf352dab7338cc9fba000c24eb03 /appl/cmd
parent77bf0d6355c02a5d5199dd37033066727cad375b (diff)
x20090330-0957
Diffstat (limited to 'appl/cmd')
-rw-r--r--appl/cmd/ssh/authpassword.b64
-rw-r--r--appl/cmd/ssh/authrsa.b184
-rw-r--r--appl/cmd/ssh/authtis.b116
-rw-r--r--appl/cmd/ssh/cipher3des.b46
-rw-r--r--appl/cmd/ssh/cipherblowfish.b38
-rw-r--r--appl/cmd/ssh/cipherdes.b38
-rw-r--r--appl/cmd/ssh/ciphernone.b24
-rw-r--r--appl/cmd/ssh/cipherrc4.b41
-rw-r--r--appl/cmd/ssh/cons.b116
-rw-r--r--appl/cmd/ssh/mkfile29
-rw-r--r--appl/cmd/ssh/shcons.b296
-rw-r--r--appl/cmd/ssh/sshio.b582
-rw-r--r--appl/cmd/ssh/sshio.m194
-rw-r--r--appl/cmd/ssh/sshserve.b491
14 files changed, 2259 insertions, 0 deletions
diff --git a/appl/cmd/ssh/authpassword.b b/appl/cmd/ssh/authpassword.b
new file mode 100644
index 00000000..369124e8
--- /dev/null
+++ b/appl/cmd/ssh/authpassword.b
@@ -0,0 +1,64 @@
+implement Auth;
+
+include "sys.m";
+ sys: Sys;
+
+include "keyring.m";
+ kr: Keyring; # TO DO: needed to avoid compiler error
+
+include "factotum.m";
+ factotum: Factotum;
+
+include "sshio.m";
+ sshio: Sshio;
+ Conn, Msg: import sshio;
+
+id(): int
+{
+ return SSH_AUTH_PASSWORD;
+}
+
+init(mod: Sshio)
+{
+ sys = load Sys Sys->PATH;
+ sshio = mod;
+}
+
+firstmsg(): int
+{
+ return SSH_CMSG_AUTH_PASSWORD;
+}
+
+authsrv(c: ref Conn, m: ref Msg): ref AuthInfo
+{
+ pass := m.getstring();
+# return auth_userpasswd(c.user, pass);
+ return ref AuthInfo(c.user, nil); # TO DO:
+}
+
+auth(c: ref Conn): int
+{
+ if(factotum == nil)
+ factotum = load Factotum Factotum->PATH;
+ (user, pass) := factotum->getuserpasswd(sys->sprint("proto=pass service=ssh server=%q user=%q", c.host, c.user));
+ if(user == nil){
+ sshio->debug(DBG_AUTH, "getuserpasswd failed");
+ return -1;
+ }
+
+ sshio->debug(DBG_AUTH, "try using password from factotum\n");
+ m := Msg.mk(SSH_CMSG_AUTH_PASSWORD, 4+Sys->UTFmax*len pass);
+ m.putstring(pass);
+ c.out <-= m;
+
+ m = sshio->recvmsg(c, -1);
+ case m.mtype {
+ SSH_SMSG_SUCCESS =>
+ return 0;
+ SSH_SMSG_FAILURE =>
+ return -1;
+ * =>
+ sshio->badmsg(m, 0, nil);
+ return -1;
+ }
+}
diff --git a/appl/cmd/ssh/authrsa.b b/appl/cmd/ssh/authrsa.b
new file mode 100644
index 00000000..be2842af
--- /dev/null
+++ b/appl/cmd/ssh/authrsa.b
@@ -0,0 +1,184 @@
+implement Auth;
+
+include "sys.m";
+ sys: Sys;
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+ PK, SK: import kr;
+
+include "factotum.m";
+ factotum: Factotum;
+ Attr: import factotum;
+ findattrval: import factotum;
+
+include "sshio.m";
+ sshio: Sshio;
+ Conn, Msg: import sshio;
+ debug: import sshio;
+
+id(): int
+{
+ return SSH_AUTH_RSA;
+}
+
+init(mod: Sshio)
+{
+ sshio = mod;
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ factotum = load Factotum Factotum->PATH;
+ factotum->init();
+}
+
+firstmsg(): int
+{
+ return SSH_CMSG_AUTH_RSA;
+}
+
+authsrv(c: ref Conn, m: ref Msg): ref AuthInfo
+{
+ # TO DO: use factotum
+ hismod := m.getipint();
+ if(hismod.bits() < 512){
+ debug(DBG_AUTH, sys->sprint("rsa key for %s < 512 bits\n", c.user));
+ return nil;
+ }
+ hispk := readpk("/keydb/ssh/"+c.user);
+ if(hispk == nil){
+ debug(DBG_AUTH, sys->sprint("no ssh/rsa key for %s: %r\n", c.user));
+ return nil;
+ }
+ if(!hispk.n.eq(hismod)){
+ debug(DBG_AUTH, sys->sprint("%s rsa key doesn't match modulus\n", c.user));
+ return nil;
+ }
+ # encrypt a challenge with his pk
+# chal := IPint.random(256).expmod(IPint.inttoip(1), hismod);
+ chal := IPint.random(256);
+ echal := kr->rsaencrypt(hispk, x := sshio->rsapad(chal, (hispk.n.bits()+7)/8));
+debug(DBG_AUTH, sys->sprint("padded %s\nrsa chal %s\n", x.iptostr(16), echal.iptostr(16)));
+ m = Msg.mk(SSH_SMSG_AUTH_RSA_CHALLENGE, 2048);
+ m.putipint(echal);
+ c.out <-= m;
+
+ m = sshio->recvmsg(c, SSH_CMSG_AUTH_RSA_RESPONSE);
+ response := m.getbytes(Keyring->MD5dlen);
+ chalbuf := array[32+SESSIDLEN] of byte;
+ sshio->iptorjustbe(chal, chalbuf, 32);
+ debug(DBG_AUTH, sys->sprint("\trjusted %s\n", sshio->hex(chalbuf[0:32])));
+ chalbuf[32:] = c.sessid[0: SESSIDLEN];
+ debug(DBG_AUTH, sys->sprint("\tappend sessid %s\n", sshio->hex(chalbuf)));
+ expected := array[Keyring->MD5dlen] of byte;
+ kr->md5(chalbuf, 32+SESSIDLEN, expected, nil);
+ if(sshio->eqbytes(expected, response, len expected))
+ return ref AuthInfo(c.user, nil);
+ return nil;
+}
+
+readpk(file: string): ref PK.RSA
+{
+ fd := sys->open(file, Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array[8192] of byte;
+ nr := sys->readn(fd, buf, len buf);
+ if(nr < 0)
+ return nil;
+ attrs := factotum->parseattrs(string buf[0: nr]);
+ if(findattrval(attrs, "proto") != "rsa" ||
+ (ns := findattrval(attrs, "n")) == nil ||
+ (eks := findattrval(attrs, "ek")) == nil){
+ sys->werrstr("missing rsa key attributes");
+ return nil;
+ }
+ n := IPint.strtoip(ns, 16);
+ ek := IPint.strtoip(eks, 16);
+ if(n == nil || ek == nil){
+ sys->werrstr("invalid rsa key values");
+ return nil;
+ }
+ return ref PK.RSA(n, ek);
+}
+
+auth(c: ref Conn): int
+{
+ chalbuf := array[32+SESSIDLEN] of byte;
+ response := array[Keyring->MD5dlen] of byte;
+
+ debug(DBG_AUTH, "authrsa\n");
+
+ afd := sys->open("/mnt/factotum/rpc", Sys->ORDWR);
+ if(afd == nil){
+ debug(DBG_AUTH, sys->sprint("open /mnt/factotum/rpc: %r\n"));
+ return -1;
+ }
+ s := "proto=rsa role=client";
+ if(factotum->rpc(afd, "start", array of byte s).t0 != "ok"){
+ debug(DBG_AUTH, sys->sprint("auth_rpc start %s failed: %r\n", s));
+ return -1;
+ }
+
+ debug(DBG_AUTH, "trying factotum rsa keys\n");
+ for(;;){
+ (tag, value) := factotum->rpc(afd, "read", nil);
+ if(tag != "ok")
+ break;
+ textkey := string value;
+ sshio->debug(DBG_AUTH, sys->sprint("try %q\n", textkey));
+ mod := IPint.strtoip(textkey, 16);
+ m := Msg.mk(SSH_CMSG_AUTH_RSA, 16+(mod.bits()+7/8));
+ m.putipint(mod);
+ c.out <-= m;
+
+ m = sshio->recvmsg(c, -1);
+ case m.mtype {
+ SSH_SMSG_FAILURE =>
+ debug(DBG_AUTH, "\tnot accepted\n");
+ continue;
+ SSH_SMSG_AUTH_RSA_CHALLENGE =>
+ ;
+ * =>
+ sshio->badmsg(m, 0, nil);
+ }
+ chal := m.getipint();
+ p := chal.iptostr(16);
+ debug(DBG_AUTH, sys->sprint("\tgot challenge %s\n", p));
+ unpad: ref IPint;
+ if(factotum->rpc(afd, "write", array of byte p).t0 == "ok" &&
+ ((tag, value) = factotum->rpc(afd, "read", nil)).t0 == "ok"){
+ debug(DBG_AUTH, sys->sprint("\tfactotum said %q\n", string value));
+ decr := IPint.strtoip(string value, 16);
+ if(decr != nil){
+ debug(DBG_AUTH, sys->sprint("\tdecrypted %s\n", decr.iptostr(16)));
+ unpad = sshio->rsaunpad(decr);
+ }else
+ unpad = IPint.inttoip(0);
+ }else{
+ debug(DBG_AUTH, sys->sprint("\tauth_rpc write or read failed: %r\n"));
+ unpad = IPint.inttoip(0); # it will fail, we'll go round again
+ }
+ debug(DBG_AUTH, sys->sprint("\tunpadded %s\n", unpad.iptostr(16)));
+ sshio->iptorjustbe(unpad, chalbuf, 32);
+# debug(DBG_AUTH, sys->sprint("\trjusted %.*H\n", 32, chalbuf));
+ chalbuf[32:] = c.sessid[0: SESSIDLEN];
+# debug(DBG_AUTH, sys->sprint("\tappend sesskey %.*H\n", 32, chalbuf));
+ kr->md5(chalbuf, 32+SESSIDLEN, response, nil);
+
+ m = Msg.mk(SSH_CMSG_AUTH_RSA_RESPONSE, Keyring->MD5dlen);
+ m.putbytes(response, Keyring->MD5dlen);
+ c.out <-= m;
+
+ m = sshio->recvmsg(c, -1);
+ case m.mtype {
+ SSH_SMSG_FAILURE =>
+ ; # retry
+ SSH_SMSG_SUCCESS =>
+ return 0;
+ * =>
+ sshio->badmsg(m, 0, nil);
+ }
+ }
+ return -1;
+}
diff --git a/appl/cmd/ssh/authtis.b b/appl/cmd/ssh/authtis.b
new file mode 100644
index 00000000..f06c10f1
--- /dev/null
+++ b/appl/cmd/ssh/authtis.b
@@ -0,0 +1,116 @@
+implement Auth;
+
+# TO DO: add chal/resp to Factotum
+
+include "sys.m";
+ sys: Sys;
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+
+include "factotum.m";
+ factotum: Factotum;
+ Attr: import factotum;
+ findattrval: import factotum;
+
+include "sshio.m";
+ sshio: Sshio;
+ Conn, Msg: import sshio;
+ debug: import sshio;
+
+id(): int
+{
+ return SSH_AUTH_TIS;
+}
+
+init(mod: Sshio)
+{
+ sshio = mod;
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ factotum = load Factotum Factotum->PATH;
+ factotum->init();
+}
+
+firstmsg(): int
+{
+ return SSH_CMSG_AUTH_TIS;
+}
+
+authsrv(conn: ref Conn, nil: ref Msg): ref AuthInfo
+{
+ if((c := factotum->challenge(sys->sprint("proto=p9cr user=%q role=server", conn.user))) == nil){
+# sshlog("auth_challenge failed for %s", conn.user);
+ return nil;
+ }
+ s := sys->sprint("Challenge: %s\nResponse: ", c.chal);
+ m := Msg.mk(SSH_SMSG_AUTH_TIS_CHALLENGE, 4+len s);
+ m.putstring(s);
+ conn.out <-= m;
+
+ m = sshio->recvmsg(conn, 0);
+ if(m == nil)
+ return nil;
+ if(m.mtype != SSH_CMSG_AUTH_TIS_RESPONSE){
+ #
+ # apparently you can just give up on
+ # this protocol and start a new one.
+ #
+ sshio->unrecvmsg(conn, m);
+ return nil;
+ }
+
+ ai := factotum->response(c, m.getstring());
+ if(ai == nil){
+ debug(DBG_AUTH, sys->sprint("response rejected: %r\n"));
+ return nil;
+ }
+ return ref AuthInfo(ai.cuid, ai.cap);
+}
+
+auth(c: ref Conn): int
+{
+ if(!c.interactive)
+ return -1;
+
+ debug(DBG_AUTH, "try TIS\n");
+ c.out <-= Msg.mk(SSH_CMSG_AUTH_TIS, 0);
+
+ m := sshio->recvmsg(c, -1);
+ case m.mtype {
+ SSH_SMSG_FAILURE =>
+ return -1;
+ SSH_SMSG_AUTH_TIS_CHALLENGE =>
+ ;
+ * =>
+ sshio->badmsg(m, SSH_SMSG_AUTH_TIS_CHALLENGE, nil);
+ }
+
+ chal := m.getstring();
+
+ if((fd := sys->open("/dev/cons", Sys->ORDWR)) == nil)
+ sshio->error(sys->sprint("can't open /dev/cons: %r"));
+
+ sys->fprint(fd, "TIS Authentication\n%s", chal);
+ resp := array[256] of byte;
+ n := sys->read(fd, resp, len resp);
+ if(n <= 0 || resp[0] == byte '\n')
+ return -1;
+
+ m = Msg.mk(SSH_CMSG_AUTH_TIS_RESPONSE, 4+n);
+ m.put4(len resp);
+ m.putbytes(resp, n);
+ c.out <-= m;
+
+ m = sshio->recvmsg(c, -1);
+ case m.mtype {
+ SSH_SMSG_SUCCESS =>
+ return 0;
+ SSH_SMSG_FAILURE =>
+ return -1;
+ * =>
+ sshio->badmsg(m, 0, nil);
+ return -1;
+ }
+}
diff --git a/appl/cmd/ssh/cipher3des.b b/appl/cmd/ssh/cipher3des.b
new file mode 100644
index 00000000..36359fb4
--- /dev/null
+++ b/appl/cmd/ssh/cipher3des.b
@@ -0,0 +1,46 @@
+implement Cipher;
+
+include "sys.m";
+
+include "keyring.m";
+ kr: Keyring;
+ DESstate: import kr;
+
+include "sshio.m";
+
+Cipherstate: adt
+{
+ enc: array of ref DESstate;
+ dec: array of ref DESstate;
+};
+
+cs: ref Cipherstate;
+
+id(): int
+{
+ return SSH_CIPHER_3DES;
+}
+
+init(key: array of byte, nil: int)
+{
+ kr = load Keyring Keyring->PATH;
+ cs = ref Cipherstate(array[3] of ref DESstate, array[3] of ref DESstate);
+ for(i := 0; i < 3; i++){
+ cs.enc[i] = kr->dessetup(key[i*8:], nil);
+ cs.dec[i] = kr->dessetup(key[i*8:], nil);
+ }
+}
+
+encrypt(buf: array of byte, nbuf: int)
+{
+ kr->descbc(cs.enc[0], buf, nbuf, Keyring->Encrypt);
+ kr->descbc(cs.enc[1], buf, nbuf, Keyring->Decrypt);
+ kr->descbc(cs.enc[2], buf, nbuf, Keyring->Encrypt);
+}
+
+decrypt(buf: array of byte, nbuf: int)
+{
+ kr->descbc(cs.dec[2], buf, nbuf, Keyring->Decrypt);
+ kr->descbc(cs.dec[1], buf, nbuf, Keyring->Encrypt);
+ kr->descbc(cs.dec[0], buf, nbuf, Keyring->Decrypt);
+}
diff --git a/appl/cmd/ssh/cipherblowfish.b b/appl/cmd/ssh/cipherblowfish.b
new file mode 100644
index 00000000..7e6210ab
--- /dev/null
+++ b/appl/cmd/ssh/cipherblowfish.b
@@ -0,0 +1,38 @@
+implement Cipher;
+
+include "sys.m";
+
+include "keyring.m";
+ kr: Keyring;
+ BFstate: import kr;
+
+include "sshio.m";
+
+Cipherstate: adt
+{
+ enc: ref BFstate;
+ dec: ref BFstate;
+};
+
+cs: ref Cipherstate;
+
+id(): int
+{
+ return SSH_CIPHER_BLOWFISH;
+}
+
+init(key: array of byte, nil: int)
+{
+ kr = load Keyring Keyring->PATH;
+ cs = ref Cipherstate(kr->blowfishsetup(key, nil), kr->blowfishsetup(key, nil));
+}
+
+encrypt(buf: array of byte, nbuf: int)
+{
+ kr->blowfishcbc(cs.enc, buf, nbuf, Keyring->Encrypt);
+}
+
+decrypt(buf: array of byte, nbuf: int)
+{
+ kr->blowfishcbc(cs.dec, buf, nbuf, Keyring->Decrypt);
+}
diff --git a/appl/cmd/ssh/cipherdes.b b/appl/cmd/ssh/cipherdes.b
new file mode 100644
index 00000000..17456848
--- /dev/null
+++ b/appl/cmd/ssh/cipherdes.b
@@ -0,0 +1,38 @@
+implement Cipher;
+
+include "sys.m";
+
+include "keyring.m";
+ kr: Keyring;
+ DESstate: import kr;
+
+include "sshio.m";
+
+Cipherstate: adt
+{
+ enc: ref DESstate;
+ dec: ref DESstate;
+};
+
+cs: ref Cipherstate;
+
+id(): int
+{
+ return SSH_CIPHER_DES;
+}
+
+init(key: array of byte, nil: int)
+{
+ kr = load Keyring Keyring->PATH;
+ cs = ref Cipherstate(kr->dessetup(key, nil), kr->dessetup(key, nil));
+}
+
+encrypt(buf: array of byte, nbuf: int)
+{
+ kr->descbc(cs.enc, buf, nbuf, Keyring->Encrypt);
+}
+
+decrypt(buf: array of byte, nbuf: int)
+{
+ kr->descbc(cs.dec, buf, nbuf, Keyring->Decrypt);
+}
diff --git a/appl/cmd/ssh/ciphernone.b b/appl/cmd/ssh/ciphernone.b
new file mode 100644
index 00000000..f4aed718
--- /dev/null
+++ b/appl/cmd/ssh/ciphernone.b
@@ -0,0 +1,24 @@
+implement Cipher;
+
+include "sys.m";
+
+include "keyring.m";
+
+include "sshio.m";
+
+id(): int
+{
+ return SSH_CIPHER_NONE;
+}
+
+init(nil: array of byte, nil: int)
+{
+}
+
+encrypt(nil: array of byte, nil: int)
+{
+}
+
+decrypt(nil: array of byte, nil: int)
+{
+}
diff --git a/appl/cmd/ssh/cipherrc4.b b/appl/cmd/ssh/cipherrc4.b
new file mode 100644
index 00000000..fc6378ef
--- /dev/null
+++ b/appl/cmd/ssh/cipherrc4.b
@@ -0,0 +1,41 @@
+implement Cipher;
+
+include "sys.m";
+
+include "keyring.m";
+ kr: Keyring;
+ RC4state: import kr;
+
+include "sshio.m";
+
+Cipherstate: adt
+{
+ enc: ref RC4state;
+ dec: ref RC4state;
+};
+
+cs: ref Cipherstate;
+
+id(): int
+{
+ return SSH_CIPHER_RC4;
+}
+
+init(key: array of byte, isserver: int)
+{
+ kr = load Keyring Keyring->PATH;
+ if(isserver)
+ cs = ref Cipherstate(kr->rc4setup(key[0:16]), kr->rc4setup(key[16:32]));
+ else
+ cs = ref Cipherstate(kr->rc4setup(key[16:32]), kr->rc4setup(key[0:16]));
+}
+
+encrypt(buf: array of byte, nbuf: int)
+{
+ kr->rc4(cs.enc, buf, nbuf);
+}
+
+decrypt(buf: array of byte, nbuf: int)
+{
+ kr->rc4(cs.dec, buf, nbuf);
+}
diff --git a/appl/cmd/ssh/cons.b b/appl/cmd/ssh/cons.b
new file mode 100644
index 00000000..bea7733d
--- /dev/null
+++ b/appl/cmd/ssh/cons.b
@@ -0,0 +1,116 @@
+# promptstring
+
+RAWON_STR := "*";
+
+RAWON : con 0;
+RAWOFF : con 1;
+
+promptstring(prompt, def: string, mode: int): string
+{
+ if(mode == RAWON || def == nil)
+ sys->fprint(stdout, "%s: ", prompt);
+ else
+ sys->fprint(stdout, "%s [%s]: ", prompt, def);
+ (eof, resp) := readline(stdin, mode);
+ if(eof)
+ exit;
+ if(resp == nil)
+ return def;
+ return resp;
+}
+
+readline(fd: ref Sys->FD, mode: int): (int, string)
+{
+ i: int;
+ eof: int;
+ fdctl: ref Sys->FD;
+
+ eof = 0;
+ buf := array[128] of byte;
+ tmp := array[128] of byte;
+
+ if(mode == RAWON){
+ fdctl = sys->open("/dev/consctl", sys->OWRITE);
+ if(fdctl == nil || sys->write(fdctl,array of byte "rawon",5) != 5){
+ sys->fprint(stderr, "unable to change console mode");
+ return (1,nil);
+ }
+ }
+
+ for(sofar := 0; sofar < 128; sofar += i){
+ i = sys->read(fd, tmp, 128 - sofar);
+ if(i <= 0){
+ eof = 1;
+ break;
+ }
+ if(tmp[i-1] == byte '\n'){
+ for(j := 0; j < i-1; j++){
+ buf[sofar+j] = tmp[j];
+ if(mode == RAWON && RAWON_STR != nil)
+ sys->write(stdout,array of byte RAWON_STR,1);
+ }
+ sofar += j;
+ if(mode == RAWON)
+ sys->write(stdout,array of byte "\n",1);
+ break;
+ }
+ else {
+ for(j := 0; j < i; j++){
+ buf[sofar+j] = tmp[j];
+ if(mode == RAWON && RAWON_STR != nil)
+ sys->write(stdout,array of byte RAWON_STR,1);
+ }
+ }
+ }
+ if(mode == RAWON)
+ sys->write(fdctl,array of byte "rawoff",6);
+ return (eof, string buf[0:sofar]);
+}
+
+# from keyfs
+
+readconsline(prompt: string, raw: int): (string, string)
+{
+ fd := sys->open("/dev/cons", Sys->ORDWR);
+ if(fd == nil)
+ return (nil, sys->sprint("can't open cons: %r"));
+ sys->fprint(fd, "%s", prompt);
+ fdctl: ref Sys->FD;
+ if(raw){
+ fdctl = sys->open("/dev/consctl", sys->OWRITE);
+ if(fdctl == nil || sys->fprint(fdctl, "rawon") < 0)
+ return (nil, sys->sprint("can't open consctl: %r"));
+ }
+ line := array[256] of byte;
+ o := 0;
+ err: string;
+ buf := array[1] of byte;
+ Read:
+ while((r := sys->read(fd, buf, len buf)) > 0){
+ c := int buf[0];
+ case c {
+ 16r7F =>
+ err = "interrupt";
+ break Read;
+ '\b' =>
+ if(o > 0)
+ o--;
+ '\n' or '\r' or 16r4 =>
+ break Read;
+ * =>
+ if(o > len line){
+ err = "line too long";
+ break Read;
+ }
+ line[o++] = byte c;
+ }
+ }
+ sys->fprint(fd, "\n");
+ if(r < 0)
+ err = sys->sprint("can't read cons: %r");
+ if(raw)
+ sys->fprint(fdctl, "rawoff");
+ if(err != nil)
+ return (nil, err);
+ return (string line[0:o], err);
+}
diff --git a/appl/cmd/ssh/mkfile b/appl/cmd/ssh/mkfile
new file mode 100644
index 00000000..afb4c903
--- /dev/null
+++ b/appl/cmd/ssh/mkfile
@@ -0,0 +1,29 @@
+<../../../mkconfig
+
+TARG=\
+ authpassword.dis\
+ authrsa.dis\
+ authtis.dis\
+ cipher3des.dis\
+ cipherblowfish.dis\
+ cipherdes.dis\
+ ciphernone.dis\
+ cipherrc4.dis\
+ sshio.dis\
+ sshserve.dis\
+# ssh.dis\
+
+SYSMODULES=\
+ arg.m\
+ keyring.m\
+ security.m\
+ rand.m\
+ sys.m\
+ draw.m\
+
+MODULES=\
+ sshio.m\
+
+DISBIN=$ROOT/dis/ssh
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/ssh/shcons.b b/appl/cmd/ssh/shcons.b
new file mode 100644
index 00000000..65fdaf66
--- /dev/null
+++ b/appl/cmd/ssh/shcons.b
@@ -0,0 +1,296 @@
+implement Cons;
+
+# possibly useful bits from wm/sh
+
+include "sys.m";
+ sys: Sys;
+ FileIO: import sys;
+
+include "draw.m";
+
+include "sh.m";
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+
+Cons: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+BSW: con 23; # ^w bacspace word
+BSL: con 21; # ^u backspace line
+EOT: con 4; # ^d end of file
+ESC: con 27; # hold mode
+
+Rdreq: adt
+{
+ off: int;
+ nbytes: int;
+ fid: int;
+ rc: chan of (array of byte, string);
+};
+
+rdreq: list of Rdreq;
+rawon := 0;
+rawinput := "";
+partialread: array of byte;
+
+events: list of string;
+evrdreq: list of Rdreq;
+
+init(ctxt: ref Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ arg := load Arg Arg->PATH;
+ arg->init(args);
+ arg->setusage("aux/cons [-ilxvn] [-c command] [file [args...]]");
+
+ sys->pctl(Sys->FORKFD | Sys->FORKNS | Sys->NEWPGRP | Sys->FORKENV, nil);
+
+ shargs: list of string;
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'c' =>
+ shargs = arg->earg() :: "-c" :: shargs;
+ 'i' or 'l' or 'x' or 'v' or 'n' =>
+ shargs = sys->sprint("-%c", opt) :: shargs;
+ * =>
+ arg->usage();
+ }
+ }
+ args = arg->args();
+ for (; shargs != nil; shargs = tl shargs)
+ args = hd shargs :: args;
+
+ ioc := chan of (int, ref FileIO, ref FileIO, string);
+ spawn newsh(ctxt, ioc, args);
+
+ (nil, file, filectl, consfile) := <-ioc;
+ if(file == nil || filectl == nil || shctl == nil) {
+ sys->fprint(sys->fildes(2), "cons: shell /dev/cons creation failed\n");
+ return;
+ }
+
+ for(;;) alt {
+ c := <-keys => # TO DO: input arriving from remote ...; echo, edit ...
+ char := c[1];
+ if(char == '\\')
+ char = c[2];
+ if(rawon){
+ rawinput[len rawinput] = char;
+ sendinput(t);
+ break;
+ }
+ case char {
+ * =>
+ cmd(t, ".ft.t insert insert "+c);
+ '\r' =>
+ ; # TO DO
+ '\n' or
+ EOT =>
+ cmd(t, ".ft.t insert insert "+c);
+ sendinput(t);
+ '\b' =>
+ cmd(t, ".ft.t tkTextDelIns -c");
+ 'u'& 8r37 =>
+ cmd(t, ".ft.t tkTextDelIns -l");
+ 'w'& 8r37 =>
+ cmd(t, ".ft.t tkTextDelIns -w");
+ }
+
+ rdrpc := <-filectl.read =>
+ if(rdrpc.rc != nil)
+ rdrpc.rc <-= (nil, "permission denied");
+
+ (nil, data, nil, wc) := <-filectl.write =>
+ if(wc == nil) {
+ # consctl closed - revert to cooked mode
+ rawon = 0;
+ continue;
+ }
+ (nc, cmdlst) := sys->tokenize(string data, " \n");
+ if(nc == 1) {
+ case hd cmdlst {
+ "rawon" =>
+ rawon = 1;
+ rawinput = "";
+ # discard previous input
+ advance := string (len tk->cmd(t, ".ft.t get outpoint end") +1);
+ cmd(t, ".ft.t mark set outpoint outpoint+" + advance + "chars");
+ partialread = nil;
+ "rawoff" =>
+ rawon = 0;
+ partialread = nil;
+ "holdon" or "holdoff" =>
+ ;
+ * =>
+ wc <-= (0, "unknown control message");
+ continue;
+ }
+ wc <-= (len data, nil);
+ continue;
+ }
+ wc <-= (0, "unknown control message");
+
+ rdrpc := <-file.read =>
+ if(rdrpc.rc == nil) {
+ (ok, nil) := sys->stat(consfile);
+ if (ok < 0)
+ return;
+ continue;
+ }
+ append(rdrpc);
+ sendinput(t);
+
+ (nil, data, nil, wc) := <-file.write =>
+ if(wc == nil) {
+ (ok, nil) := sys->stat(consfile);
+ if (ok < 0)
+ return;
+ continue;
+ }
+ # TO DO: data from cons; edit (eg, add \r) and forward to remote
+ wc <-= (len data, nil);
+ data = nil;
+ }
+}
+
+RPCread: type (int, int, int, chan of (array of byte, string));
+
+append(r: RPCread)
+{
+ t := r :: nil;
+ while(rdreq != nil) {
+ t = hd rdreq :: t;
+ rdreq = tl rdreq;
+ }
+ rdreq = t;
+}
+
+sendinput(t: ref Tk->Toplevel)
+{
+ input: string;
+ if(rawon)
+ input = rawinput;
+ else
+ input = tk->cmd(t, ".ft.t get outpoint end");
+ if(rdreq == nil || (input == nil && len partialread == 0))
+ return;
+ r := hd rdreq;
+ (chars, bytes, partial) := triminput(r.nbytes, input, partialread);
+ if(bytes == nil)
+ return; # no terminator yet
+ rdreq = tl rdreq;
+
+ alt {
+ r.rc <-= (bytes, nil) =>
+ # check that it really was sent
+ alt {
+ r.rc <-= (nil, nil) =>
+ ;
+ * =>
+ return;
+ }
+ * =>
+ return; # requester has disappeared; ignore his request and try another
+ }
+ if(rawon)
+ rawinput = rawinput[chars:];
+ else
+ cmd(t, ".ft.t mark set outpoint outpoint+" + string chars + "chars");
+ partialread = partial;
+}
+
+# read at most nr bytes from the input string, returning the number of characters
+# consumed, the bytes to be read, and any remaining bytes from a partially
+# read multibyte UTF character.
+triminput(nr: int, input: string, partial: array of byte): (int, array of byte, array of byte)
+{
+ if(nr <= len partial)
+ return (0, partial[0:nr], partial[nr:]);
+ if(holding)
+ return (0, nil, partial);
+
+ # keep the array bounds within sensible limits
+ if(nr > len input*Sys->UTFmax)
+ nr = len input*Sys->UTFmax;
+ buf := array[nr+Sys->UTFmax] of byte;
+ t := len partial;
+ buf[0:] = partial;
+
+ hold := !rawon;
+ i := 0;
+ while(i < len input){
+ c := input[i++];
+ # special case for ^D - don't read the actual ^D character
+ if(!rawon && c == EOT){
+ hold = 0;
+ break;
+ }
+
+ t += sys->char2byte(c, buf, t);
+ if(c == '\n' && !rawon){
+ hold = 0;
+ break;
+ }
+ if(t >= nr)
+ break;
+ }
+ if(hold){
+ for(j := i; j < len input; j++){
+ c := input[j];
+ if(c == '\n' || c == EOT)
+ break;
+ }
+ if(j == len input)
+ return (0, nil, partial);
+ # strip ^D when next read would read it, otherwise
+ # we'll give premature EOF.
+ if(i == j && input[i] == EOT)
+ i++;
+ }
+ partial = nil;
+ if(t > nr){
+ partial = buf[nr:t];
+ t = nr;
+ }
+ return (i, buf[0:t], partial);
+}
+
+newsh(ctxt: ref Context, ioc: chan of (int, ref FileIO, ref FileIO, string, ref FileIO), args: list of string)
+{
+ pid := sys->pctl(sys->NEWFD, nil);
+
+ sh := load Command "/dis/sh.dis";
+ if(sh == nil) {
+ ioc <-= (0, nil, nil, nil);
+ return;
+ }
+
+ tty := "cons."+string pid;
+
+ sys->bind("#s","/chan",Sys->MBEFORE);
+ fio := sys->file2chan("/chan", tty);
+ fioctl := sys->file2chan("/chan", tty + "ctl");
+ ioc <-= (pid, fio, fioctl, "/chan/"+tty);
+ if(fio == nil || fioctl == nil)
+ return;
+
+ sys->bind("/chan/"+tty, "/dev/cons", sys->MREPL);
+ sys->bind("/chan/"+tty+"ctl", "/dev/consctl", sys->MREPL);
+
+ fd0 := sys->open("/dev/cons", Sys->OREAD|Sys->ORCLOSE);
+ fd1 := sys->open("/dev/cons", Sys->OWRITE);
+ fd2 := sys->open("/dev/cons", Sys->OWRITE);
+
+ {
+ sh->init(ctxt, "sh" :: "-n" :: args);
+ }exception{
+ "fail:*" =>
+ exit;
+ }
+}
diff --git a/appl/cmd/ssh/sshio.b b/appl/cmd/ssh/sshio.b
new file mode 100644
index 00000000..405b444d
--- /dev/null
+++ b/appl/cmd/ssh/sshio.b
@@ -0,0 +1,582 @@
+implement Sshio;
+
+include "sys.m";
+ sys: Sys;
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+ PK, SK: import kr;
+
+include "sshio.m";
+
+include "rand.m";
+ rand: Rand;
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ rand = load Rand Rand->PATH;
+ rand->init(sys->millisec());
+}
+
+msgnames := array[45] of {
+ "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",
+ "SSH_CMSG_REQUEST_PTY", # 10
+ "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",
+ "SSH_SMSG_EXITSTATUS", # 20
+ "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",
+ "SSH_CMSG_AGENT_REQUEST_FORWARDING", # 30
+ "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",
+ "SSH_SMSG_AUTH_TIS_CHALLENGE", # 40
+ "SSH_CMSG_AUTH_TIS_RESPONSE",
+ "SSH_CMSG_AUTH_KERBEROS",
+ "SSH_SMSG_AUTH_KERBEROS_RESPONSE",
+ "SSH_CMSG_HAVE_KERBEROS_TGT",
+};
+
+Conn.mk(host: string, fd: ref Sys->FD): ref Conn
+{
+ c := ref Conn;
+ c.host = host;
+ c.sesskey = array[SESSKEYLEN] of byte;
+ c.sessid = array[SESSIDLEN] of byte;
+ c.in = chan of (ref Msg, string);
+ c.out = chan of ref Msg;
+ c.flags = 0;
+ c.interactive = 0;
+ sync := chan of int;
+ spawn msgreader(c, fd, sync);
+ <-sync;
+ spawn msgwriter(c, fd, sync);
+ <-sync;
+ return c;
+}
+
+Conn.setkey(c: self ref Conn, key: ref PK.RSA)
+{
+ c.hostkey = key;
+}
+
+msgreader(c: ref Conn, fd: ref Sys->FD, sync: chan of int)
+{
+ sys->pctl(Sys->NEWFD, 2 :: fd.fd :: nil);
+ sync <-= 1;
+ fd = sys->fildes(fd.fd);
+ for(;;){
+ m := readmsg(c, fd);
+ if(m == nil){
+ c.in <-= (nil, sys->sprint("%r"));
+ break;
+ }
+ debug(DBG_PROTO, sys->sprint("<-[%d] %s\n", m.ep-m.rp, m.fulltext()));
+ case m.mtype {
+ SSH_MSG_IGNORE =>
+ ;
+ SSH_MSG_DEBUG =>
+ debug(DBG_PROTO, sys->sprint("remote DEBUG: %s\n", m.getstring()));
+ * =>
+ c.in <-= (m, nil);
+ }
+ }
+}
+
+msgwriter(c: ref Conn, fd: ref Sys->FD, sync: chan of int)
+{
+ sys->pctl(Sys->NEWFD, 2 :: fd.fd :: nil);
+ sync <-= 1;
+ fd = sys->fildes(fd.fd);
+ while((m := <-c.out) != nil)
+ if(writemsg(c, m, fd) < 0){
+ while(<-c.out != nil)
+ {} # flush
+ exit;
+ }
+}
+
+#
+# read initial SSH-m.n-comment line
+#
+readversion(fd: ref Sys->FD): (int, int, string)
+{
+ buf := array[128] of byte;
+ if((n := readstrnl(fd, buf, len buf)) < 0)
+ return (-1, -1, sys->sprint("error reading version: %r"));
+ # id string is "SSH-m.n-comment". We need m=1, n>=5.
+ s := string buf[0: n];
+ (nf, fld) := sys->tokenize(s, "-\r\n");
+ if(nf < 3 || hd fld != "SSH")
+ return (-1, -1, sys->sprint("unexpected protocol reply: %s", s));
+ (nf, fld) = sys->tokenize(hd tl fld, ".");
+ if(nf < 2)
+ return (-1, -1, "invalid SSH version string in "+s);
+ return (1, int hd tl fld, s);
+}
+
+calcsessid(hostmod: ref IPint, servermod: ref IPint, cookie: array of byte): array of byte
+{
+ b1 := hostmod.iptobebytes();
+ b2 := servermod.iptobebytes();
+ buf := array[len b1+len b2+COOKIELEN] of byte;
+ buf[0:] = b1;
+ buf[len b1:] = b2;
+ buf[len b1+len b2:] = cookie[0: COOKIELEN];
+ sessid := array[Keyring->MD5dlen] of byte;
+ kr->md5(buf, len buf, sessid, nil);
+ return sessid;
+}
+
+Msg.text(m: self ref Msg): string
+{
+ if(0 <= m.mtype && m.mtype < len msgnames)
+ return msgnames[m.mtype];
+ return sys->sprint("<unknown type %d>", m.mtype);
+}
+
+Msg.fulltext(m: self ref Msg): string
+{
+ s := m.text();
+ n := m.ep;
+ if(n > 64)
+ n = 64;
+ for(i := 0; i < n; i++)
+ s += sys->sprint(" %.2ux", int m.data[i]);
+ if(n != m.ep)
+ s += " ...";
+ return s;
+}
+
+badmsg(m: ref Msg, want: int, errmsg: string)
+{
+ if(m == nil)
+ s := sys->sprint("<early eof: %s>", errmsg);
+ else
+ s = m.text();
+ if(want)
+ error(sys->sprint("got %s message expecting %s", s, msgnames[want]));
+ error(sys->sprint("got unexpected %s message", s));
+}
+
+Msg.mk(mtype: int, length: int): ref Msg
+{
+ if(length > 256*1024)
+ raise "message too large";
+ return ref Msg(mtype, array[4+8+1+length+4] of byte, 0, 0, length);
+}
+
+# used by auth tis
+unrecvmsg(c: ref Conn, m: ref Msg)
+{
+ debug(DBG_PROTO, sys->sprint("unreceived %s len %d\n", msgnames[m.mtype], m.ep-m.rp));
+ c.unget = m;
+}
+
+readmsg(c: ref Conn, fd: ref Sys->FD): ref Msg
+{
+ if(c.unget != nil){ # TO DO: assumes state of processes ensures exclusive access
+ m := c.unget;
+ c.unget = nil;
+ return m;
+ }
+ buf := array[4] of byte;
+ if((n := sys->readn(fd, buf, len buf)) != len buf){
+ if(n < 0)
+ sys->werrstr("short net read: %r");
+ else
+ sys->werrstr("short net read");
+ return nil;
+ }
+ length := get4(buf, 0);
+ if(length < 5 || length > 256*1024){
+ sys->werrstr(sys->sprint("implausible packet length: %.8ux", length));
+ return nil;
+ }
+ pad := 8-length%8;
+ m := ref Msg(0, array[pad+length] of byte, pad, 0, pad+length-4);
+ if(sys->readn(fd, m.data, len m.data) != len m.data){
+ sys->werrstr(sys->sprint("short net read: %r"));
+ return nil;
+ }
+ if(c.cipher != nil)
+ c.cipher->decrypt(m.data, length+pad);
+ crc := sum32(0, m.data, m.ep);
+ crc0 := get4(m.data, m.ep);
+ if(crc != crc0){
+ sys->werrstr(sys->sprint("bad crc %#ux != %#ux (packet length %ud)", crc, crc0, length));
+ return nil;
+ }
+ m.mtype = int m.data[m.rp++];
+ return m;
+}
+
+recvmsg(c: ref Conn, mtype: int): ref Msg
+{
+ (m, errmsg) := <-c.in;
+ if(mtype == 0){
+ # no checking
+ }else if(mtype == -1){
+ # must not be nil
+ if(m == nil)
+ error(Ehangup);
+ }else if(m == nil || m.mtype != mtype) # must be given type
+ badmsg(m, mtype, errmsg);
+ return m;
+}
+
+writemsg(c: ref Conn, m: ref Msg, fd: ref Sys->FD): int
+{
+ datalen := m.wp;
+ length := datalen+1+4; # will add type and crc
+ pad := 8-length%8;
+ debug(DBG_PROTO, sys->sprint("->[%d] %s\n", datalen, m.fulltext()));
+ m.data[4+pad+1:] = m.data[0: datalen]; # slide data to correct position (is this guaranteed?) TO DO
+ put4(m.data, 0, length);
+ p := 4;
+ if(c.cipher != nil)
+ for(i := 0; i < pad; i++)
+ m.data[p++] = byte fastrand();
+ else{
+ for(i = 0; i < pad; i++)
+ m.data[p++] = byte 0;
+ }
+ m.data[p++] = byte m.mtype;
+ # data already in position
+ p += datalen;
+ crc := sum32(0, m.data[4:], pad+1+datalen);
+ put4(m.data, p, crc);
+ p += 4;
+ if(c.cipher != nil)
+ c.cipher->encrypt(m.data[4:], length+pad);
+ if(sys->write(fd, m.data, p) != p)
+ return -1;
+ return 0;
+}
+
+Msg.get1(m: self ref Msg): int
+{
+ if(m.rp >= m.ep)
+ raise Edecode;
+ return int m.data[m.rp++];
+}
+
+Msg.get2(m: self ref Msg): int
+{
+ if(m.rp+2 > m.ep)
+ raise Edecode;
+ x := (int m.data[m.rp+0]<<8) | int m.data[m.rp+1];
+ m.rp += 2;
+ return x;
+}
+
+Msg.get4(m: self ref Msg): int
+{
+ if(m.rp+4 > m.ep)
+ raise Edecode;
+ x := int m.data[m.rp+0]<<24|int m.data[m.rp+1]<<16|int m.data[m.rp+2]<<8|int m.data[m.rp+3];
+ m.rp += 4;
+ return x;
+}
+
+Msg.getarray(m: self ref Msg): array of byte
+{
+ length := m.get4();
+ if(m.rp+length > m.ep)
+ raise Edecode;
+ p := m.data[m.rp: m.rp+length];
+ m.rp += length;
+ return p;
+}
+
+Msg.getstring(m: self ref Msg): string
+{
+ return string m.getarray();
+}
+
+Msg.getbytes(m: self ref Msg, n: int): array of byte
+{
+ if(m.rp+n > m.ep)
+ raise Edecode;
+ p := m.data[m.rp: m.rp+n];
+ m.rp += n;
+ return p;
+}
+
+Msg.getipint(m: self ref Msg): ref IPint
+{
+ n := (m.get2()+7)/8; # get2 returns # bits
+ return IPint.bebytestoip(m.getbytes(n));
+}
+
+Msg.getpk(m: self ref Msg): ref PK.RSA
+{
+ m.get4();
+ ek := m.getipint();
+ n := m.getipint();
+ return ref PK.RSA(n, ek);
+}
+
+Msg.put1(m: self ref Msg, x: int)
+{
+ if(m.wp >= m.ep)
+ raise Eencode;
+ m.data[m.wp++] = byte x;
+}
+
+Msg.put2(m: self ref Msg, x: int)
+{
+ if(m.wp+2 > m.ep)
+ raise Eencode;
+ (m.data[m.wp+0], m.data[m.wp+1]) = (byte (x>>8), byte x);
+ m.wp += 2;
+}
+
+Msg.put4(m: self ref Msg, x: int)
+{
+ if(m.wp+4 > m.ep)
+ raise Eencode;
+ (m.data[m.wp+0], m.data[m.wp+1], m.data[m.wp+2], m.data[m.wp+3]) = (byte (x>>24), byte (x>>16), byte (x>>8), byte x);
+ m.wp += 4;
+}
+
+Msg.putstring(m: self ref Msg, s: string)
+{
+ b := array of byte s;
+ m.put4(len b);
+ m.putbytes(b, len b);
+}
+
+Msg.putbytes(m: self ref Msg, a: array of byte, n: int)
+{
+ if(m.wp+n > m.ep)
+ raise Eencode;
+ m.data[m.wp:] = a[0: n];
+ m.wp += n;
+}
+
+Msg.putipint(m: self ref Msg, b: ref IPint)
+{
+ bits := b.bits();
+ m.put2(bits);
+# n := (bits+7)/8;
+ ba := b.iptobebytes();
+ n := len ba;
+ if(m.wp+n > m.ep)
+ raise Eencode;
+ m.data[m.wp:] = ba;
+ m.wp += n;
+}
+
+Msg.putpk(m: self ref Msg, key: ref PK.RSA)
+{
+ m.put4(key.n.bits());
+ m.putipint(key.ek);
+ m.putipint(key.n);
+}
+
+crctab := array[256] of int;
+
+initsum32()
+{
+ poly := int 16redb88320;
+ for(i := 0; i < 256; i++){
+ crc := i;
+ for(j := 0; j < 8; j++)
+ if(crc&1)
+ crc = ((crc>>1) & int ~16r80000000)^poly; # need unsigned shift
+ else
+ crc = (crc>>1) & int ~16r80000000;
+ crctab[i] = crc;
+ }
+}
+
+first_38: int = 1;
+
+sum32(lcrc: int, buf: array of byte, n: int): int
+{
+ crc := lcrc;
+ if(first_38){
+ first_38 = 0;
+ initsum32();
+ }
+ s := 0;
+ while(n-- > 0)
+ crc = crctab[(crc^int buf[s++])&16rff]^((crc>>8)&int ~16rFF000000);
+ return crc;
+}
+
+erase(b: array of byte)
+{
+ for(i := 0; i < len b; i++)
+ b[i] = byte 0;
+}
+
+#
+# PKCS#1 padding
+#
+rsapad(b: ref IPint, n: int): ref IPint
+{
+ a := b.iptobebytes();
+ pad := n - len a - 3;
+ if(pad < 0)
+ error("value too large to pad"); # can't happen if keys are required size
+ buf := array[n] of byte;
+ buf[0] = byte 0;
+ buf[1] = byte 2;
+ for(i := 2; --pad >= 0; i++)
+ buf[i] = byte (1+fastrand()%255);
+ buf[i++] = byte 0;
+ buf[i:] = a;
+ c := IPint.bebytestoip(buf);
+ erase(buf);
+ erase(a);
+ return c;
+}
+
+rsaunpad(b: ref IPint): ref IPint
+{
+ buf := b.iptobebytes();
+ i := 0;
+ if(buf[0] == byte 0)
+ i++;
+ if(buf[i] != byte 2)
+ error("bad data in rsaunpad");
+ for(; i < len buf; i++)
+ if(buf[i] == byte 0)
+ break;
+ c := IPint.bebytestoip(buf[i:]);
+ erase(buf);
+ return c;
+}
+
+rsaencryptbuf(key: ref PK.RSA, buf: array of byte, nbuf: int): ref IPint
+{
+ n := (key.n.bits()+7)/8;
+ a := IPint.bebytestoip(buf[0: nbuf]);
+ b := rsapad(a, n);
+ return kr->rsaencrypt(key, b);
+}
+
+iptorjustbe(b: ref IPint, buf: array of byte, length: int)
+{
+ a := b.iptobebytes();
+ if(len a < length){
+ length -= len a;
+ erase(buf[0: length]);
+ buf[length:] = a;
+ }else
+ buf[0:] = a[0: length];
+ erase(a);
+}
+
+hex(a: array of byte): string
+{
+ s := "";
+ for(i := 0; i < len a; i++)
+ s += sys->sprint("%.2ux", int a[i]);
+ return s;
+}
+
+debug(n: int, s: string)
+{
+ sys->fprint(sys->fildes(2), "debug: %s", s);
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "error: %s\n", s);
+ raise "error";
+}
+
+rsagen(bits: int): ref SK.RSA
+{
+ return kr->rsagen(bits, 6, 0);
+}
+
+rsaencrypt(key: ref PK.RSA, b: ref IPint): ref IPint
+{
+ return kr->rsaencrypt(key, b);
+}
+
+rsadecrypt(key: ref SK.RSA, b: ref IPint): ref IPint
+{
+ return kr->rsadecrypt(key, b);
+}
+
+fastrand(): int
+{
+ return int rand->bigrand(4294967295);
+}
+
+readstrnl(fd: ref Sys->FD, buf: array of byte, nbuf: int): int
+{
+ for(i := 0; i < nbuf; i++)
+ case sys->read(fd, buf[i:], 1) {
+ -1 =>
+ return -1;
+ 0 =>
+ sys->werrstr("unexpected EOF");
+ return -1;
+ * =>
+ if(buf[i] == byte '\n')
+ return i;
+ }
+ sys->werrstr("line too long");
+ return -1;
+}
+
+eqbytes(a: array of byte, b: array of byte, n: int): int
+{
+ if(len a > n || len b > n)
+ return 0;
+ for(i := 0; i < n; i++)
+ if(a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+get4(a: array of byte, o: int): int
+{
+ return int a[o+0]<<24 | int a[o+1]<<16 | int a[o+2]<<8 | int a[o+3];
+}
+
+put4(a: array of byte, o: int, v: int)
+{
+ a[o+0] = byte (v>>24);
+ a[o+1] = byte (v>>16);
+ a[o+2] = byte (v>>8);
+ a[o+3] = byte v;
+}
diff --git a/appl/cmd/ssh/sshio.m b/appl/cmd/ssh/sshio.m
new file mode 100644
index 00000000..3532dce0
--- /dev/null
+++ b/appl/cmd/ssh/sshio.m
@@ -0,0 +1,194 @@
+
+ # internal debugging flags
+ DBG, DBG_CRYPTO, DBG_PACKET, DBG_AUTH, DBG_PROC, DBG_PROTO, DBG_IO, DBG_SCP: con 1<<iota;
+
+ # protocol packet types
+ 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: con iota;
+
+ SSH_MSG_ERROR: con -1;
+
+ # protocol flags
+ SSH_PROTOFLAG_SCREEN_NUMBER: con 1<<0;
+ SSH_PROTOFLAG_HOST_IN_FWD_OPEN: con 1<<1;
+
+ # agent protocol packet types
+ SSH_AGENTC_NONE,
+ 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: con iota;
+
+ # protocol constants
+ SSH_MAX_DATA: con 256*1024;
+ SSH_MAX_MSG: con SSH_MAX_DATA+4;
+ SESSKEYLEN: con 32;
+ SESSIDLEN: con 16;
+ COOKIELEN: con 8;
+
+ # crypto ids
+ SSH_CIPHER_NONE,
+ SSH_CIPHER_IDEA,
+ SSH_CIPHER_DES,
+ SSH_CIPHER_3DES,
+ SSH_CIPHER_TSS,
+ SSH_CIPHER_RC4,
+ SSH_CIPHER_BLOWFISH: con iota;
+
+ # auth method ids
+ SSH_AUTH_RHOSTS,
+ SSH_AUTH_RSA,
+ SSH_AUTH_PASSWORD,
+ SSH_AUTH_RHOSTS_RSA,
+ SSH_AUTH_TIS,
+ SSH_AUTH_USER_RSA: con 1+iota;
+
+Edecode: con "error decoding input packet";
+Eencode: con "out of space encoding output packet (BUG)";
+Ehangup: con "hungup connection";
+Ememory: con "out of memory";
+
+Cipher: module
+{
+ id: fn(): int;
+ init: fn(key: array of byte, isserver: int);
+ encrypt: fn(a: array of byte, n: int);
+ decrypt: fn(a: array of byte, n: int);
+};
+
+Auth: module
+{
+ AuthInfo: adt{
+ user: string;
+ cap: string;
+ };
+
+ id: fn(): int;
+ firstmsg: fn(): int;
+ init: fn(nil: Sshio);
+ authsrv: fn(nil: ref Sshio->Conn, nil: ref Sshio->Msg): ref AuthInfo;
+ auth: fn(nil: ref Sshio->Conn): int;
+};
+
+Sshio: module
+{
+ PATH: con "sshio.dis";
+
+ Conn: adt{
+ in: chan of (ref Msg, string);
+ out: chan of ref Msg;
+
+ sessid: array of byte;
+ sesskey: array of byte;
+ hostkey: ref Keyring->PK.RSA;
+ flags: int;
+ cipher: Cipher; # chosen cipher
+ user: string;
+ host: string;
+ interactive: int;
+ unget: ref Msg;
+
+ mk: fn(host: string, fd: ref Sys->FD): ref Conn;
+ setkey: fn(c: self ref Conn, key: ref Keyring->PK.RSA);
+ };
+
+ Msg: adt{
+ mtype: int;
+ data: array of byte;
+ rp: int; # read pointer
+ wp: int; # write pointer
+ ep: int; # byte just beyond message data
+
+ mk: fn(mtype: int, length: int): ref Msg;
+ text: fn(m: self ref Msg): string;
+ fulltext: fn(m: self ref Msg): string;
+
+ get1: fn(m: self ref Msg): int;
+ get2: fn(m: self ref Msg): int;
+ get4: fn(m: self ref Msg): int;
+ getstring: fn(m: self ref Msg): string;
+ getbytes: fn(m: self ref Msg, n: int): array of byte;
+ getarray: fn(m: self ref Msg): array of byte;
+ getipint: fn(m: self ref Msg): ref Keyring->IPint;
+ getpk: fn(m: self ref Msg): ref Keyring->PK.RSA;
+
+ put1: fn(m: self ref Msg, nil: int);
+ put2: fn(m: self ref Msg, nil: int);
+ put4: fn(m: self ref Msg, nil: int);
+ putstring: fn(m: self ref Msg, s: string);
+ putbytes: fn(m: self ref Msg, a: array of byte, n: int);
+ putipint: fn(m: self ref Msg, mp: ref Keyring->IPint);
+ putpk: fn(m: self ref Msg, pk: ref Keyring->PK.RSA);
+ };
+
+ init: fn();
+
+ badmsg: fn(nil: ref Msg, nil: int, err: string);
+ recvmsg: fn(nil: ref Conn, nil: int): ref Msg;
+ unrecvmsg: fn(nil: ref Conn, nil: ref Msg);
+ rsapad: fn(nil: ref Keyring->IPint, nil: int): ref Keyring->IPint;
+ rsaunpad: fn(nil: ref Keyring->IPint): ref Keyring->IPint;
+ iptorjustbe: fn(nil: ref Keyring->IPint, nil: array of byte, nil: int);
+ rsaencryptbuf: fn(nil: ref Keyring->PK.RSA, nil: array of byte, nil: int): ref Keyring->IPint;
+ rsagen: fn(nbits: int): ref Keyring->SK.RSA;
+ rsaencrypt: fn(key: ref Keyring->PK.RSA, b: ref Keyring->IPint): ref Keyring->IPint;
+ rsadecrypt: fn(key: ref Keyring->SK.RSA, b: ref Keyring->IPint): ref Keyring->IPint;
+
+ debug: fn(nil: int, nil: string);
+ error: fn(nil: string);
+ readstrnl: fn(fd: ref Sys->FD, buf: array of byte, nbytes: int): int;
+ calcsessid: fn(hostmod: ref Keyring->IPint, servermod: ref Keyring->IPint, cookie: array of byte): array of byte;
+# sshlog: fn(nil: array of byte); # TBA was ...
+
+ fastrand: fn(): int;
+ eqbytes: fn(a: array of byte, b: array of byte, n: int): int;
+ readversion: fn(fd: ref Sys->FD): (int, int, string);
+ hex: fn(a: array of byte): string;
+};
diff --git a/appl/cmd/ssh/sshserve.b b/appl/cmd/ssh/sshserve.b
new file mode 100644
index 00000000..25b65544
--- /dev/null
+++ b/appl/cmd/ssh/sshserve.b
@@ -0,0 +1,491 @@
+implement Sshserve;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+ PK, SK: import kr;
+
+include "env.m";
+ env: Env;
+
+include "sh.m";
+ sh: Sh;
+
+include "wait.m";
+ wait: Wait;
+
+include "arg.m";
+
+include "sshio.m";
+ sshio: Sshio;
+ Conn, Msg: import sshio;
+ recvmsg: import sshio;
+ error, debug: import sshio;
+
+Sshserve: module
+{
+ init: fn(nil: ref Draw->Context, argl: list of string);
+};
+
+AuthRpc: adt {};
+debuglevel := 0;
+
+cipherlist := "blowfish rc4 3des";
+ciphers: list of Cipher;
+
+authlist := "rsa password tis";
+authsrvs: list of Auth;
+
+maxmsg := 256*1024;
+
+serverpriv: ref SK.RSA;
+serverkey: ref PK.RSA;
+hostpriv: ref SK.RSA;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ env = load Env Env->PATH;
+ sh = load Sh Sh->PATH;
+ sshio = load Sshio Sshio->PATH;
+ sshio->init();
+ wait = load Wait Wait->PATH;
+ wait->init();
+# fmtinstall('B', mpfmt);
+# fmtinstall('H', encodefmt);
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS|Sys->FORKENV, nil);
+ keyfile: string;
+ arg := load Arg Arg->PATH;
+ arg->setusage("sshserve [-A authlist] [-c cipherlist] [-k keyfile] client-ip-address");
+ arg->init(args);
+ while((o := arg->opt()) != 0){
+ case o {
+ 'D' =>
+ debuglevel = int arg->earg();
+ 'A' =>
+ authlist = arg->earg();
+ 'c' =>
+ cipherlist = arg->earg();
+ 'k' =>
+ keyfile = arg->earg();
+ * =>
+ arg->usage();
+ }
+ }
+ args = arg->argv();
+ if(len args != 1)
+ arg->usage();
+ arg = nil;
+
+ sys->dup(2, 1);
+# if(keyfile != nil)
+# ; # read hostpriv from file
+# sshlog("connect from %s", c.host);
+ authsrvs = loadlist("auth", authlist, authload);
+ ciphers = loadlist("cipher", cipherlist, cipherload);
+ hostpriv = kr->rsagen(1024, 6, 0);
+ serverpriv = kr->rsagen(768, 6, 0);
+ serverkey = serverpriv.pk;
+ {
+ versioning(sys->fildes(0));
+ c := Conn.mk(hd args, sys->fildes(0));
+ c.setkey(hostpriv.pk);
+ authenticate(c);
+ comms(c);
+ }exception e{
+ "fail:*" =>
+ raise e;
+ "error*" =>
+ notegrp(sys->pctl(0, nil), "error");
+ raise "fail:"+e;
+ }
+}
+
+authload(f: string): Auth
+{
+ return load Auth f;
+}
+
+cipherload(f: string): Cipher
+{
+ return load Cipher f;
+}
+
+loadlist[T](sort: string, set: string, loadf: ref fn(f: string): T): list of T
+{
+ l: list of T;
+ (nil, fld) := sys->tokenize(set, " \t,");
+ for(; fld != nil; fld = tl fld){
+ f := "/dis/ssh/"+sort+hd fld+".dis";
+ m := loadf(f);
+ if(m == nil)
+ error(sys->sprint("unknown %s scheme %s (%s)", sort, hd fld, f));
+ l = m :: l;
+ }
+ return l;
+}
+
+comms(c: ref Conn)
+{
+ (kidpid, infd, waiting) := prelude(c);
+Work:
+ for(;;)alt{
+ (m, nil) := <-c.in =>
+ if(m == nil){
+ notegrp(kidpid, "hungup");
+ exit;
+ }
+ case m.mtype {
+ * =>
+ sshio->badmsg(m, 0, nil);
+ SSH_MSG_DISCONNECT =>
+ notegrp(kidpid, "hungup");
+ sysfatal("client disconnected");
+ SSH_CMSG_STDIN_DATA =>
+ if(infd != nil){
+ n := m.get4();
+ sys->write(infd, m.getbytes(n), n);
+ }
+ SSH_CMSG_EOF =>
+ infd = nil;
+ SSH_CMSG_EXIT_CONFIRMATION =>
+ # sent by some clients as dying breath
+ notegrp(kidpid, "hungup");
+ break Work;
+ SSH_CMSG_WINDOW_SIZE =>
+ ; # we don't care
+ }
+ (pid, nil, status) := <-waiting =>
+ if(pid == kidpid){
+ if(status != "" && status != "0"){
+ m := Msg.mk(SSH_MSG_DISCONNECT, 4+Sys->UTFmax*len status);
+ m.putstring(status);
+ sendmsg(c, m);
+ }else{
+ m := Msg.mk(SSH_SMSG_EXITSTATUS, 4);
+ m.put4(0);
+ sendmsg(c, m);
+ }
+ sendmsg(c, nil);
+ break Work;
+ }
+ }
+ notegrp(sys->pctl(0, nil), "done");
+}
+
+prelude(c: ref Conn): (int, ref Sys->FD, chan of (int, string, string))
+{
+ for(;;){
+ m := recvmsg(c, -1);
+ if(m == nil)
+ return (-1, nil, nil);
+ case m.mtype {
+ * =>
+ sendmsg(c, Msg.mk(SSH_SMSG_FAILURE, 0));
+ SSH_MSG_DISCONNECT =>
+ sysfatal("client disconnected");
+ SSH_CMSG_REQUEST_PTY =>
+ sendmsg(c, Msg.mk(SSH_SMSG_SUCCESS, 0));
+ SSH_CMSG_MAX_PACKET_SIZE =>
+ n := m.get4();
+ if(n >= 32 && n <= SSH_MAX_MSG){
+ maxmsg = n;
+ sendmsg(c, Msg.mk(SSH_SMSG_SUCCESS, 0));
+ }else
+ sendmsg(c, Msg.mk(SSH_SMSG_FAILURE, 0));
+ SSH_CMSG_EXEC_SHELL =>
+ return startcmd(c, nil);
+ SSH_CMSG_EXEC_CMD =>
+ cmd := m.getstring();
+ return startcmd(c, cmd);
+ }
+ }
+}
+
+copyout(c: ref Conn, fd: ref Sys->FD, mtype: int)
+{
+ buf := array[8192] of byte;
+ max := len buf;
+ if(max > maxmsg-32) # 32 is an overestimate of packet overhead
+ max = maxmsg-32;
+ if(max <= 0)
+ sysfatal("maximum message size too small");
+ while((n := sys->read(fd, buf, max)) > 0){
+ m := Msg.mk(mtype, 4+n);
+ m.put4(n);
+ m.putbytes(buf, n);
+ sendmsg(c, m);
+ }
+}
+
+send_ssh_smsg_public_key(c: ref Conn, cookie: array of byte)
+{
+ m := Msg.mk(SSH_SMSG_PUBLIC_KEY, 2048);
+ m.putbytes(cookie, COOKIELEN);
+ m.putpk(serverkey);
+ m.putpk(c.hostkey);
+ m.put4(c.flags);
+ ciphermask := 0;
+ for(l1 := ciphers; l1 != nil; l1 = tl l1)
+ ciphermask |= 1<<(hd l1)->id();
+ m.put4(ciphermask);
+ authmask := 0;
+ for(l2 := authsrvs; l2 != nil; l2 = tl l2)
+ authmask |= 1<<(hd l2)->id();
+ m.put4(authmask);
+ sendmsg(c, m);
+}
+
+rpcdecrypt(rpc: ref AuthRpc, b: ref IPint): ref IPint
+{
+ raise "rpcdecrypt";
+# p := array of byte b.iptostr(16);
+# if(auth_rpc(rpc, "write", p, len p) != ARok)
+# sysfatal("factotum rsa write: %r");
+# if(auth_rpc(rpc, "read", nil, 0) != ARok)
+# sysfatal("factotum rsa read: %r");
+# return strtomp(rpc.arg, nil, 16, nil);
+}
+
+recv_ssh_cmsg_session_key(c: ref Conn, rpc: ref AuthRpc, cookie: array of byte)
+{
+ m := recvmsg(c, SSH_CMSG_SESSION_KEY);
+ id := m.get1();
+ c.cipher = nil;
+ for(l := ciphers; l != nil; l = tl l)
+ if((hd l)->id() == id){
+ c.cipher = hd l;
+ break;
+ }
+ if(c.cipher == nil)
+ sysfatal(sys->sprint("invalid cipher %d selected", id));
+ if(!sshio->eqbytes(m.getbytes(COOKIELEN), cookie, len cookie))
+ sysfatal("bad cookie");
+ serverkeylen := serverkey.n.bits();
+ hostkeylen := c.hostkey.n.bits();
+ ksmall, kbig: ref SK.RSA;
+ if(serverkeylen+128 <= hostkeylen){
+ ksmall = serverpriv;
+ kbig = nil;
+ }else if(hostkeylen+128 <= serverkeylen){
+ ksmall = nil;
+ kbig = serverpriv;
+ }else
+ sysfatal("server session and host keys do not differ by at least 128 bits");
+ b := m.getipint();
+ debug(DBG_CRYPTO, sys->sprint("encrypted with kbig is %s\n", b.iptostr(16)));
+ if(kbig != nil)
+ b = sshio->rsadecrypt(kbig, b);
+ else
+# b = rpcdecrypt(rpc, b);
+ b = sshio->rsadecrypt(hostpriv, b);
+ b = sshio->rsaunpad(b);
+ sshio->debug(DBG_CRYPTO, sys->sprint("encrypted with ksmall is %s\n", b.iptostr(16)));
+ if(ksmall != nil)
+ b = sshio->rsadecrypt(ksmall, b);
+ else
+# b = rpcdecrypt(rpc, b);
+ b = sshio->rsadecrypt(hostpriv, b);
+ b = sshio->rsaunpad(b);
+ debug(DBG_CRYPTO, sys->sprint("munged is %s\n", b.iptostr(16)));
+ n := (b.bits()+7)/8;
+ if(n < SESSKEYLEN)
+ sysfatal("client sent short session key");
+ buf := array[SESSKEYLEN] of byte;
+ sshio->iptorjustbe(b, buf, SESSKEYLEN);
+ for(i := 0; i < SESSIDLEN; i++)
+ buf[i] ^= c.sessid[i];
+ c.sesskey[0: ] = buf[0: SESSKEYLEN];
+ debug(DBG_CRYPTO, sys->sprint("unmunged is %.*s\n", SESSKEYLEN*2, sshio->hex(buf)));
+ c.flags = m.get4();
+}
+
+authsrvuser(c: ref Conn)
+{
+ m := recvmsg(c, SSH_CMSG_USER);
+ user := m.getstring();
+ c.user = user;
+ inited := 0;
+ ai: ref Auth->AuthInfo;
+ while(authsrvs != nil && 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)
+ sendmsg(c, Msg.mk(SSH_SMSG_FAILURE, 0));
+ m = recvmsg(c, -1);
+ for(l := authsrvs; l != nil; l = tl l)
+ if((hd l)->firstmsg() == m.mtype){
+ bit := 1 << (hd l)->id();
+ if((inited & bit) == 0){
+ (hd l)->init(sshio);
+ inited |= bit;
+ }
+ ai = (hd l)->authsrv(c, m);
+ break;
+ }
+ if(l == nil)
+ sshio->badmsg(m, 0, nil);
+ }
+ sendmsg(c, Msg.mk(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 %q", ai.user);
+ if(ai != nil)
+ sys->print("logged in as %q\n", ai.user);
+}
+
+keyjunk()
+{
+ p: array of byte;
+ m: ref IPint;
+ rpc: ref AuthRpc;
+ key: ref PK.RSA;
+
+# #
+# # 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){
+# if(strstr(p, " proto=rsa ") != nil && strstr(p, " service=sshserve ") != nil)
+# break;
+# }
+# if(p == nil)
+# sysfatal("no sshserve keys found in /mnt/factotum/ctl");
+# a = _parseattr(p);
+# Bterm(b);
+# key = rsaprivalloc();
+# if((p = _strfindattr(a, "n")) == nil)
+# sysfatal("no n in sshserve key");
+# if((key.n = IPint.strtoip(p, 16)) == nil)
+# sysfatal("bad n in sshserve key");
+# if((p = _strfindattr(a, "ek")) == nil)
+# sysfatal("no ek in sshserve key");
+# if((key.ek = IPint.strtoip(p, 16)) == nil)
+# sysfatal("bad ek in sshserve key");
+# _freeattr(a);
+# if((afd = sys->open("/mnt/factotum/rpc", ORDWR)) == nil)
+# 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, len 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;
+}
+
+versioning(fd: ref Sys->FD)
+{
+ sys->fprint(fd, "SSH-1.5-Inferno\n");
+ (maj, min, err_or_id) := sshio->readversion(fd);
+ if(maj < 0)
+ sysfatal(err_or_id);
+ if(maj != 1 || min < 5)
+ sysfatal(sys->sprint("protocol mismatch; got %s, need SSH-1.x for x >= 5", err_or_id));
+}
+
+authenticate(c: ref Conn)
+{
+ rpc: ref AuthRpc;
+
+ cookie := array[COOKIELEN] of {* => byte sshio->fastrand()};
+ c.sessid = sshio->calcsessid(c.hostkey.n, serverkey.n, cookie);
+ send_ssh_smsg_public_key(c, cookie);
+ recv_ssh_cmsg_session_key(c, rpc, cookie);
+# afd = nil;
+ c.cipher->init(c.sesskey, 1); # turns on encryption
+ sendmsg(c, Msg.mk(SSH_SMSG_SUCCESS, 0));
+ authsrvuser(c);
+}
+
+startcmd(c: ref Conn, cmd: string): (int, ref Sys->FD, chan of (int, string, string))
+{
+ pfd := array[3] of {* => array[2] of ref Sys->FD};
+ for(i := 0; i < 3; i++)
+ if(sys->pipe(pfd[i]) < 0)
+ sysfatal(sys->sprint("pipe: %r"));
+ wfd := sys->open("#p/"+string sys->pctl(0, nil)+"/wait", Sys->OREAD);
+ if(wfd == nil)
+ sysfatal(sys->sprint("open wait: %r"));
+ pidc := chan of int;
+ spawn startcmd1(c, cmd, pfd, pidc);
+ kidpid := <-pidc;
+ (nil, waited) := wait->monitor(wfd);
+ spawn copyout(c, pfd[1][0], SSH_SMSG_STDOUT_DATA);
+ pfd[1][0] = nil;
+ spawn copyout(c, pfd[2][0], SSH_SMSG_STDERR_DATA);
+ pfd[2][0] = nil;
+ return (kidpid, pfd[0][0], waited);
+}
+
+startcmd1(c: ref Conn, cmd: string, pfd: array of array of ref Sys->FD, pidc: chan of int)
+{
+ sysname := env->getenv("sysname");
+ tz := env->getenv("timezone");
+ sys->pctl(Sys->FORKFD, nil);
+ for(i := 0; i < len pfd; i++)
+ if(sys->dup(pfd[i][1].fd, i) < 0)
+ sysfatal(sys->sprint("dup: %r"));
+ pfd = nil;
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKENV|Sys->NEWFD, 0::1::2::nil);
+ pidc <-= sys->pctl(0, nil);
+ env->setenv("user", c.user);
+ if(sysname != nil)
+ env->setenv("sysname", sysname);
+ if(tz != nil)
+ env->setenv("tz", tz);
+ if(sys->chdir("/usr/"+c.user) < 0)
+ sys->chdir("/");
+ if(cmd != nil){
+ env->setenv("service", "rx");
+ status := sh->run(nil, list of {"/dis/sh.dis", "-lc", cmd});
+ if(status != nil)
+ raise "fail:"+status;
+ }else{
+ env->setenv("service", "con");
+ #execl("/bin/ip/telnetd", "telnetd", "-tn", nil); # TO DO: just for echo and line editing
+ sys->fprint(sys->fildes(2), "sshserve: cannot run /dis/ip/telnetd: %r");
+ }
+}
+
+sysfatal(s: string)
+{
+ sys->print("sysfatal: %s\n", s);
+ notegrp(sys->pctl(0, nil), "zap");
+ exit;
+}
+
+notegrp(pid: int, nil: string)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+}
+
+sendmsg(c: ref Conn, m: ref Msg)
+{
+ c.out <-= m;
+}