From 9e85ae9b75acdbe245243784d8d5b1b4de1b24f9 Mon Sep 17 00:00:00 2001 From: "Charles.Forsyth" Date: Mon, 30 Mar 2009 08:58:09 +0000 Subject: x20090330-0957 --- appl/cmd/ssh/sshserve.b | 491 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 491 insertions(+) create mode 100644 appl/cmd/ssh/sshserve.b (limited to 'appl/cmd/ssh/sshserve.b') 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; +} -- cgit v1.2.3