summaryrefslogtreecommitdiff
path: root/appl/cmd/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ssh')
-rw-r--r--appl/cmd/ssh/authpassword.b68
-rw-r--r--appl/cmd/ssh/authrsa.b188
-rw-r--r--appl/cmd/ssh/authtis.b119
-rw-r--r--appl/cmd/ssh/cipher3des.b51
-rw-r--r--appl/cmd/ssh/cipherblowfish.b43
-rw-r--r--appl/cmd/ssh/cipherdes.b43
-rw-r--r--appl/cmd/ssh/ciphernone.b28
-rw-r--r--appl/cmd/ssh/cipherrc4.b46
-rw-r--r--appl/cmd/ssh/mkfile29
-rw-r--r--appl/cmd/ssh/sshio.b586
-rw-r--r--appl/cmd/ssh/sshio.m194
-rw-r--r--appl/cmd/ssh/sshserve.b495
12 files changed, 1890 insertions, 0 deletions
diff --git a/appl/cmd/ssh/authpassword.b b/appl/cmd/ssh/authpassword.b
new file mode 100644
index 00000000..1a149117
--- /dev/null
+++ b/appl/cmd/ssh/authpassword.b
@@ -0,0 +1,68 @@
+implement Auth;
+
+include "sys.m";
+ sys: Sys;
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt; # 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..427355d5
--- /dev/null
+++ b/appl/cmd/ssh/authrsa.b
@@ -0,0 +1,188 @@
+implement Auth;
+
+include "sys.m";
+ sys: Sys;
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt;
+ PK, SK: import crypt;
+
+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;
+ ipints = load IPints IPints->PATH;
+ crypt = load Crypt Crypt->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 := crypt->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(Crypt->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[Crypt->MD5dlen] of byte;
+ crypt->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[Crypt->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));
+ crypt->md5(chalbuf, 32+SESSIDLEN, response, nil);
+
+ m = Msg.mk(SSH_CMSG_AUTH_RSA_RESPONSE, Crypt->MD5dlen);
+ m.putbytes(response, Crypt->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..0fd5edd6
--- /dev/null
+++ b/appl/cmd/ssh/authtis.b
@@ -0,0 +1,119 @@
+implement Auth;
+
+# TO DO: add chal/resp to Factotum
+
+include "sys.m";
+ sys: Sys;
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt; # avoid compiler error
+
+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;
+ ipints = load IPints IPints->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..e6f347f1
--- /dev/null
+++ b/appl/cmd/ssh/cipher3des.b
@@ -0,0 +1,51 @@
+implement Cipher;
+
+include "sys.m";
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt;
+ DESstate: import crypt;
+
+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)
+{
+ ipints = load IPints IPints->PATH;
+ crypt = load Crypt Crypt->PATH;
+ cs = ref Cipherstate(array[3] of ref DESstate, array[3] of ref DESstate);
+ for(i := 0; i < 3; i++){
+ cs.enc[i] = crypt->dessetup(key[i*8:], nil);
+ cs.dec[i] = crypt->dessetup(key[i*8:], nil);
+ }
+}
+
+encrypt(buf: array of byte, nbuf: int)
+{
+ crypt->descbc(cs.enc[0], buf, nbuf, Crypt->Encrypt);
+ crypt->descbc(cs.enc[1], buf, nbuf, Crypt->Decrypt);
+ crypt->descbc(cs.enc[2], buf, nbuf, Crypt->Encrypt);
+}
+
+decrypt(buf: array of byte, nbuf: int)
+{
+ crypt->descbc(cs.dec[2], buf, nbuf, Crypt->Decrypt);
+ crypt->descbc(cs.dec[1], buf, nbuf, Crypt->Encrypt);
+ crypt->descbc(cs.dec[0], buf, nbuf, Crypt->Decrypt);
+}
diff --git a/appl/cmd/ssh/cipherblowfish.b b/appl/cmd/ssh/cipherblowfish.b
new file mode 100644
index 00000000..8d3b0c31
--- /dev/null
+++ b/appl/cmd/ssh/cipherblowfish.b
@@ -0,0 +1,43 @@
+implement Cipher;
+
+include "sys.m";
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt;
+ BFstate: import crypt;
+
+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)
+{
+ ipints = load IPints IPints->PATH;
+ crypt = load Crypt Crypt->PATH;
+ cs = ref Cipherstate(crypt->blowfishsetup(key, nil), crypt->blowfishsetup(key, nil));
+}
+
+encrypt(buf: array of byte, nbuf: int)
+{
+ crypt->blowfishcbc(cs.enc, buf, nbuf, Crypt->Encrypt);
+}
+
+decrypt(buf: array of byte, nbuf: int)
+{
+ crypt->blowfishcbc(cs.dec, buf, nbuf, Crypt->Decrypt);
+}
diff --git a/appl/cmd/ssh/cipherdes.b b/appl/cmd/ssh/cipherdes.b
new file mode 100644
index 00000000..7de0a7ca
--- /dev/null
+++ b/appl/cmd/ssh/cipherdes.b
@@ -0,0 +1,43 @@
+implement Cipher;
+
+include "sys.m";
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt;
+ DESstate: import crypt;
+
+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)
+{
+ ipints = load IPints IPints->PATH;
+ crypt = load Crypt Crypt->PATH;
+ cs = ref Cipherstate(crypt->dessetup(key, nil), crypt->dessetup(key, nil));
+}
+
+encrypt(buf: array of byte, nbuf: int)
+{
+ crypt->descbc(cs.enc, buf, nbuf, Crypt->Encrypt);
+}
+
+decrypt(buf: array of byte, nbuf: int)
+{
+ crypt->descbc(cs.dec, buf, nbuf, Crypt->Decrypt);
+}
diff --git a/appl/cmd/ssh/ciphernone.b b/appl/cmd/ssh/ciphernone.b
new file mode 100644
index 00000000..01a7a1f3
--- /dev/null
+++ b/appl/cmd/ssh/ciphernone.b
@@ -0,0 +1,28 @@
+implement Cipher;
+
+include "sys.m";
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.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..f43f9c8d
--- /dev/null
+++ b/appl/cmd/ssh/cipherrc4.b
@@ -0,0 +1,46 @@
+implement Cipher;
+
+include "sys.m";
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt;
+ RC4state: import crypt;
+
+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)
+{
+ ipints = load IPints IPints->PATH;
+ crypt = load Crypt Crypt->PATH;
+ if(isserver)
+ cs = ref Cipherstate(crypt->rc4setup(key[0:16]), crypt->rc4setup(key[16:32]));
+ else
+ cs = ref Cipherstate(crypt->rc4setup(key[16:32]), crypt->rc4setup(key[0:16]));
+}
+
+encrypt(buf: array of byte, nbuf: int)
+{
+ crypt->rc4(cs.enc, buf, nbuf);
+}
+
+decrypt(buf: array of byte, nbuf: int)
+{
+ crypt->rc4(cs.dec, buf, nbuf);
+}
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/sshio.b b/appl/cmd/ssh/sshio.b
new file mode 100644
index 00000000..942b06df
--- /dev/null
+++ b/appl/cmd/ssh/sshio.b
@@ -0,0 +1,586 @@
+implement Sshio;
+
+include "sys.m";
+ sys: Sys;
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt;
+ PK, SK: import crypt;
+
+include "sshio.m";
+
+include "rand.m";
+ rand: Rand;
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ ipints = load IPints IPints->PATH;
+ crypt = load Crypt Crypt->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[Crypt->MD5dlen] of byte;
+ crypt->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 crypt->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 crypt->rsagen(bits, 6, 0);
+}
+
+rsaencrypt(key: ref PK.RSA, b: ref IPint): ref IPint
+{
+ return crypt->rsaencrypt(key, b);
+}
+
+rsadecrypt(key: ref SK.RSA, b: ref IPint): ref IPint
+{
+ return crypt->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..6c186e63
--- /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 Crypt->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 Crypt->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 IPints->IPint;
+ getpk: fn(m: self ref Msg): ref Crypt->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 IPints->IPint);
+ putpk: fn(m: self ref Msg, pk: ref Crypt->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 IPints->IPint, nil: int): ref IPints->IPint;
+ rsaunpad: fn(nil: ref IPints->IPint): ref IPints->IPint;
+ iptorjustbe: fn(nil: ref IPints->IPint, nil: array of byte, nil: int);
+ rsaencryptbuf: fn(nil: ref Crypt->PK.RSA, nil: array of byte, nil: int): ref IPints->IPint;
+ rsagen: fn(nbits: int): ref Crypt->SK.RSA;
+ rsaencrypt: fn(key: ref Crypt->PK.RSA, b: ref IPints->IPint): ref IPints->IPint;
+ rsadecrypt: fn(key: ref Crypt->SK.RSA, b: ref IPints->IPint): ref IPints->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 IPints->IPint, servermod: ref IPints->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..dc8bbd31
--- /dev/null
+++ b/appl/cmd/ssh/sshserve.b
@@ -0,0 +1,495 @@
+implement Sshserve;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "ipints.m";
+ ipints: IPints;
+ IPint: import ipints;
+
+include "crypt.m";
+ crypt: Crypt;
+ PK, SK: import crypt;
+
+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;
+ ipints = load IPints IPints->PATH;
+ crypt = load Crypt Crypt->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 = crypt->rsagen(1024, 6, 0);
+ serverpriv = crypt->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;
+}