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