Plan 9 from Bell Labs’s /usr/web/sources/extra/i/ftp.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


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


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.