/* * 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 <u.h>#include <fcntl.h>#include <libc.h>#include <bio.h> #undef pipe int debug;#define dprint if(debug)printint sftpfd;int sftperr;Biobuf bin; voidusage(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<sizeof buf){ buf[tot++] = c; if(c == '\n'){ buf[tot] = 0; dprint("OUT %s", buf); return buf; } if(c == ' ' && tot == 6 && memcmp(buf, "sftp> ", 5) == 0){ buf[tot] = 0; dprint("OUT %s\n", buf); return buf; } } if(tot == sizeof buf) sysfatal("response too long"); return nil;} intreadstr(int fd, char *a, int n){ int i; for(i=0; i<n; i++){ if(read(fd, a+i, 1) != 1) return -1; if(a[i] == '\n'){ a[i] = 0; return i; } } return n;} voiddoerrors(int fd){ char buf[100]; int n, first; first = 1; while((n = read(sftperr, buf, sizeof buf)) > 0){ if(debug){ if(first){ first = 0; fprint(2, "OUT errors:\n"); } write(1, buf, n); } write(fd, buf, n); }} voidbell(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);} voidmain(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){ fprint(fd, "DONE\n"); 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(q[0] != '-' || strcmp(q+1, 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); }}
/*
* 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 <u.h>
#include <fcntl.h>
#include <libc.h>
#include <bio.h>
#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<sizeof buf){
buf[tot++] = c;
if(c == '\n'){
buf[tot] = 0;
dprint("OUT %s", buf);
return buf;
}
if(c == ' ' && tot == 6 && memcmp(buf, "sftp> ", 5) == 0){
buf[tot] = 0;
dprint("OUT %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<n; i++){
if(read(fd, a+i, 1) != 1)
return -1;
if(a[i] == '\n'){
a[i] = 0;
return i;
}
}
return n;
}
void
doerrors(int fd)
{
char buf[100];
int n, first;
first = 1;
while((n = read(sftperr, buf, sizeof buf)) > 0){
if(debug){
if(first){
first = 0;
fprint(2, "OUT errors:\n");
}
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){
fprint(fd, "DONE\n");
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(q[0] != '-' || strcmp(q+1, 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);
}
}