#include "i.h" enum { FTPPORT = 21 }; // Return codes enum { Extra = 1, Success = 2, Incomplete = 3, TempFail = 4, PermFail = 5 }; int dbgftp = 0; static int dialdata(Netconn* nc, int ctlfd); static int sendrequest(Netconn* nc, int fd, char* cmd); static int getreply(Netconn* nc, int fd, Rune** pmsg); static Rune* passvap(Rune* s, int* pport); static void closeconn(Netconn* nc); void ftpinit(void) { dbgftp = (config).dbg['n']; } void ftpconnect(Netconn* nc, ByteSource* bs) { int port; int err; int ctlfd; int code; Rune* msg; char dir[SMALLBUFSIZE]; char addr[BIGBUFSIZE]; port = nc->port; snprint(addr, sizeof(addr), "tcp!%S!%d", nc->host, port); if(dbgftp) trace("ftp %d: dialing %s\n", nc->id, addr); nc->dfd = -1; nc->cfd = dial(addr, nil, dir, nil); if(nc->cfd < 0) err = ERRconnecterr; else { if(dbgftp) trace("ftp %d: connected\n", nc->id); ctlfd = nc->cfd; code = getreply(nc, ctlfd, &msg); if(code != Success) err = ERRftperr; else { err = sendrequest(nc, ctlfd, "USER anonymous"); if(!err) { code = getreply(nc, ctlfd, &msg); if(code == Incomplete) { err = sendrequest(nc, ctlfd, "PASS webget@webget.com"); if(!err) code = getreply(nc, ctlfd, &msg); } if(!err) { if(code != Success) err = ERRftpnologin; else { err = sendrequest(nc, ctlfd, "TYPE I"); if(!err) { code = getreply(nc, ctlfd, &msg); if(code != Success) err = ERRftperr; } } } } } } if(!err) { nc->connected = 1; nc->state = NCgethdr; } else { if(dbgftp) trace("ftp %d: connection failed: %S\n", nc->id, errphrase(err)); bs->err = err; closeconn(nc); } } void ftpwritereq(Netconn* nc, ByteSource* bs) { int ctlfd; int err; ParsedUrl* u; char reqbuf[BIGBUFSIZE]; ctlfd = nc->cfd; assert(ctlfd != -1); err = dialdata(nc, ctlfd); if(!err) { u = bs->req->url; if(u->npstart == 1) snprint(reqbuf, sizeof(reqbuf), "RETR /%S", Strndup(u->path, u->npath)); else snprint(reqbuf, sizeof(reqbuf), "RETR %S", Strndup(u->path, u->npath)); err = sendrequest(nc, ctlfd, reqbuf); } if(err) { if(dbgftp) trace("ftp %d: error: %S\n", nc->id, errphrase(err)); bs->err = err; closeconn(nc); } } void ftpgethdr(Netconn* nc, ByteSource* bs) { Header* hdr; int err; int ctlfd; int dfd; int code; Rune* msg; int n; uchar* buf; hdr = newheader(); bs->hdr = hdr; err = 0; ctlfd = nc->cfd; dfd = nc->dfd; assert(ctlfd != -1 && dfd != -1); code = getreply(nc, ctlfd, &msg); if(code != Extra) { if(dbgftp) trace("ftp %d: retrieve failed: %S\n", nc->id, msg); hdr->code = HCNotFound; hdr->msg = L"Not found"; } else { hdr->code = HCOk; // try to guess media type before returning header buf = (uchar*)emalloc(ATOMICIO); n = read(dfd, buf, sizeof(buf)); if(dbgftp) trace("ftp %d: read %d bytes\n", nc->id, n); if(n < 0) err = ERRreaderr; else { if(n > 0) { nc->tbuf = buf; nc->tbuflen = ATOMICIO; nc->tn1 = n; } else { nc->tbuf = nil; nc->tbuflen = 0; nc->tn1 = 0; } setmediatype(hdr, bs->req->url->path, nc->tbuf, nc->tn1); hdr->actual = copyurl(bs->req->url); hdr->base = hdr->actual; hdr->length = -1; hdr->msg = L"Ok"; } } if(err != 0) { if(dbgftp) trace("ftp %d: error %S\n", nc->id, errphrase(err)); bs->err = err; closeconn(nc); } } void ftpgetdata(Netconn* nc, ByteSource* bs) { int dfd; uchar* buf; int n; dfd = nc->dfd; assert(dfd != -1); buf = bs->data; n = 0; if(nc->tbuf != nil) { n = nc->tn1; assert(bs->dalloclen >= n); memmove(buf, nc->tbuf, n); nc->tbuf = nil; nc->tbuflen = 0; } if(n == 0) n = read(dfd, buf+bs->edata, bs->dalloclen - bs->edata); if(dbgftp > 1) trace("ftp %d: read %d bytes\n", nc->id, n); if(n <= 0) { closeconn(nc); bs->err = ERReof; } else { bs->edata += n; if(bs->edata == bs->dalloclen) closeconn(nc); } if(bs->err != 0) { if(dbgftp) trace("ftp %d: error %S\n", nc->id, errphrase(bs->err)); closeconn(nc); } } int ftpdefaultport(int scheme) { USED(scheme); return FTPPORT; } // Ask ftp server on ctlfd for passive port and dial it. // Return error code, if any. static int dialdata(Netconn* nc, int ctlfd) { int err; int code; Rune* msg; Rune* paddr; int pport; char dir[SMALLBUFSIZE]; char daddr[BIGBUFSIZE]; err = sendrequest(nc, ctlfd, "PASV"); msg = nil; if(!err) { code = getreply(nc, ctlfd, &msg); if(code != Success) err = ERRftperr; else { paddr = passvap(msg, &pport); if(paddr == nil) err = ERRconnecterr; else { snprint(daddr, sizeof(daddr), "tcp!%S!%d", paddr, pport); if(dbgftp) trace("ftp %d: dialing data %s", nc->id, daddr); nc->dfd = dial(daddr, nil, dir, nil); if(nc->dfd < 0) err = ERRftperr; } } } return err; } static int sendrequest(Netconn* nc, int fd, char* cmd) { char buf[SMALLBUFSIZE]; int n; if(dbgftp > 1) trace("ftp %d: send request: %s\n", nc->id, cmd); n = snprint(buf, sizeof(buf), "%s\r\n", cmd); if(write(fd, buf, n) != n) return ERRwriteerr; return 0; } // 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 in *pmsg; else -1 and put nil in *pmsg. static int getreply(Netconn* nc, int fd, Rune** pmsg) { int i; int j; uchar* aline; int alinelen; int eof; Rune* line; int n; int rv; uchar cmdbuf[SMALLBUFSIZE]; i = 0; j = 0; *pmsg = nil; while(1) { eof = getline(fd, cmdbuf, sizeof(cmdbuf), &i, &j, &aline, &alinelen); if(eof) break; line = toStr(aline, alinelen, UTF_8); n = Strlen(line); if(n == 0) break; if(dbgftp > 1) trace("ftp %d: got reply: %S\n", nc->id, line); rv = Strtol(line, nil, 10); if(rv >= 100 && rv < 600) { // if line is like '123-stuff' // then there will be more lines until // '123 stuff' if(n < 4 || line[3] == ' ') { *pmsg = line; return rv/100; } } } 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. // Return address and put port number in *pport. static Rune* passvap(Rune* s, int* pport) { Rune* addr; int addrlen; int port; Rune* v; Rune* x[6]; int xn[6]; int p1; int p2; int n; addr = nil; port = 0; *pport = 0; v = Strclass(s, L"("); if(v != nil) s = v+1; else s = Strclass(s, L"0123456789"); if(s != nil && *s != 0) { n = splitall(s, Strlen(s), L",", x, xn, 6); if(n >= 6) { addrlen = xn[0] + xn[1] + xn[2] + xn[3] + 3; addr = newstr(addrlen); v = Stradd(addr, x[0], xn[0]); *v++ = '.'; v = Stradd(v, x[1], xn[1]); *v++ = '.'; v = Stradd(v, x[2], xn[2]); *v++ = '.'; v = Stradd(v, x[3], xn[3]); *v = 0; p1 = Strtol(x[4], nil, 10); p2 = Strtol(x[5], nil, 10); port =((p1&255) << 8)|(p2&255); } } *pport = port; return addr; } static void closeconn(Netconn* nc) { if(nc->dfd >= 0) { close(nc->dfd); close(nc->cfd); } nc->dfd = -1; nc->cfd = -1; nc->connected = 0; }