commit e96129189712a83de71d45cbc852a54c5960d575 from: rsc date: Mon Mar 21 07:37:13 2005 UTC sftp session cache commit - b1e32ef3661879a140728c4ee32b382d7c71679f commit + e96129189712a83de71d45cbc852a54c5960d575 blob - /dev/null blob + cfe60ab1ea127b47cb1be76a32aa3ce577d83280 (mode 644) --- /dev/null +++ src/cmd/sftpcache.c @@ -0,0 +1,210 @@ +/* + * Multiplexor for sftp sessions. + * Assumes can parse session with sftp> prompts. + * Assumes clients are well-behaved and don't hang up the system. + * + * Stupid sftp bug: sftp invokes ssh, which always set O_NONBLOCK + * on 0, 1, and 2. Ssh inherits sftp's 2, so we can't use the output pipe + * on fd 2, since it will get set O_NONBLOCK, sftp won't notice, and + * writes will be lost. So instead we use a separate pipe for errors + * and consult it after each command. Assume the pipe buffer is + * big enough to hold the error output. + */ +#include +#include +#include +#include + +#undef pipe + +int debug; +#define dprint if(debug)print +int sftpfd; +int sftperr; +Biobuf bin; + +void +usage(void) +{ + fprint(2, "usage: sftpcache system\n"); + exits("usage"); +} + +char* +Brd(Biobuf *bin) +{ + static char buf[1000]; + int c, tot; + + tot = 0; + while((c = Bgetc(bin)) >= 0 && tot ", 5) == 0){ + buf[tot] = 0; + dprint("%s\n", buf); + return buf; + } + } + if(tot == sizeof buf) + sysfatal("response too long"); + return nil; +} + +int +readstr(int fd, char *a, int n) +{ + int i; + + for(i=0; i 0){ + if(debug) + write(1, buf, n); + write(fd, buf, n); + } +} + +void +bell(void *x, char *msg) +{ + if(strcmp(msg, "sys: child") == 0 || strcmp(msg, "sys: write on closed pipe") == 0) + sysfatal("sftp exited"); + if(strcmp(msg, "alarm") == 0) + noted(NCONT); + noted(NDFLT); +} + +void +main(int argc, char **argv) +{ + char buf[200], cmd[1000], *q, *s; + char dir[100], ndir[100]; + int p[2], px[2], pe[2], pid, ctl, nctl, fd, n; + + notify(bell); + fmtinstall('H', encodefmt); + + ARGBEGIN{ + case 'D': + debug = 1; + break; + default: + usage(); + }ARGEND + + if(argc != 1) + usage(); + + if(pipe(p) < 0 || pipe(px) < 0 || pipe(pe) < 0) + sysfatal("pipe: %r"); + pid = fork(); + if(pid < 0) + sysfatal("fork: %r"); + if(pid == 0){ + close(p[1]); + close(px[0]); + close(pe[0]); + dup(p[0], 0); + dup(px[1], 1); + dup(pe[1], 2); + if(p[0] > 2) + close(p[0]); + if(px[1] > 2) + close(px[1]); + if(pe[1] > 2) + close(pe[1]); + execl("sftp", "sftp", "-b", "/dev/stdin", argv[0], nil); + sysfatal("exec sftp: %r"); + } + + close(p[0]); + close(px[1]); + close(pe[1]); + + sftpfd = p[1]; + sftperr = pe[0]; + Binit(&bin, px[0], OREAD); + + fcntl(sftperr, F_SETFL, fcntl(sftperr, F_GETFL, 0)|O_NONBLOCK); + + do + q = Brd(&bin); + while(q && strcmp(q, "sftp> ") != 0); + if(q == nil) + sysfatal("unexpected eof"); + + snprint(buf, sizeof buf, "unix!%s/%s.sftp", getns(), argv[0]); + ctl = announce(buf, dir); + if(ctl < 0) + sysfatal("announce %s: %r", buf); + + pid = fork(); + if(pid < 0) + sysfatal("fork"); + if(pid != 0) + exits(nil); + + for(;;){ + nctl = listen(dir, ndir); + if(nctl < 0) + sysfatal("listen %s: %r", buf); + fd = accept(ctl, ndir); + close(nctl); + if(fd < 0) + continue; + for(;;){ + // alarm(1000); + n = readstr(fd, cmd, sizeof cmd); + // alarm(0); + if(n <= 0) + break; + dprint("CMD %s\n", cmd); + if(strcmp(cmd, "DONE") == 0) + break; + fprint(sftpfd, "%s\n", cmd); + q = Brd(&bin); + if(*q==0 || q[strlen(q)-1] != '\n') + sysfatal("unexpected response"); + q[strlen(q)-1] = 0; + if(strcmp(q, cmd) != 0) + sysfatal("unexpected response"); + while((q = Brd(&bin)) != nil){ + if(strcmp(q, "sftp> ") == 0){ + doerrors(fd); + break; + } + s = q+strlen(q); + while(s > q && (s[-1] == ' ' || s[-1] == '\n' || s[-1] == '\t' || s[-1] == '\r')) + s--; + *s = 0; + fprint(fd, "%s\n", q); + } + if(q == nil){ + fprint(fd, "!!! unexpected eof\n"); + sysfatal("unexpected eof"); + } + } + close(fd); + } +} +