summaryrefslogtreecommitdiff
path: root/appl/cmd/ssh/sshserve.b
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/ssh/sshserve.b
parent77bf0d6355c02a5d5199dd37033066727cad375b (diff)
x20090330-0957
Diffstat (limited to 'appl/cmd/ssh/sshserve.b')
-rw-r--r--appl/cmd/ssh/sshserve.b491
1 files changed, 491 insertions, 0 deletions
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;
+}