plan9fox/sys/src/cmd/ssh.c
cinap_lenrek 346f5828e0 libsec: sha256 support for thumbprint files, use it in ssh as well
initThumbprints() now takes an application tag argument
so x509 and ssh can coexist.

the thumbprint entries can now hold both sha1 and sha256
hashes. okThumbprint() now takes a len argument for the
hash length used.

the new function okCertificate() hashes the certificate
with both and checks for any matches.

on failure, okCertificate() returns 0 and sets error string.

we also check for include loops now in thumbfiles, limiting
the number of includes to 8.
2017-04-23 19:00:08 +02:00

1284 lines
24 KiB
C

#include <u.h>
#include <libc.h>
#include <mp.h>
#include <libsec.h>
#include <auth.h>
#include <authsrv.h>
enum {
MSG_DISCONNECT = 1,
MSG_IGNORE,
MSG_UNIMPLEMENTED,
MSG_DEBUG,
MSG_SERVICE_REQUEST,
MSG_SERVICE_ACCEPT,
MSG_KEXINIT = 20,
MSG_NEWKEYS,
MSG_ECDH_INIT = 30,
MSG_ECDH_REPLY,
MSG_USERAUTH_REQUEST = 50,
MSG_USERAUTH_FAILURE,
MSG_USERAUTH_SUCCESS,
MSG_USERAUTH_BANNER,
MSG_USERAUTH_PK_OK = 60,
MSG_USERAUTH_INFO_REQUEST = 60,
MSG_USERAUTH_INFO_RESPONSE = 61,
MSG_GLOBAL_REQUEST = 80,
MSG_REQUEST_SUCCESS,
MSG_REQUEST_FAILURE,
MSG_CHANNEL_OPEN = 90,
MSG_CHANNEL_OPEN_CONFIRMATION,
MSG_CHANNEL_OPEN_FAILURE,
MSG_CHANNEL_WINDOW_ADJUST,
MSG_CHANNEL_DATA,
MSG_CHANNEL_EXTENDED_DATA,
MSG_CHANNEL_EOF,
MSG_CHANNEL_CLOSE,
MSG_CHANNEL_REQUEST,
MSG_CHANNEL_SUCCESS,
MSG_CHANNEL_FAILURE,
};
enum {
Overhead = 256, // enougth for MSG_CHANNEL_DATA header
MaxPacket = 1<<15,
WinPackets = 8, // (1<<15) * 8 = 256K
};
typedef struct
{
u32int seq;
u32int kex;
u32int chan;
int win;
int pkt;
int eof;
Chachastate cs1;
Chachastate cs2;
uchar *r;
uchar *w;
uchar b[Overhead + MaxPacket];
char *v;
int pid;
Rendez;
} Oneway;
int nsid;
uchar sid[256];
char thumb[2*SHA2_256dlen+1], *thumbfile;
int fd, intr, raw, debug;
char *user, *service, *status, *host, *cmd;
Oneway recv, send;
void dispatch(void);
void
shutdown(void)
{
recv.eof = send.eof = 1;
if(send.pid > 0)
postnote(PNPROC, send.pid, "shutdown");
}
void
catch(void*, char *msg)
{
if(strstr(msg, "interrupt") != nil){
intr = 1;
noted(NCONT);
}
noted(NDFLT);
}
int
wasintr(void)
{
char err[ERRMAX];
int r;
if(intr)
return 1;
memset(err, 0, sizeof(err));
errstr(err, sizeof(err));
r = strstr(err, "interrupt") != nil;
errstr(err, sizeof(err));
return r;
}
#define PUT4(p, u) (p)[0] = (u)>>24, (p)[1] = (u)>>16, (p)[2] = (u)>>8, (p)[3] = (u)
#define GET4(p) (u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24
int
vpack(uchar *p, int n, char *fmt, va_list a)
{
uchar *p0 = p, *e = p+n;
u32int u;
mpint *m;
void *s;
int c;
for(;;){
switch(c = *fmt++){
case '\0':
return p - p0;
case '_':
if(++p > e) goto err;
break;
case '.':
*va_arg(a, void**) = p;
break;
case 'b':
if(p >= e) goto err;
*p++ = va_arg(a, int);
break;
case 'm':
m = va_arg(a, mpint*);
u = (mpsignif(m)+8)/8;
if(p+4 > e) goto err;
PUT4(p, u), p += 4;
if(u > e-p) goto err;
mptober(m, p, u), p += u;
break;
case '[':
case 's':
s = va_arg(a, void*);
u = va_arg(a, int);
if(c == 's'){
if(p+4 > e) goto err;
PUT4(p, u), p += 4;
}
if(u > e-p) goto err;
memmove(p, s, u);
p += u;
break;
case 'u':
u = va_arg(a, int);
if(p+4 > e) goto err;
PUT4(p, u), p += 4;
break;
}
}
err:
return -1;
}
int
vunpack(uchar *p, int n, char *fmt, va_list a)
{
uchar *p0 = p, *e = p+n;
u32int u;
mpint *m;
void *s;
for(;;){
switch(*fmt++){
case '\0':
return p - p0;
case '_':
if(++p > e) goto err;
break;
case '.':
*va_arg(a, void**) = p;
break;
case 'b':
if(p >= e) goto err;
*va_arg(a, int*) = *p++;
break;
case 'm':
if(p+4 > e) goto err;
u = GET4(p), p += 4;
if(u > e-p) goto err;
m = va_arg(a, mpint*);
betomp(p, u, m), p += u;
break;
case 's':
if(p+4 > e) goto err;
u = GET4(p), p += 4;
if(u > e-p) goto err;
*va_arg(a, void**) = p;
*va_arg(a, int*) = u;
p += u;
break;
case '[':
s = va_arg(a, void*);
u = va_arg(a, int);
if(u > e-p) goto err;
memmove(s, p, u);
p += u;
break;
case 'u':
if(p+4 > e) goto err;
u = GET4(p);
*va_arg(a, int*) = u;
p += 4;
break;
}
}
err:
return -1;
}
int
pack(uchar *p, int n, char *fmt, ...)
{
va_list a;
va_start(a, fmt);
n = vpack(p, n, fmt, a);
va_end(a);
return n;
}
int
unpack(uchar *p, int n, char *fmt, ...)
{
va_list a;
va_start(a, fmt);
n = vunpack(p, n, fmt, a);
va_end(a);
return n;
}
void
setupcs(Oneway *c, uchar otk[32])
{
uchar iv[8];
memset(otk, 0, 32);
pack(iv, sizeof(iv), "uu", 0, c->seq);
chacha_setiv(&c->cs1, iv);
chacha_setiv(&c->cs2, iv);
chacha_setblock(&c->cs1, 0);
chacha_setblock(&c->cs2, 0);
chacha_encrypt(otk, 32, &c->cs2);
}
void
sendpkt(char *fmt, ...)
{
static uchar buf[sizeof(send.b)];
int n, pad;
va_list a;
va_start(a, fmt);
n = vpack(send.b, sizeof(send.b), fmt, a);
va_end(a);
if(n < 0) {
toobig: sysfatal("sendpkt: message too big");
return;
}
send.r = send.b;
send.w = send.b+n;
if(debug > 1)
fprint(2, "sendpkt: (%d) %.*H\n", send.r[0], (int)(send.w-send.r), send.r);
if(nsid){
/* undocumented */
pad = ChachaBsize - ((5+n) % ChachaBsize) + 4;
} else {
for(pad=4; (5+n+pad) % 8; pad++)
;
}
prng(send.w, pad);
n = pack(buf, sizeof(buf)-16, "ub[[", 1+n+pad, pad, send.b, n, send.w, pad);
if(n < 0) goto toobig;
if(nsid){
uchar otk[32];
setupcs(&send, otk);
chacha_encrypt(buf, 4, &send.cs1);
chacha_encrypt(buf+4, n-4, &send.cs2);
poly1305(buf, n, otk, sizeof(otk), buf+n, nil);
n += 16;
}
if(write(fd, buf, n) != n)
sysfatal("write: %r");
send.seq++;
}
int
readall(int fd, uchar *data, int len)
{
int n, tot;
for(tot = 0; tot < len; tot += n){
n = read(fd, data+tot, len-tot);
if(n <= 0){
if(n < 0 && wasintr()){
n = 0;
continue;
} else if(n == 0)
werrstr("eof");
break;
}
}
return tot;
}
int
recvpkt(void)
{
uchar otk[32], tag[16];
DigestState *ds = nil;
int n;
if(readall(fd, recv.b, 4) != 4)
sysfatal("read1: %r");
if(nsid){
setupcs(&recv, otk);
ds = poly1305(recv.b, 4, otk, sizeof(otk), nil, nil);
chacha_encrypt(recv.b, 4, &recv.cs1);
unpack(recv.b, 4, "u", &n);
n += 16;
} else {
unpack(recv.b, 4, "u", &n);
}
if(n < 8 || n > sizeof(recv.b)){
badlen: sysfatal("bad length %d", n);
}
if(readall(fd, recv.b, n) != n)
sysfatal("read2: %r");
if(nsid){
n -= 16;
if(n < 0) goto badlen;
poly1305(recv.b, n, otk, sizeof(otk), tag, ds);
if(tsmemcmp(tag, recv.b+n, 16) != 0)
sysfatal("bad tag");
chacha_encrypt(recv.b, n, &recv.cs2);
}
n -= recv.b[0]+1;
if(n < 1) goto badlen;
recv.r = recv.b + 1;
recv.w = recv.r + n;
recv.seq++;
if(debug > 1)
fprint(2, "recvpkt: (%d) %.*H\n", recv.r[0], (int)(recv.w-recv.r), recv.r);
return recv.r[0];
}
static char sshrsa[] = "ssh-rsa";
int
rsapub2ssh(RSApub *rsa, uchar *data, int len)
{
return pack(data, len, "smm", sshrsa, sizeof(sshrsa)-1, rsa->ek, rsa->n);
}
RSApub*
ssh2rsapub(uchar *data, int len)
{
RSApub *pub;
char *s;
int n;
pub = rsapuballoc();
pub->n = mpnew(0);
pub->ek = mpnew(0);
if(unpack(data, len, "smm", &s, &n, pub->ek, pub->n) < 0
|| n != sizeof(sshrsa)-1 || memcmp(s, sshrsa, n) != 0){
rsapubfree(pub);
return nil;
}
return pub;
}
int
rsasig2ssh(RSApub *pub, mpint *S, uchar *data, int len)
{
int l = (mpsignif(pub->n)+7)/8;
if(4+7+4+l > len)
return -1;
mptober(S, data+4+7+4, l);
return pack(data, len, "ss", sshrsa, sizeof(sshrsa)-1, data+4+7+4, l);
}
mpint*
ssh2rsasig(uchar *data, int len)
{
mpint *m;
char *s;
int n;
m = mpnew(0);
if(unpack(data, len, "sm", &s, &n, m) < 0
|| n != sizeof(sshrsa)-1 || memcmp(s, sshrsa, n) != 0){
mpfree(m);
return nil;
}
return m;
}
/* libsec */
extern mpint* pkcs1padbuf(uchar *buf, int len, mpint *modulus, int blocktype);
extern int asn1encodedigest(DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*),
uchar *digest, uchar *buf, int len);
mpint*
pkcs1digest(uchar *data, int len, RSApub *pub)
{
uchar digest[SHA1dlen], buf[256];
sha1(data, len, digest, nil);
return pkcs1padbuf(buf, asn1encodedigest(sha1, digest, buf, sizeof(buf)), pub->n, 1);
}
int
pkcs1verify(uchar *data, int len, RSApub *pub, mpint *S)
{
mpint *V;
int ret;
V = pkcs1digest(data, len, pub);
ret = V != nil;
if(ret){
rsaencrypt(pub, S, S);
ret = mpcmp(V, S) == 0;
mpfree(V);
}
return ret;
}
DigestState*
hashstr(void *data, ulong len, DigestState *ds)
{
uchar l[4];
pack(l, 4, "u", len);
return sha2_256((uchar*)data, len, nil, sha2_256(l, 4, nil, ds));
}
void
kdf(uchar *k, int nk, uchar *h, char x, uchar *out, int len)
{
uchar digest[SHA2_256dlen], *out0;
DigestState *ds;
int n;
ds = hashstr(k, nk, nil);
ds = sha2_256(h, sizeof(digest), nil, ds);
ds = sha2_256((uchar*)&x, 1, nil, ds);
sha2_256(sid, nsid, digest, ds);
for(out0=out;;){
n = len;
if(n > sizeof(digest))
n = sizeof(digest);
memmove(out, digest, n);
len -= n;
if(len == 0)
break;
out += n;
ds = hashstr(k, nk, nil);
ds = sha2_256(h, sizeof(digest), nil, ds);
sha2_256(out0, out-out0, digest, ds);
}
}
void
kex(int gotkexinit)
{
static char kexalgs[] = "curve25519-sha256,curve25519-sha256@libssh.org";
static char hostkeyalgs[] = "ssh-rsa";
static char cipheralgs[] = "chacha20-poly1305@openssh.com";
static char zipalgs[] = "none";
static char macalgs[] = "";
static char langs[] = "";
uchar cookie[16], x[32], yc[32], z[32], k[32+1], h[SHA2_256dlen], *ys, *ks, *sig;
uchar k12[2*ChachaKeylen];
int i, nk, nys, nks, nsig;
DigestState *ds;
mpint *S, *K;
RSApub *pub;
ds = hashstr(send.v, strlen(send.v), nil);
ds = hashstr(recv.v, strlen(recv.v), ds);
genrandom(cookie, sizeof(cookie));
sendpkt("b[ssssssssssbu", MSG_KEXINIT,
cookie, sizeof(cookie),
kexalgs, sizeof(kexalgs)-1,
hostkeyalgs, sizeof(hostkeyalgs)-1,
cipheralgs, sizeof(cipheralgs)-1,
cipheralgs, sizeof(cipheralgs)-1,
macalgs, sizeof(macalgs)-1,
macalgs, sizeof(macalgs)-1,
zipalgs, sizeof(zipalgs)-1,
zipalgs, sizeof(zipalgs)-1,
langs, sizeof(langs)-1,
langs, sizeof(langs)-1,
0,
0);
ds = hashstr(send.r, send.w-send.r, ds);
if(!gotkexinit){
Next0: switch(recvpkt()){
default:
dispatch();
goto Next0;
case MSG_KEXINIT:
break;
}
}
ds = hashstr(recv.r, recv.w-recv.r, ds);
if(debug){
char *tab[] = {
"kexalgs", "hostalgs",
"cipher1", "cipher2",
"mac1", "mac2",
"zip1", "zip2",
"lang1", "lang2",
nil,
}, **t, *s;
uchar *p = recv.r+17;
int n;
for(t=tab; *t != nil; t++){
if(unpack(p, recv.w-p, "s.", &s, &n, &p) < 0)
break;
fprint(2, "%s: %.*s\n", *t, n, s);
}
}
curve25519_dh_new(x, yc);
yc[31] &= ~0x80;
sendpkt("bs", MSG_ECDH_INIT, yc, sizeof(yc));
Next1: switch(recvpkt()){
default:
dispatch();
goto Next1;
case MSG_KEXINIT:
sysfatal("inception");
case MSG_ECDH_REPLY:
if(unpack(recv.r, recv.w-recv.r, "_sss", &ks, &nks, &ys, &nys, &sig, &nsig) < 0)
sysfatal("bad ECDH_REPLY");
break;
}
if(nys != 32)
sysfatal("bad server ECDH ephermal public key length");
ds = hashstr(ks, nks, ds);
ds = hashstr(yc, 32, ds);
ds = hashstr(ys, 32, ds);
if(thumb[0] == 0){
Thumbprint *ok;
sha2_256(ks, nks, h, nil);
i = enc64(thumb, sizeof(thumb), h, sizeof(h));
while(i > 0 && thumb[i-1] == '=')
i--;
thumb[i] = '\0';
if(debug)
fprint(2, "host fingerprint: %s\n", thumb);
ok = initThumbprints(thumbfile, nil, "ssh");
if(ok == nil || !okThumbprint(h, sizeof(h), ok)){
if(ok != nil) werrstr("unknown host");
fprint(2, "%s: %r, to add after verification:\n", argv0);
fprint(2, "\techo 'ssh sha256=%s server=%s' >> %q\n", thumb, host, thumbfile);
sysfatal("checking hostkey failed: %r");
}
freeThumbprints(ok);
}
if((pub = ssh2rsapub(ks, nks)) == nil)
sysfatal("bad server public key");
if((S = ssh2rsasig(sig, nsig)) == nil)
sysfatal("bad server signature");
curve25519_dh_finish(x, ys, z);
K = betomp(z, 32, nil);
nk = (mpsignif(K)+8)/8;
mptober(K, k, nk);
mpfree(K);
ds = hashstr(k, nk, ds);
sha2_256(nil, 0, h, ds);
if(!pkcs1verify(h, sizeof(h), pub, S))
sysfatal("server verification failed");
mpfree(S);
rsapubfree(pub);
sendpkt("b", MSG_NEWKEYS);
Next2: switch(recvpkt()){
default:
dispatch();
goto Next2;
case MSG_KEXINIT:
sysfatal("inception");
case MSG_NEWKEYS:
break;
}
/* next key exchange */
recv.kex = recv.seq + 100000;
send.kex = send.seq + 100000;
if(nsid == 0)
memmove(sid, h, nsid = sizeof(h));
kdf(k, nk, h, 'C', k12, sizeof(k12));
setupChachastate(&send.cs1, k12+1*ChachaKeylen, ChachaKeylen, nil, 64/8, 20);
setupChachastate(&send.cs2, k12+0*ChachaKeylen, ChachaKeylen, nil, 64/8, 20);
kdf(k, nk, h, 'D', k12, sizeof(k12));
setupChachastate(&recv.cs1, k12+1*ChachaKeylen, ChachaKeylen, nil, 64/8, 20);
setupChachastate(&recv.cs2, k12+0*ChachaKeylen, ChachaKeylen, nil, 64/8, 20);
}
static char *authnext;
int
authok(char *meth)
{
int ok = authnext == nil || strstr(authnext, meth) != nil;
if(debug)
fprint(2, "userauth %s %s\n", meth, ok ? "ok" : "skipped");
return ok;
}
int
authfailure(char *meth)
{
char *s;
int n, partial;
if(unpack(recv.r, recv.w-recv.r, "_sb", &s, &n, &partial) < 0)
sysfatal("bad auth failure response");
free(authnext);
authnext = smprint("%.*s", n, s);
if(debug)
fprint(2, "userauth %s failed: partial=%d, next=%s\n", meth, partial, authnext);
return partial != 0 || !authok(meth);
}
int
pubkeyauth(void)
{
static char authmeth[] = "publickey";
uchar pk[4096], sig[4096];
int npk, nsig;
int afd, n;
char *s;
mpint *S;
AuthRpc *rpc;
RSApub *pub;
if(!authok(authmeth))
return -1;
if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0)
return -1;
if((rpc = auth_allocrpc(afd)) == nil){
close(afd);
return -1;
}
s = "proto=rsa service=ssh role=client";
if(auth_rpc(rpc, "start", s, strlen(s)) != ARok){
auth_freerpc(rpc);
close(afd);
return -1;
}
pub = rsapuballoc();
pub->n = mpnew(0);
pub->ek = mpnew(0);
while(auth_rpc(rpc, "read", nil, 0) == ARok){
s = rpc->arg;
if(strtomp(s, &s, 16, pub->n) == nil)
break;
if(*s++ != ' ')
continue;
if(strtomp(s, nil, 16, pub->ek) == nil)
continue;
npk = rsapub2ssh(pub, pk, sizeof(pk));
sendpkt("bsssbss", MSG_USERAUTH_REQUEST,
user, strlen(user),
service, strlen(service),
authmeth, sizeof(authmeth)-1,
0,
sshrsa, sizeof(sshrsa)-1,
pk, npk);
Next1: switch(recvpkt()){
default:
dispatch();
goto Next1;
case MSG_USERAUTH_FAILURE:
if(authfailure(authmeth))
goto Failed;
continue;
case MSG_USERAUTH_SUCCESS:
case MSG_USERAUTH_PK_OK:
break;
}
/* sign sid and the userauth request */
n = pack(send.b, sizeof(send.b), "sbsssbss",
sid, nsid,
MSG_USERAUTH_REQUEST,
user, strlen(user),
service, strlen(service),
authmeth, sizeof(authmeth)-1,
1,
sshrsa, sizeof(sshrsa)-1,
pk, npk);
S = pkcs1digest(send.b, n, pub);
n = snprint((char*)send.b, sizeof(send.b), "%B", S);
mpfree(S);
if(auth_rpc(rpc, "write", (char*)send.b, n) != ARok)
break;
if(auth_rpc(rpc, "read", nil, 0) != ARok)
break;
S = strtomp(rpc->arg, nil, 16, nil);
nsig = rsasig2ssh(pub, S, sig, sizeof(sig));
mpfree(S);
/* send final userauth request with the signature */
sendpkt("bsssbsss", MSG_USERAUTH_REQUEST,
user, strlen(user),
service, strlen(service),
authmeth, sizeof(authmeth)-1,
1,
sshrsa, sizeof(sshrsa)-1,
pk, npk,
sig, nsig);
Next2: switch(recvpkt()){
default:
dispatch();
goto Next2;
case MSG_USERAUTH_FAILURE:
if(authfailure(authmeth))
goto Failed;
continue;
case MSG_USERAUTH_SUCCESS:
break;
}
rsapubfree(pub);
auth_freerpc(rpc);
close(afd);
return 0;
}
Failed:
rsapubfree(pub);
auth_freerpc(rpc);
close(afd);
return -1;
}
int
passauth(void)
{
static char authmeth[] = "password";
UserPasswd *up;
if(!authok(authmeth))
return -1;
up = auth_getuserpasswd(auth_getkey, "proto=pass servive=ssh user=%q server=%q thumb=%q",
user, host, thumb);
if(up == nil)
return -1;
sendpkt("bsssbs", MSG_USERAUTH_REQUEST,
user, strlen(user),
service, strlen(service),
authmeth, sizeof(authmeth)-1,
0,
up->passwd, strlen(up->passwd));
memset(up->passwd, 0, strlen(up->passwd));
free(up);
Next0: switch(recvpkt()){
default:
dispatch();
goto Next0;
case MSG_USERAUTH_FAILURE:
werrstr("wrong password");
authfailure(authmeth);
return -1;
case MSG_USERAUTH_SUCCESS:
return 0;
}
}
int
kbintauth(void)
{
static char authmeth[] = "keyboard-interactive";
char *name, *inst, *s, *a;
int fd, i, n, m;
int nquest, echo;
uchar *ans, *answ;
if(!authok(authmeth))
return -1;
sendpkt("bsssss", MSG_USERAUTH_REQUEST,
user, strlen(user),
service, strlen(service),
authmeth, sizeof(authmeth)-1,
"", 0,
"", 0);
Next0: switch(recvpkt()){
default:
dispatch();
goto Next0;
case MSG_USERAUTH_FAILURE:
authfailure(authmeth);
return -1;
case MSG_USERAUTH_SUCCESS:
return 0;
case MSG_USERAUTH_INFO_REQUEST:
break;
}
Retry:
if((fd = open("/dev/cons", OWRITE)) < 0)
return -1;
if(unpack(recv.r, recv.w-recv.r, "_ss.", &name, &n, &inst, &m, &recv.r) < 0)
sysfatal("bad info request: name, inst");
while(n > 0 && strchr("\r\n\t ", name[n-1]) != nil)
n--;
while(m > 0 && strchr("\r\n\t ", inst[m-1]) != nil)
m--;
if(n > 0)
fprint(fd, "%.*s\n", n, name);
if(m > 0)
fprint(fd, "%.*s\n", m, inst);
/* lang, nprompt */
if(unpack(recv.r, recv.w-recv.r, "su.", &s, &n, &nquest, &recv.r) < 0)
sysfatal("bad info request: lang, #quest");
ans = answ = nil;
for(i = 0; i < nquest; i++){
if(unpack(recv.r, recv.w-recv.r, "sb.", &s, &n, &echo, &recv.r) < 0)
sysfatal("bad info request: question [%d]", i);
while(n > 0 && strchr("\r\n\t :", s[n-1]) != nil)
n--;
s[n] = '\0';
if((a = readcons(s, nil, !echo)) == nil)
sysfatal("readcons: %r");
n = answ - ans;
m = strlen(a)+4;
if((s = realloc(ans, n + m)) == nil)
sysfatal("realloc: %r");
ans = (uchar*)s;
answ = ans+n;
answ += pack(answ, m, "s", a, m-4);
}
sendpkt("bu[", MSG_USERAUTH_INFO_RESPONSE, i, ans, answ - ans);
free(ans);
close(fd);
Next1: switch(recvpkt()){
default:
dispatch();
goto Next1;
case MSG_USERAUTH_INFO_REQUEST:
goto Retry;
case MSG_USERAUTH_FAILURE:
authfailure(authmeth);
return -1;
case MSG_USERAUTH_SUCCESS:
return 0;
}
}
void
dispatch(void)
{
char *s;
uchar *p;
int n, b, c;
switch(recv.r[0]){
case MSG_IGNORE:
case MSG_GLOBAL_REQUEST:
return;
case MSG_DISCONNECT:
if(unpack(recv.r, recv.w-recv.r, "_us", &c, &s, &n) < 0)
break;
sysfatal("disconnect: (%d) %.*s", c, n, s);
return;
case MSG_DEBUG:
if(unpack(recv.r, recv.w-recv.r, "__sb", &s, &n, &c) < 0)
break;
if(c != 0 || debug) fprint(2, "%s: %.*s\n", argv0, n, s);
return;
case MSG_USERAUTH_BANNER:
if(unpack(recv.r, recv.w-recv.r, "_s", &s, &n) < 0)
break;
if(raw) write(2, s, n);
return;
case MSG_CHANNEL_DATA:
if(unpack(recv.r, recv.w-recv.r, "_us", &c, &s, &n) < 0)
break;
if(c != recv.chan)
break;
if(write(1, s, n) != n)
sysfatal("write out: %r");
Winadjust:
recv.win -= n;
if(recv.win < recv.pkt){
n = WinPackets*recv.pkt;
recv.win += n;
sendpkt("buu", MSG_CHANNEL_WINDOW_ADJUST, send.chan, n);
}
return;
case MSG_CHANNEL_EXTENDED_DATA:
if(unpack(recv.r, recv.w-recv.r, "_uus", &c, &b, &s, &n) < 0)
break;
if(c != recv.chan)
break;
if(b == 1) write(2, s, n);
goto Winadjust;
case MSG_CHANNEL_WINDOW_ADJUST:
if(unpack(recv.r, recv.w-recv.r, "_uu", &c, &n) < 0)
break;
if(c != recv.chan)
break;
send.win += n;
if(send.win >= send.pkt)
rwakeup(&send);
return;
case MSG_CHANNEL_REQUEST:
if(unpack(recv.r, recv.w-recv.r, "_usb.", &c, &s, &n, &b, &p) < 0)
break;
if(c != recv.chan)
break;
if(n == 11 && memcmp(s, "exit-signal", n) == 0){
if(unpack(p, recv.w-p, "s", &s, &n) < 0)
break;
if(n != 0 && status == nil)
status = smprint("%.*s", n, s);
} else if(n == 11 && memcmp(s, "exit-status", n) == 0){
if(unpack(p, recv.w-p, "u", &n) < 0)
break;
if(n != 0 && status == nil)
status = smprint("%d", n);
} else if(debug) {
fprint(2, "%s: channel request: %.*s\n", argv0, n, s);
}
return;
case MSG_CHANNEL_EOF:
recv.eof = 1;
if(!raw) write(1, "", 0);
return;
case MSG_CHANNEL_CLOSE:
shutdown();
return;
case MSG_KEXINIT:
kex(1);
return;
}
sysfatal("got: %.*H", (int)(recv.w - recv.r), recv.r);
}
char*
readline(void)
{
uchar *p;
for(p = send.b; p < &send.b[sizeof(send.b)-1]; p++){
*p = '\0';
if(read(fd, p, 1) != 1 || *p == '\n')
break;
}
while(p >= send.b && (*p == '\n' || *p == '\r'))
*p-- = '\0';
return (char*)send.b;
}
static struct {
char *term;
int xpixels;
int ypixels;
int lines;
int cols;
} tty = {
"dumb",
0,
0,
0,
0,
};
void
rawon(void)
{
int ctl;
char *s;
close(0);
if(open("/dev/cons", OREAD) != 0)
sysfatal("open: %r");
close(1);
if(open("/dev/cons", OWRITE) != 1)
sysfatal("open: %r");
dup(1, 2);
if((ctl = open("/dev/consctl", OWRITE)) >= 0)
write(ctl, "rawon", 5);
if(s = getenv("TERM")){
tty.term = s;
if(s = getenv("XPIXELS")){
tty.xpixels = atoi(s);
free(s);
}
if(s = getenv("YPIXELS")){
tty.ypixels = atoi(s);
free(s);
}
if(s = getenv("LINES")){
tty.lines = atoi(s);
free(s);
}
if(s = getenv("COLS")){
tty.cols = atoi(s);
free(s);
}
}
}
void
usage(void)
{
fprint(2, "usage: %s [-dR] [-t thumbfile] [-u user] [user@]host [cmd]\n", argv0);
exits("usage");
}
void
main(int argc, char *argv[])
{
static QLock sl;
int b, n, c;
char *s;
quotefmtinstall();
fmtinstall('B', mpfmt);
fmtinstall('H', encodefmt);
s = getenv("TERM");
raw = s != nil && strcmp(s, "dumb") != 0;
free(s);
ARGBEGIN {
case 'd':
debug++;
break;
case 'R':
raw = 0;
break;
case 'u':
user = EARGF(usage());
break;
case 't':
thumbfile = EARGF(usage());
break;
} ARGEND;
if(argc == 0)
usage();
host = *argv++;
if(user == nil){
s = strchr(host, '@');
if(s != nil){
*s++ = '\0';
user = host;
host = s;
}
}
for(cmd = nil; *argv != nil; argv++){
if(cmd == nil)
cmd = strdup(*argv);
else {
s = smprint("%s %q", cmd, *argv);
free(cmd);
cmd = s;
}
}
if(cmd != nil)
raw = 0;
if((fd = dial(netmkaddr(host, nil, "ssh"), nil, nil, nil)) < 0)
sysfatal("dial: %r");
send.v = "SSH-2.0-(9)";
fprint(fd, "%s\r\n", send.v);
recv.v = readline();
if(debug)
fprint(2, "server verison: %s\n", recv.v);
if(strncmp("SSH-2.0-", recv.v, 8) != 0)
sysfatal("bad server version: %s", recv.v);
recv.v = strdup(recv.v);
send.l = recv.l = &sl;
if(user == nil)
user = getuser();
if(thumbfile == nil)
thumbfile = smprint("%s/lib/sshthumbs", getenv("home"));
kex(0);
service = "ssh-connection";
sendpkt("bs", MSG_SERVICE_REQUEST, "ssh-userauth", 12);
Next0: switch(recvpkt()){
default:
dispatch();
goto Next0;
case MSG_SERVICE_ACCEPT:
break;
}
if(pubkeyauth() < 0 && passauth() < 0 && kbintauth() < 0)
sysfatal("auth: %r");
recv.pkt = MaxPacket;
recv.win = WinPackets*recv.pkt;
recv.chan = 0;
/* open hailing frequencies */
sendpkt("bsuuu", MSG_CHANNEL_OPEN,
"session", 7,
recv.chan,
recv.win,
recv.pkt);
Next1: switch(recvpkt()){
default:
dispatch();
goto Next1;
case MSG_CHANNEL_OPEN_FAILURE:
if(unpack(recv.r, recv.w-recv.r, "_uus", &c, &b, &s, &n) < 0)
n = strlen(s = "???");
sysfatal("channel open failure: (%d) %.*s", b, n, s);
case MSG_CHANNEL_OPEN_CONFIRMATION:
break;
}
if(unpack(recv.r, recv.w-recv.r, "_uuuu", &recv.chan, &send.chan, &send.win, &send.pkt) < 0)
sysfatal("bad channel open confirmation");
if(send.pkt <= 0 || send.pkt > MaxPacket)
send.pkt = MaxPacket;
notify(catch);
atexit(shutdown);
recv.pid = getpid();
n = rfork(RFPROC|RFMEM);
if(n < 0)
sysfatal("fork: %r");
/* parent reads and dispatches packets */
if(n > 0) {
send.pid = n;
while((send.eof|recv.eof) == 0){
recvpkt();
qlock(&sl);
dispatch();
if((int)(send.kex - send.seq) <= 0 || (int)(recv.kex - recv.seq) <= 0)
kex(0);
qunlock(&sl);
}
exits(status);
}
/* child reads input and sends packets */
qlock(&sl);
if(raw) {
rawon();
sendpkt("busbsuuuus", MSG_CHANNEL_REQUEST,
send.chan,
"pty-req", 7,
0,
tty.term, strlen(tty.term),
tty.cols,
tty.lines,
tty.xpixels,
tty.ypixels,
"", 0);
}
if(cmd == nil){
sendpkt("busb", MSG_CHANNEL_REQUEST,
send.chan,
"shell", 5,
0);
} else {
sendpkt("busbs", MSG_CHANNEL_REQUEST,
send.chan,
"exec", 4,
0,
cmd, strlen(cmd));
}
for(;;){
static uchar buf[MaxPacket];
qunlock(&sl);
n = read(0, buf, send.pkt);
qlock(&sl);
if(send.eof)
break;
if(n < 0 && wasintr()){
if(!raw) break;
sendpkt("busbs", MSG_CHANNEL_REQUEST,
send.chan,
"signal", 6,
0,
"INT", 3);
intr = 0;
continue;
}
if(n <= 0)
break;
send.win -= n;
while(send.win < 0)
rsleep(&send);
sendpkt("bus", MSG_CHANNEL_DATA,
send.chan,
buf, n);
}
if(send.eof++ == 0)
sendpkt("bu", raw ? MSG_CHANNEL_CLOSE : MSG_CHANNEL_EOF, send.chan);
qunlock(&sl);
exits(nil);
}