#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;
}
|