WinPackets = 8, // (1<<15) * 8 = 256K
};
+int MaxPwTries = 3; // retry this often for keyboard-interactive
+
typedef struct
{
u32int seq;
int nsid;
uchar sid[256];
-char thumb[2*SHA2_256dlen+1];
+char thumb[2*SHA2_256dlen+1], *thumbfile;
-int fd, intr, raw, debug;
-char *user, *service, *status, *host, *cmd;
+int fd, intr, raw, port, mux, debug;
+char *user, *service, *status, *host, *remote, *cmd;
Oneway recv, send;
void dispatch(void);
void
catch(void*, char *msg)
{
- if(strstr(msg, "interrupt") != nil){
+ if(strcmp(msg, "interrupt") == 0){
intr = 1;
noted(NCONT);
}
char err[ERRMAX];
int r;
- if(intr)
- return 1;
memset(err, 0, sizeof(err));
errstr(err, sizeof(err));
- r = strstr(err, "interrupt") != nil;
+ r = strcmp(err, "interrupted") == 0;
errstr(err, sizeof(err));
return r;
}
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)
{
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 macalgs[] = "hmac-sha1"; /* work around for github.com */
static char langs[] = "";
uchar cookie[16], x[32], yc[32], z[32], k[32+1], h[SHA2_256dlen], *ys, *ks, *sig;
sendpkt("b[ssssssssssbu", MSG_KEXINIT,
cookie, sizeof(cookie),
kexalgs, sizeof(kexalgs)-1,
- hostkeyalgs, sizeof(hostkeyalgs)-1,
+ sshrsa, sizeof(sshrsa)-1,
cipheralgs, sizeof(cipheralgs)-1,
cipheralgs, sizeof(cipheralgs)-1,
macalgs, sizeof(macalgs)-1,
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);
+ fprint(2, "%s: %.*s\n", *t, utfnlen(s, n), s);
}
}
ds = hashstr(yc, 32, ds);
ds = hashstr(ys, 32, ds);
- sha2_256(ks, nks, h, nil);
- i = snprint(thumb, sizeof(thumb), "%.*[", sizeof(h), h);
- while(i > 0 && thumb[i-1] == '=')
- thumb[--i] = '\0';
-
-if(debug)
- fprint(2, "host fingerprint: %s\n", thumb);
+ 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\n", argv0);
+ fprint(2, "verify hostkey: %s %.*[\n", sshrsa, nks, ks);
+ fprint(2, "add thumbprint after verification:\n");
+ 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(unpack(recv.r, recv.w-recv.r, "_sb", &s, &n, &partial) < 0)
sysfatal("bad auth failure response");
free(authnext);
- authnext = smprint("%.*s", n, s);
+ authnext = smprint("%.*s", utfnlen(s, n), s);
if(debug)
fprint(2, "userauth %s failed: partial=%d, next=%s\n", meth, partial, authnext);
return partial != 0 || !authok(meth);
}
+int
+noneauth(void)
+{
+ static char authmeth[] = "none";
+
+ if(!authok(authmeth))
+ return -1;
+
+ sendpkt("bsss", MSG_USERAUTH_REQUEST,
+ user, strlen(user),
+ service, strlen(service),
+ authmeth, sizeof(authmeth)-1);
+
+Next0: switch(recvpkt()){
+ default:
+ dispatch();
+ goto Next0;
+ case MSG_USERAUTH_FAILURE:
+ werrstr("authentication needed");
+ authfailure(authmeth);
+ return -1;
+ case MSG_USERAUTH_SUCCESS:
+ return 0;
+ }
+}
+
int
pubkeyauth(void)
{
if(!authok(authmeth))
return -1;
- up = auth_getuserpasswd(auth_getkey, "proto=pass servive=ssh user=%q server=%q thumb=%q",
+ up = auth_getuserpasswd(auth_getkey, "proto=pass service=ssh user=%q server=%q thumb=%q",
user, host, thumb);
if(up == nil)
return -1;
kbintauth(void)
{
static char authmeth[] = "keyboard-interactive";
+ int tries;
char *name, *inst, *s, *a;
int fd, i, n, m;
int nquest, echo;
uchar *ans, *answ;
+ tries = 0;
if(!authok(authmeth))
return -1;
+Loop:
+ if(++tries > MaxPwTries)
+ return -1;
+
sendpkt("bsssss", MSG_USERAUTH_REQUEST,
user, strlen(user),
service, strlen(service),
dispatch();
goto Next0;
case MSG_USERAUTH_FAILURE:
- authfailure(authmeth);
- return -1;
+ werrstr("keyboard-interactive failed");
+ if(authfailure(authmeth))
+ return -1;
+ goto Loop;
case MSG_USERAUTH_SUCCESS:
return 0;
case MSG_USERAUTH_INFO_REQUEST:
m--;
if(n > 0)
- fprint(fd, "%.*s\n", n, name);
+ fprint(fd, "%.*s\n", utfnlen(name, n), name);
if(m > 0)
- fprint(fd, "%.*s\n", m, inst);
+ fprint(fd, "%.*s\n", utfnlen(inst, m), inst);
/* lang, nprompt */
if(unpack(recv.r, recv.w-recv.r, "su.", &s, &n, &nquest, &recv.r) < 0)
case MSG_USERAUTH_INFO_REQUEST:
goto Retry;
case MSG_USERAUTH_FAILURE:
- authfailure(authmeth);
- return -1;
+ werrstr("keyboard-interactive failed");
+ if(authfailure(authmeth))
+ return -1;
+ goto Loop;
case MSG_USERAUTH_SUCCESS:
return 0;
}
switch(recv.r[0]){
case MSG_IGNORE:
+ return;
case MSG_GLOBAL_REQUEST:
+ if(unpack(recv.r, recv.w-recv.r, "_sb", &s, &n, &b) < 0)
+ break;
+ if(debug)
+ fprint(2, "%s: global request: %.*s\n",
+ argv0, utfnlen(s, n), s);
+ if(b != 0)
+ sendpkt("b", MSG_REQUEST_FAILURE);
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);
+ sysfatal("disconnect: (%d) %.*s", c, utfnlen(s, 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);
+ if(c != 0 || debug)
+ fprint(2, "%s: %.*s\n", argv0, utfnlen(s, 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_KEXINIT:
+ kex(1);
+ return;
+ }
+
+ if(mux){
+ n = recv.w - recv.r;
+ if(write(1, recv.r, n) != n)
+ sysfatal("write out: %r");
+ return;
+ }
+
+ switch(recv.r[0]){
case MSG_CHANNEL_DATA:
if(unpack(recv.r, recv.w-recv.r, "_us", &c, &s, &n) < 0)
break;
if(unpack(p, recv.w-p, "s", &s, &n) < 0)
break;
if(n != 0 && status == nil)
- status = smprint("%.*s", n, s);
+ status = smprint("%.*s", utfnlen(s, n), s);
+ c = MSG_CHANNEL_SUCCESS;
} 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);
+ c = MSG_CHANNEL_SUCCESS;
+ } else {
+ if(debug)
+ fprint(2, "%s: channel request: %.*s\n",
+ argv0, utfnlen(s, n), s);
+ c = MSG_CHANNEL_FAILURE;
}
+ if(b != 0)
+ sendpkt("bu", c, recv.chan);
return;
case MSG_CHANNEL_EOF:
recv.eof = 1;
case MSG_CHANNEL_CLOSE:
shutdown();
return;
- case MSG_KEXINIT:
- kex(1);
- return;
}
sysfatal("got: %.*H", (int)(recv.w - recv.r), recv.r);
}
int ypixels;
int lines;
int cols;
-} tty = {
- "dumb",
- 0,
- 0,
- 0,
- 0,
-};
+} tty;
+
+void
+getdim(void)
+{
+ char *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
rawon(void)
{
int ctl;
- char *s;
close(0);
if(open("/dev/cons", OREAD) != 0)
if(open("/dev/cons", OWRITE) != 1)
sysfatal("open: %r");
dup(1, 2);
- if((ctl = open("/dev/consctl", OWRITE)) >= 0)
+ 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);
- }
+ write(ctl, "winchon", 7); /* vt(1): interrupt note on window change */
}
+ getdim();
+}
+
+#pragma varargck type "k" char*
+
+kfmt(Fmt *f)
+{
+ char *s, *p;
+ int n;
+
+ s = va_arg(f->args, char*);
+ n = fmtstrcpy(f, "'");
+ while((p = strchr(s, '\'')) != nil){
+ *p = '\0';
+ n += fmtstrcpy(f, s);
+ *p = '\'';
+ n += fmtstrcpy(f, "'\\''");
+ s = p+1;
+ }
+ n += fmtstrcpy(f, s);
+ n += fmtstrcpy(f, "'");
+ return n;
}
void
usage(void)
{
- fprint(2, "usage: %s [-dR] [-u user] [user@]host [cmd]\n", argv0);
+ fprint(2, "usage: %s [-dR] [-t thumbfile] [-T tries] [-u user] [-h] [user@]host [-W remote!port] [cmd args...]\n", argv0);
exits("usage");
}
fmtinstall('B', mpfmt);
fmtinstall('H', encodefmt);
fmtinstall('[', encodefmt);
+ fmtinstall('k', kfmt);
- s = getenv("TERM");
- raw = s != nil && strcmp(s, "dumb") != 0;
- free(s);
+ tty.term = getenv("TERM");
+ if(tty.term == nil)
+ tty.term = "";
+ raw = *tty.term != 0;
ARGBEGIN {
case 'd':
debug++;
break;
+ case 'W':
+ remote = EARGF(usage());
+ s = strrchr(remote, '!');
+ if(s == nil)
+ s = strrchr(remote, ':');
+ if(s == nil)
+ usage();
+ *s++ = 0;
+ port = atoi(s);
+ raw = 0;
+ break;
case 'R':
raw = 0;
break;
+ case 'r':
+ raw = 2; /* bloody */
+ break;
case 'u':
user = EARGF(usage());
break;
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 't':
+ thumbfile = EARGF(usage());
+ break;
+ case 'T':
+ MaxPwTries = strtol(EARGF(usage()), &s, 0);
+ if(*s != 0) usage();
+ break;
+ case 'X':
+ mux = 1;
+ raw = 0;
+ break;
+ default:
+ usage();
} ARGEND;
- if(argc == 0)
- usage();
+ if(host == nil){
+ if(argc == 0)
+ usage();
+ host = *argv++;
+ }
- host = *argv++;
if(user == nil){
s = strchr(host, '@');
if(s != nil){
host = s;
}
}
+
for(cmd = nil; *argv != nil; argv++){
- if(cmd == nil)
+ if(cmd == nil){
cmd = strdup(*argv);
- else {
- s = smprint("%s %q", cmd, *argv);
+ if(raw == 1)
+ raw = 0;
+ }else{
+ s = smprint("%s %k", cmd, *argv);
free(cmd);
cmd = s;
}
}
- if(cmd != nil)
- raw = 0;
+
+ if(remote != nil && cmd != nil)
+ usage();
if((fd = dial(netmkaddr(host, nil, "ssh"), nil, nil, nil)) < 0)
sysfatal("dial: %r");
send.l = recv.l = &sl;
- kex(0);
-
if(user == nil)
user = getuser();
- service = "ssh-connection";
+ if(thumbfile == nil)
+ thumbfile = smprint("%s/lib/sshthumbs", getenv("home"));
+
+ kex(0);
sendpkt("bs", MSG_SERVICE_REQUEST, "ssh-userauth", 12);
Next0: switch(recvpkt()){
break;
}
- if(pubkeyauth() < 0 && passauth() < 0 && kbintauth() < 0)
+ service = "ssh-connection";
+ if(noneauth() < 0 && pubkeyauth() < 0 && passauth() < 0 && kbintauth() < 0)
sysfatal("auth: %r");
- recv.pkt = MaxPacket;
- recv.win = WinPackets*recv.pkt;
- recv.chan = 0;
+ recv.pkt = send.pkt = MaxPacket;
+ recv.win = send.win = WinPackets*recv.pkt;
+ recv.chan = send.win = 0;
- /* open hailing frequencies */
- sendpkt("bsuuu", MSG_CHANNEL_OPEN,
- "session", 7,
- recv.chan,
- recv.win,
- recv.pkt);
+ if(mux)
+ goto Mux;
+ /* open hailing frequencies */
+ if(remote != nil){
+ NetConnInfo *nci = getnetconninfo(nil, fd);
+ if(nci == nil)
+ sysfatal("can't get netconninfo: %r");
+ sendpkt("bsuuususu", MSG_CHANNEL_OPEN,
+ "direct-tcpip", 12,
+ recv.chan,
+ recv.win,
+ recv.pkt,
+ remote, strlen(remote),
+ port,
+ nci->laddr, strlen(nci->laddr),
+ atoi(nci->lserv));
+ free(nci);
+ } else {
+ sendpkt("bsuuu", MSG_CHANNEL_OPEN,
+ "session", 7,
+ recv.chan,
+ recv.win,
+ recv.pkt);
+ }
Next1: switch(recvpkt()){
default:
dispatch();
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);
+ sysfatal("channel open failure: (%d) %.*s", b, utfnlen(s, n), s);
case MSG_CHANNEL_OPEN_CONFIRMATION:
break;
}
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");
+ if(remote != nil)
+ goto Mux;
- /* 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,
"shell", 5,
0);
+ } else if(*cmd == '#') {
+ sendpkt("busbs", MSG_CHANNEL_REQUEST,
+ send.chan,
+ "subsystem", 9,
+ 0,
+ cmd+1, strlen(cmd)-1);
} else {
sendpkt("busbs", MSG_CHANNEL_REQUEST,
send.chan,
0,
cmd, strlen(cmd));
}
+
+Mux:
+ 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(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);
for(;;){
static uchar buf[MaxPacket];
qunlock(&sl);
qlock(&sl);
if(send.eof)
break;
- if(n < 0 && wasintr()){
+ if(n < 0 && wasintr())
+ intr = 1;
+ if(intr){
if(!raw) break;
+ getdim();
+ sendpkt("busbuuuu", MSG_CHANNEL_REQUEST,
+ send.chan,
+ "window-change", 13,
+ 0,
+ tty.cols,
+ tty.lines,
+ tty.xpixels,
+ tty.ypixels);
sendpkt("busbs", MSG_CHANNEL_REQUEST,
send.chan,
"signal", 6,
}
if(n <= 0)
break;
+ if(mux){
+ sendpkt("[", buf, n);
+ continue;
+ }
send.win -= n;
while(send.win < 0)
rsleep(&send);
send.chan,
buf, n);
}
- if(send.eof++ == 0)
+ if(send.eof++ == 0 && !mux)
sendpkt("bu", raw ? MSG_CHANNEL_CLOSE : MSG_CHANNEL_EOF, send.chan);
+ else if(recv.pid > 0 && mux)
+ postnote(PNPROC, recv.pid, "shutdown");
qunlock(&sl);
exits(nil);