1implement Transport; 2 3include "common.m"; 4include "transport.m"; 5 6# local copies from CU 7sys: Sys; 8U: Url; 9 Parsedurl: import U; 10S: String; 11DI: Dial; 12CU: CharonUtils; 13 Netconn, ByteSource, Header, config: import CU; 14 15FTPPORT: con 21; 16 17# Return codes 18Extra, Success, Incomplete, TempFail, PermFail : con (1+iota); 19 20cmdbuf := array[200] of byte; 21dbg := 0; 22 23init(c: CharonUtils) 24{ 25 CU = c; 26 sys = load Sys Sys->PATH; 27 S = load String String->PATH; 28 U = load Url Url->PATH; 29 if (U != nil) 30 U->init(); 31 DI = CU->DI; 32 dbg = int (CU->config).dbg['n']; 33} 34 35connect(nc: ref Netconn, bs: ref ByteSource) 36{ 37 port := nc.port; 38 if(port == 0) 39 port = FTPPORT; 40 addr := DI->netmkaddr(nc.host, "net", string port); 41 if(dbg) 42 sys->print("ftp %d: dialing %s\n", nc.id, addr); 43 err := ""; 44 ctlfd : ref sys->FD = nil; 45 nc.conn = DI->dial(addr, nil); 46 if(nc.conn == nil) { 47 syserr := sys->sprint("%r"); 48 if(S->prefix("cs: dialup", syserr)) 49 err = syserr[4:]; 50 else if(S->prefix("cs: dns: no translation found", syserr)) 51 err = "unknown host"; 52 else 53 err = sys->sprint("couldn't connect: %s", syserr); 54 } 55 else { 56 if(dbg) 57 sys->print("ftp %d: connected\n", nc.id); 58 ctlfd = nc.conn.dfd; 59 # use cfd to hold control connection so can use dfd to hold data connection 60 nc.conn.cfd = ctlfd; 61 nc.conn.dfd = nil; 62 63 # look for Hello 64 (code, msg) := getreply(nc, ctlfd); 65 if(code != Success) 66 err = "instead of hello: " + msg; 67 else { 68 # logon 69 err = sendrequest(nc, ctlfd, "USER anonymous"); 70 if(err == "") { 71 (code, msg) = getreply(nc, ctlfd); 72 if(code == Incomplete) { 73 # need password 74 err = sendrequest(nc, ctlfd, "PASS webget@webget.com"); 75 if(err == "") 76 (code, msg) = getreply(nc, ctlfd); 77 } 78 if(err == "") { 79 if(code != Success) 80 err = "login failed: " + msg; 81 82 # image type 83 err = sendrequest(nc, ctlfd, "TYPE I"); 84 if(err == "") { 85 (code, msg) = getreply(nc, ctlfd); 86 if(code != Success) 87 err = "can't set type I: " + msg; 88 } 89 } 90 } 91 } 92 } 93 if(err == "") { 94 nc.connected = 1; 95 nc.state = CU->NCgethdr; 96 } 97 else { 98 if(dbg) 99 sys->print("ftp %d: connection failed: %s\n", nc.id, err); 100 bs.err = err; 101 closeconn(nc); 102 } 103} 104 105# Ask ftp server on ctlfd for passive port and dial it 106dialdata(nc: ref Netconn, ctlfd: ref sys->FD) : string 107{ 108 # put in passive mode 109 sendrequest(nc, ctlfd, "PASV"); 110 (code, msg) := getreply(nc, ctlfd); 111 if(code != Success) 112 return "can't use passive mode: " + msg; 113 (paddr, pport) := passvap(msg); 114 if(paddr == "") 115 return "passive mode protocol botch: " + msg; 116 # dial data port 117 daddr := DI->netmkaddr(paddr, "net", pport); 118 if(dbg) 119 sys->print("ftp %d: dialing data %s", nc.id, daddr); 120 dnet := DI->dial(daddr, nil); 121 if(dnet == nil) 122 return "data dial error"; 123 nc.conn.dfd = dnet.dfd; 124 return ""; 125} 126 127writereq(nc: ref Netconn, bs: ref ByteSource) 128{ 129 ctlfd := nc.conn.cfd; 130 CU->assert(ctlfd != nil); 131 err := dialdata(nc, ctlfd); 132 if(err == "") { 133 # tell remote to send file 134 err = sendrequest(nc, ctlfd, "RETR " + bs.req.url.path); 135 } 136 if(err != "") { 137 if(dbg) 138 sys->print("ftp %d: error: %s\n", nc.id, err); 139 bs.err = err; 140 closeconn(nc); 141 } 142} 143 144gethdr(nc: ref Netconn, bs: ref ByteSource) 145{ 146 hdr := Header.new(); 147 bs.hdr = hdr; 148 err := ""; 149 ctlfd := nc.conn.cfd; 150 dfd := nc.conn.dfd; 151 CU->assert(ctlfd != nil && dfd != nil); 152 (code, msg) := getreply(nc, ctlfd); 153 if(code != Extra) { 154 if(dbg) 155 sys->print("ftp %d: retrieve failed: %s\n", 156 nc.id, msg); 157 hdr.code = CU->HCNotFound; 158 hdr.msg = "Not found"; 159 } 160 else { 161 hdr.code = CU->HCOk; 162 163 # try to guess media type before returning header 164 buf := array[sys->ATOMICIO] of byte; 165 n := sys->read(dfd, buf, len buf); 166 if(dbg) 167 sys->print("ftp %d: read %d bytes\n", nc.id, n); 168 if(n < 0) 169 err = "error reading data"; 170 else { 171 if(n > 0) 172 nc.tbuf = buf[0:n]; 173 else 174 nc.tbuf = nil; 175 hdr.setmediatype(bs.req.url.path, nc.tbuf); 176 hdr.actual = bs.req.url; 177 hdr.base = hdr.actual; 178 hdr.length = -1; 179 hdr.msg = "Ok"; 180 } 181 } 182 if(err != "") { 183 if(dbg) 184 sys->print("ftp %d: error %s\n", nc.id, err); 185 bs.err = err; 186 closeconn(nc); 187 } 188} 189 190getdata(nc: ref Netconn, bs: ref ByteSource): int 191{ 192 dfd := nc.conn.dfd; 193 CU->assert(dfd != nil); 194 if (bs.data == nil || bs.edata >= len bs.data) { 195 closeconn(nc); 196 return 0; 197 } 198 buf := bs.data[bs.edata:]; 199 n := len buf; 200 if (nc.tbuf != nil) { 201 # initial overread of header 202 if (n >= len nc.tbuf) { 203 n = len nc.tbuf; 204 buf[:] = nc.tbuf; 205 nc.tbuf = nil; 206 return n; 207 } 208 buf[:] = nc.tbuf[:n]; 209 nc.tbuf = nc.tbuf[n:]; 210 return n; 211 } 212 n = sys->read(dfd, buf, n); 213 if(dbg > 1) 214 sys->print("ftp %d: read %d bytes\n", nc.id, n); 215 if(n <= 0) { 216 bs.err = "eof"; 217 closeconn(nc); 218 } 219 return n; 220} 221 222# Send ftp request cmd along fd; return "" if OK else error string. 223sendrequest(nc: ref Netconn, fd: ref sys->FD, cmd: string) : string 224{ 225 if(dbg > 1) 226 sys->print("ftp %d: send request: %s\n", nc.id, cmd); 227 cmd = cmd + "\r\n"; 228 buf := array of byte cmd; 229 n := len buf; 230 if(sys->write(fd, buf, n) != n) 231 return sys->sprint("write error: %r"); 232 return ""; 233} 234 235# Get reply to ftp request along fd. 236# Reply may be more than one line ("commentary") 237# but ends with a line that has a status code in the first 238# three characters (a number between 100 and 600) 239# followed by a blank and a possible message. 240# If OK, return the hundreds digit of the status (which will 241# mean one of Extra, Success, etc.), and the whole 242# last line; else return (-1, ""). 243getreply(nc: ref Netconn, fd: ref sys->FD) : (int, string) 244{ 245 # Reply might contain more than one line, 246 # because there might be "commentary" lines. 247 i := 0; 248 j := 0; 249 aline: array of byte; 250 eof := 0; 251 for(;;) { 252 (aline, eof, i, j) = CU->getline(fd, cmdbuf, i, j); 253 if(eof) 254 break; 255 line := string aline; 256 n := len line; 257 if(n == 0) 258 break; 259 if(dbg > 1) 260 sys->print("ftp %d: got reply: %s\n", nc.id, line); 261 rv := int line; 262 if(rv >= 100 && rv < 600) { 263 # if line is like '123-stuff' 264 # then there will be more lines until 265 # '123 stuff' 266 if(len line<4 || line[3]==' ') 267 return (rv/100, line); 268 } 269 } 270 return (-1, ""); 271} 272 273# Parse reply to PASSV to find address and port numbers. 274# This is AI because extant agents aren't good at following 275# the standard. 276passvap(s: string) : (string, string) 277{ 278 addr := ""; 279 port := ""; 280 (nil, v) := S->splitl(s, "("); 281 if(v != "") 282 s = v[1:]; 283 else 284 (nil, s) = S->splitl(s, "0123456789"); 285 if(s != "") { 286 (n, l) := sys->tokenize(s, ","); 287 if(n >= 6) { 288 addr = hd l + "."; 289 l = tl l; 290 addr += hd l + "."; 291 l = tl l; 292 addr += hd l + "."; 293 l = tl l; 294 addr += hd l; 295 l = tl l; 296 p1 := int hd l; 297 p2 := int hd tl l; 298 port = string (((p1&255)<<8)|(p2&255)); 299 } 300 } 301 return (addr, port); 302} 303 304defaultport(nil: string) : int 305{ 306 return FTPPORT; 307} 308 309closeconn(nc: ref Netconn) 310{ 311 nc.conn = nil; 312 nc.connected = 0; 313} 314