218 lines · 4.0 KB · C
RawDownload
/* * 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);	}}