ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/charon/ftp.b/
implement Transport;
include "common.m";
include "transport.m";
# local copies from CU
sys: Sys;
U: Url;
Parsedurl: import U;
S: String;
DI: Dial;
CU: CharonUtils;
Netconn, ByteSource, Header, config: import CU;
FTPPORT: con 21;
# Return codes
Extra, Success, Incomplete, TempFail, PermFail : con (1+iota);
cmdbuf := array[200] of byte;
dbg := 0;
init(c: CharonUtils)
{
CU = c;
sys = load Sys Sys->PATH;
S = load String String->PATH;
U = load Url Url->PATH;
if (U != nil)
U->init();
DI = CU->DI;
dbg = int (CU->config).dbg['n'];
}
connect(nc: ref Netconn, bs: ref ByteSource)
{
port := nc.port;
if(port == 0)
port = FTPPORT;
addr := DI->netmkaddr(nc.host, "net", string port);
if(dbg)
sys->print("ftp %d: dialing %s\n", nc.id, addr);
err := "";
ctlfd : ref sys->FD = nil;
nc.conn = DI->dial(addr, nil);
if(nc.conn == nil) {
syserr := sys->sprint("%r");
if(S->prefix("cs: dialup", syserr))
err = syserr[4:];
else if(S->prefix("cs: dns: no translation found", syserr))
err = "unknown host";
else
err = sys->sprint("couldn't connect: %s", syserr);
}
else {
if(dbg)
sys->print("ftp %d: connected\n", nc.id);
ctlfd = nc.conn.dfd;
# use cfd to hold control connection so can use dfd to hold data connection
nc.conn.cfd = ctlfd;
nc.conn.dfd = nil;
# look for Hello
(code, msg) := getreply(nc, ctlfd);
if(code != Success)
err = "instead of hello: " + msg;
else {
# logon
err = sendrequest(nc, ctlfd, "USER anonymous");
if(err == "") {
(code, msg) = getreply(nc, ctlfd);
if(code == Incomplete) {
# need password
err = sendrequest(nc, ctlfd, "PASS webget@webget.com");
if(err == "")
(code, msg) = getreply(nc, ctlfd);
}
if(err == "") {
if(code != Success)
err = "login failed: " + msg;
# image type
err = sendrequest(nc, ctlfd, "TYPE I");
if(err == "") {
(code, msg) = getreply(nc, ctlfd);
if(code != Success)
err = "can't set type I: " + msg;
}
}
}
}
}
if(err == "") {
nc.connected = 1;
nc.state = CU->NCgethdr;
}
else {
if(dbg)
sys->print("ftp %d: connection failed: %s\n", nc.id, err);
bs.err = err;
closeconn(nc);
}
}
# Ask ftp server on ctlfd for passive port and dial it
dialdata(nc: ref Netconn, ctlfd: ref sys->FD) : string
{
# put in passive mode
sendrequest(nc, ctlfd, "PASV");
(code, msg) := getreply(nc, ctlfd);
if(code != Success)
return "can't use passive mode: " + msg;
(paddr, pport) := passvap(msg);
if(paddr == "")
return "passive mode protocol botch: " + msg;
# dial data port
daddr := DI->netmkaddr(paddr, "net", pport);
if(dbg)
sys->print("ftp %d: dialing data %s", nc.id, daddr);
dnet := DI->dial(daddr, nil);
if(dnet == nil)
return "data dial error";
nc.conn.dfd = dnet.dfd;
return "";
}
writereq(nc: ref Netconn, bs: ref ByteSource)
{
ctlfd := nc.conn.cfd;
CU->assert(ctlfd != nil);
err := dialdata(nc, ctlfd);
if(err == "") {
# tell remote to send file
err = sendrequest(nc, ctlfd, "RETR " + bs.req.url.path);
}
if(err != "") {
if(dbg)
sys->print("ftp %d: error: %s\n", nc.id, err);
bs.err = err;
closeconn(nc);
}
}
gethdr(nc: ref Netconn, bs: ref ByteSource)
{
hdr := Header.new();
bs.hdr = hdr;
err := "";
ctlfd := nc.conn.cfd;
dfd := nc.conn.dfd;
CU->assert(ctlfd != nil && dfd != nil);
(code, msg) := getreply(nc, ctlfd);
if(code != Extra) {
if(dbg)
sys->print("ftp %d: retrieve failed: %s\n",
nc.id, msg);
hdr.code = CU->HCNotFound;
hdr.msg = "Not found";
}
else {
hdr.code = CU->HCOk;
# try to guess media type before returning header
buf := array[sys->ATOMICIO] of byte;
n := sys->read(dfd, buf, len buf);
if(dbg)
sys->print("ftp %d: read %d bytes\n", nc.id, n);
if(n < 0)
err = "error reading data";
else {
if(n > 0)
nc.tbuf = buf[0:n];
else
nc.tbuf = nil;
hdr.setmediatype(bs.req.url.path, nc.tbuf);
hdr.actual = bs.req.url;
hdr.base = hdr.actual;
hdr.length = -1;
hdr.msg = "Ok";
}
}
if(err != "") {
if(dbg)
sys->print("ftp %d: error %s\n", nc.id, err);
bs.err = err;
closeconn(nc);
}
}
getdata(nc: ref Netconn, bs: ref ByteSource): int
{
dfd := nc.conn.dfd;
CU->assert(dfd != nil);
if (bs.data == nil || bs.edata >= len bs.data) {
closeconn(nc);
return 0;
}
buf := bs.data[bs.edata:];
n := len buf;
if (nc.tbuf != nil) {
# initial overread of header
if (n >= len nc.tbuf) {
n = len nc.tbuf;
buf[:] = nc.tbuf;
nc.tbuf = nil;
return n;
}
buf[:] = nc.tbuf[:n];
nc.tbuf = nc.tbuf[n:];
return n;
}
n = sys->read(dfd, buf, n);
if(dbg > 1)
sys->print("ftp %d: read %d bytes\n", nc.id, n);
if(n <= 0) {
bs.err = "eof";
closeconn(nc);
}
return n;
}
# Send ftp request cmd along fd; return "" if OK else error string.
sendrequest(nc: ref Netconn, fd: ref sys->FD, cmd: string) : string
{
if(dbg > 1)
sys->print("ftp %d: send request: %s\n", nc.id, cmd);
cmd = cmd + "\r\n";
buf := array of byte cmd;
n := len buf;
if(sys->write(fd, buf, n) != n)
return sys->sprint("write error: %r");
return "";
}
# Get reply to ftp request along fd.
# Reply may be more than one line ("commentary")
# but ends with a line that has a status code in the first
# three characters (a number between 100 and 600)
# followed by a blank and a possible message.
# If OK, return the hundreds digit of the status (which will
# mean one of Extra, Success, etc.), and the whole
# last line; else return (-1, "").
getreply(nc: ref Netconn, fd: ref sys->FD) : (int, string)
{
# Reply might contain more than one line,
# because there might be "commentary" lines.
i := 0;
j := 0;
aline: array of byte;
eof := 0;
for(;;) {
(aline, eof, i, j) = CU->getline(fd, cmdbuf, i, j);
if(eof)
break;
line := string aline;
n := len line;
if(n == 0)
break;
if(dbg > 1)
sys->print("ftp %d: got reply: %s\n", nc.id, line);
rv := int line;
if(rv >= 100 && rv < 600) {
# if line is like '123-stuff'
# then there will be more lines until
# '123 stuff'
if(len line<4 || line[3]==' ')
return (rv/100, line);
}
}
return (-1, "");
}
# Parse reply to PASSV to find address and port numbers.
# This is AI because extant agents aren't good at following
# the standard.
passvap(s: string) : (string, string)
{
addr := "";
port := "";
(nil, v) := S->splitl(s, "(");
if(v != "")
s = v[1:];
else
(nil, s) = S->splitl(s, "0123456789");
if(s != "") {
(n, l) := sys->tokenize(s, ",");
if(n >= 6) {
addr = hd l + ".";
l = tl l;
addr += hd l + ".";
l = tl l;
addr += hd l + ".";
l = tl l;
addr += hd l;
l = tl l;
p1 := int hd l;
p2 := int hd tl l;
port = string (((p1&255)<<8)|(p2&255));
}
}
return (addr, port);
}
defaultport(nil: string) : int
{
return FTPPORT;
}
closeconn(nc: ref Netconn)
{
nc.conn = nil;
nc.connected = 0;
}