sftpcache.c (4039B)
1 /* 2 * Multiplexor for sftp sessions. 3 * Assumes can parse session with sftp> prompts. 4 * Assumes clients are well-behaved and don't hang up the system. 5 * 6 * Stupid sftp bug: sftp invokes ssh, which always set O_NONBLOCK 7 * on 0, 1, and 2. Ssh inherits sftp's 2, so we can't use the output pipe 8 * on fd 2, since it will get set O_NONBLOCK, sftp won't notice, and 9 * writes will be lost. So instead we use a separate pipe for errors 10 * and consult it after each command. Assume the pipe buffer is 11 * big enough to hold the error output. 12 */ 13 #include <u.h> 14 #include <fcntl.h> 15 #include <libc.h> 16 #include <bio.h> 17 18 #undef pipe 19 20 int debug; 21 #define dprint if(debug)print 22 int sftpfd; 23 int sftperr; 24 Biobuf bin; 25 26 void 27 usage(void) 28 { 29 fprint(2, "usage: sftpcache system\n"); 30 exits("usage"); 31 } 32 33 char* 34 Brd(Biobuf *bin) 35 { 36 static char buf[1000]; 37 int c, tot; 38 39 tot = 0; 40 while((c = Bgetc(bin)) >= 0 && tot<sizeof buf){ 41 buf[tot++] = c; 42 if(c == '\n'){ 43 buf[tot] = 0; 44 dprint("OUT %s", buf); 45 return buf; 46 } 47 if(c == ' ' && tot == 6 && memcmp(buf, "sftp> ", 5) == 0){ 48 buf[tot] = 0; 49 dprint("OUT %s\n", buf); 50 return buf; 51 } 52 } 53 if(tot == sizeof buf) 54 sysfatal("response too long"); 55 return nil; 56 } 57 58 int 59 readstr(int fd, char *a, int n) 60 { 61 int i; 62 63 for(i=0; i<n; i++){ 64 if(read(fd, a+i, 1) != 1) 65 return -1; 66 if(a[i] == '\n'){ 67 a[i] = 0; 68 return i; 69 } 70 } 71 return n; 72 } 73 74 void 75 doerrors(int fd) 76 { 77 char buf[100]; 78 int n, first; 79 80 first = 1; 81 while((n = read(sftperr, buf, sizeof buf)) > 0){ 82 if(debug){ 83 if(first){ 84 first = 0; 85 fprint(2, "OUT errors:\n"); 86 } 87 write(1, buf, n); 88 } 89 write(fd, buf, n); 90 } 91 } 92 93 void 94 bell(void *x, char *msg) 95 { 96 if(strcmp(msg, "sys: child") == 0 || strcmp(msg, "sys: write on closed pipe") == 0) 97 sysfatal("sftp exited"); 98 if(strcmp(msg, "alarm") == 0) 99 noted(NCONT); 100 noted(NDFLT); 101 } 102 103 void 104 main(int argc, char **argv) 105 { 106 char buf[200], cmd[1000], *q, *s; 107 char dir[100], ndir[100]; 108 int p[2], px[2], pe[2], pid, ctl, nctl, fd, n; 109 110 notify(bell); 111 fmtinstall('H', encodefmt); 112 113 ARGBEGIN{ 114 case 'D': 115 debug = 1; 116 break; 117 default: 118 usage(); 119 }ARGEND 120 121 if(argc != 1) 122 usage(); 123 124 if(pipe(p) < 0 || pipe(px) < 0 || pipe(pe) < 0) 125 sysfatal("pipe: %r"); 126 pid = fork(); 127 if(pid < 0) 128 sysfatal("fork: %r"); 129 if(pid == 0){ 130 close(p[1]); 131 close(px[0]); 132 close(pe[0]); 133 dup(p[0], 0); 134 dup(px[1], 1); 135 dup(pe[1], 2); 136 if(p[0] > 2) 137 close(p[0]); 138 if(px[1] > 2) 139 close(px[1]); 140 if(pe[1] > 2) 141 close(pe[1]); 142 execl("sftp", "sftp", "-b", "/dev/stdin", argv[0], nil); 143 sysfatal("exec sftp: %r"); 144 } 145 146 close(p[0]); 147 close(px[1]); 148 close(pe[1]); 149 150 sftpfd = p[1]; 151 sftperr = pe[0]; 152 Binit(&bin, px[0], OREAD); 153 154 fcntl(sftperr, F_SETFL, fcntl(sftperr, F_GETFL, 0)|O_NONBLOCK); 155 156 do 157 q = Brd(&bin); 158 while(q && strcmp(q, "sftp> ") != 0); 159 if(q == nil) 160 sysfatal("unexpected eof"); 161 162 snprint(buf, sizeof buf, "unix!%s/%s.sftp", getns(), argv[0]); 163 ctl = announce(buf, dir); 164 if(ctl < 0) 165 sysfatal("announce %s: %r", buf); 166 167 pid = fork(); 168 if(pid < 0) 169 sysfatal("fork"); 170 if(pid != 0) 171 exits(nil); 172 173 for(;;){ 174 nctl = listen(dir, ndir); 175 if(nctl < 0) 176 sysfatal("listen %s: %r", buf); 177 fd = accept(ctl, ndir); 178 close(nctl); 179 if(fd < 0) 180 continue; 181 for(;;){ 182 /* alarm(1000); */ 183 n = readstr(fd, cmd, sizeof cmd); 184 /* alarm(0); */ 185 if(n <= 0) 186 break; 187 dprint("CMD %s\n", cmd); 188 if(strcmp(cmd, "DONE") == 0){ 189 fprint(fd, "DONE\n"); 190 break; 191 } 192 fprint(sftpfd, "-%s\n", cmd); 193 q = Brd(&bin); 194 if(*q==0 || q[strlen(q)-1] != '\n') 195 sysfatal("unexpected response"); 196 q[strlen(q)-1] = 0; 197 if(q[0] != '-' || strcmp(q+1, cmd) != 0) 198 sysfatal("unexpected response"); 199 while((q = Brd(&bin)) != nil){ 200 if(strcmp(q, "sftp> ") == 0){ 201 doerrors(fd); 202 break; 203 } 204 s = q+strlen(q); 205 while(s > q && (s[-1] == ' ' || s[-1] == '\n' || s[-1] == '\t' || s[-1] == '\r')) 206 s--; 207 *s = 0; 208 fprint(fd, "%s\n", q); 209 } 210 if(q == nil){ 211 fprint(fd, "!!! unexpected eof\n"); 212 sysfatal("unexpected eof"); 213 } 214 } 215 close(fd); 216 } 217 }