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

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


#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <panel.h>
#include <bio.h>
#include <ndb.h>
#include <ctype.h>
#include <ip.h>
#include "mothra.h"

enum
{
	/* return codes */
	Extra=		1,
	Success=	2,
	Incomplete=	3,
	TempFail=	4,
	PermFail=	5,

	NAMELEN=	28,
	Nnetdir=	3*NAMELEN,	/* max length of network directory paths */
	Ndialstr=	64,		/* max length of dial strings */
};

typedef struct Ftp Ftp;
struct Ftp
{
	char	net[Nnetdir];
	Biobuf	*ftpctl;
	Url	*u;
};

static int ftpdebug;


/*
 *  read from biobuf turning cr/nl into nl
 */
char*
getcrnl(Biobuf *b)
{
	char *p, *ep;

	p = Brdline(b, '\n');
	if(p == nil)
		return nil;
	ep = p + Blinelen(b) - 1;
	if(*(ep-1) == '\r')
		ep--;
	*ep = 0;
	return p;
}

char*
readfile(char *file, char *buf, int len)
{
	int n, fd;

	fd = open(file, OREAD);
	if(fd < 0)
		return nil;
	n = read(fd, buf, len-1);
	close(fd);
	if(n <= 0)
		return nil;
	buf[n] = 0;
	return buf;
}

char*
sysname(void)
{
	static char sys[Ndbvlen];
	char *p;

	p = readfile("/dev/sysname", sys, sizeof(sys));
	if(p == nil)
		return "unknown";
	return p;
}

char*
domainname(void)
{
	static char domain[Ndbvlen];
	Ndbtuple *t;

	if(*domain)
		return domain;

	t = csgetval(0, "sys", sysname(), "dom", domain);
	if(t){
		ndbfree(t);
		return domain;
	} else
		return sysname();
}

static int
sendrequest(Biobuf *b, char *fmt, ...)
{
	char buf[2*1024], *s;
	va_list args;

	va_start(args, fmt);
	s = buf + vsnprint(buf, (sizeof(buf)-4) / sizeof(*buf), fmt, args);
	va_end(args);
	*s++ = '\r';
	*s++ = '\n';
	if(write(Bfildes(b), buf, s - buf) != s - buf)
		return -1;
	if(ftpdebug)
		write(2, buf, s - buf);
	return 0;
}

static int
getreply(Biobuf *b, char *msg, int len)
{
	char *line;
	int rv;
	int i, n;

	while(line = getcrnl(b)){
		/* add line to message buffer, strip off \r */
		n = Blinelen(b);
		if(ftpdebug)
			write(2, line, n);
		if(n > len - 1)
			i = len - 1;
		else
			i = n;
		if(i > 0){
			memmove(msg, line, i);
			msg += i;
			len -= i;
			*msg = 0;
		}

		/* stop if not a continuation */
		rv = atoi(line);
		if(rv >= 100 && rv < 600 && (n == 4 || (n > 4 && line[3] == ' ')))
			return rv/100;
	}

	return -1;
}

int
terminateftp(Ftp *d)
{
	if(d->ftpctl){
		close(Bfildes(d->ftpctl));
		Bterm(d->ftpctl);
		free(d->ftpctl);
		d->ftpctl = nil;
	}
	free(d);
	return -1;
}

Biobuf*
hello(Ftp *d)
{
	int fd;
	char *p;
	Biobuf *b;
	char msg[1024];
	char ndir[Nnetdir];

	snprint(msg, sizeof msg, "tcp!%s!%d", d->u->ipaddr, d->u->port);
	fd = dial(msg, 0, ndir, 0);
	if(fd < 0){
		d->ftpctl = nil;
		return nil;
	}
	b = emalloc(sizeof(Biobuf));
	Binit(b, fd, OREAD);
	d->ftpctl = b;

	/* remember network for the data connections */
	p = strrchr(ndir, '/');
	if(p == 0){
		fprint(2, "dial is out of date\n");
		return nil;
	}
	*p = 0;
	strcpy(d->net, ndir);

	/* wait for hello from other side */
	if(getreply(b, msg, sizeof(msg)) != Success){
		fprint(2, "instead of hello: %s\n", msg);
		return nil;
	}
	return b;
}

int
logon(Ftp *d)
{
	char msg[1024];

	/* login anonymous */
	sendrequest(d->ftpctl, "USER anonymous");
	switch(getreply(d->ftpctl, msg, sizeof(msg))){
	case Success:
		return 0;
	case Incomplete:
		break;	/* need password */
	default:
		fprint(2, "login failed: %s\n", msg);
		werrstr(msg);
		return -1;
	}

	/* send user id as password */
	sprint(msg, "%s@", getuser());
	sendrequest(d->ftpctl, "PASS %s", msg);
	if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
		fprint(2, "login failed: %s\n", msg);
		werrstr(msg);
		return -1;
	}

	return 0;
}

int
xfertype(Ftp *d, char *t)
{
	char msg[1024];

	sendrequest(d->ftpctl, "TYPE %s", t);
	if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
		fprint(2, "can't set type %s: %s\n", t, msg);
		werrstr(msg);
		return -1;
	}
	return 0;
}

int
passive(Ftp *d)
{
	char msg[1024];
	char dialstr[Ndialstr];
	char *f[6];
	char *p;
	int fd;

	sendrequest(d->ftpctl, "PASV");
	if(getreply(d->ftpctl, msg, sizeof(msg)) != Success)
		return -1;

	/* get address and port number from reply, this is AI */
	p = strchr(msg, '(');
	if(p == nil){
		for(p = msg+3; *p; p++)
			if(isdigit(*p))
				break;
	} else
		p++;
	if(getfields(p, f, 6, 0, ",") < 6){
		fprint(2, "passive mode protocol botch: %s\n", msg);
		werrstr("ftp protocol botch");
		return -1;
	}
	snprint(dialstr, sizeof(dialstr), "%s!%s.%s.%s.%s!%d", d->net,
		f[0], f[1], f[2], f[3],
		((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff));


	/* open data connection */
	fd = dial(dialstr, 0, 0, 0);
	if(fd < 0){
		fprint(2, "passive mode connect to %s failed: %r\n", dialstr);
		return -1;
	}

	/* tell remote to send a file */
	sendrequest(d->ftpctl, "RETR %s", d->u->reltext);
	if(getreply(d->ftpctl, msg, sizeof(msg)) != Extra){
		fprint(2, "passive mode retrieve failed: %s\n", msg);
		werrstr(msg);
		return -1;
	}
	return fd;
}

int
active(Ftp *d)
{
	char msg[1024];
	char buf[Ndialstr];
	char netdir[Nnetdir];
	char newdir[Nnetdir];
	uchar ipaddr[4];
	int dfd, cfd, listenfd;
	char *p;
	int port;

	/* get a channel to listen on, let kernel pick the port number */
	sprint(buf, "%s!*!0", d->net);
	listenfd = announce(buf, netdir);
	if(listenfd < 0){
		fprint(2, "can't listen for ftp callback: %r\n", buf);
		return -1;
	}

	/* get the local address and port number */
	sprint(newdir, "%s/local", netdir);
	readfile(newdir, buf, sizeof buf);
	p = strchr(buf, '!')+1;
	parseip(ipaddr, buf);
	port = atoi(p);

	/* tell remote side address and port*/
	sendrequest(d->ftpctl, "PORT %d,%d,%d,%d,%d,%d", ipaddr[0], ipaddr[1], ipaddr[2],
		ipaddr[3], port>>8, port&0xff);
	if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
		close(listenfd);
		werrstr("ftp protocol botch");
		fprint(2, "active mode connect failed %s\n", msg);
		return -1;
	}

	/* tell remote to send a file */
	sendrequest(d->ftpctl, "RETR %s", d->u->reltext);
	if(getreply(d->ftpctl, msg, sizeof(msg)) != Extra){
		close(listenfd);
		fprint(2, "active mode connect failed: %s\n", msg);
		werrstr(msg);
		return -1;
	}

	/* wait for a new call */
	cfd = listen(netdir, newdir);
	close(listenfd);
	if(cfd < 0){
		fprint(2, "active mode connect failed: %r\n");
		return -1;
	}

	/* open the data connection and close the control connection */
	dfd = accept(cfd, newdir);
	close(cfd);
	if(dfd < 0){
		fprint(2, "active mode connect failed: %r\n");
		werrstr("ftp protocol botch");
		return -1;
	}

	return dfd;
}

/*
 * Given a url, return a file descriptor on which caller can
 * read an ftp document.
 * The caller is responsible for processing redirection loops.
 */
int
ftp(Url *url)
{
	int n;
	int data;
	Ftp *d;
	int pfd[2];
	char buf[2048];

	if(url->type == 0)
		url->type = PLAIN;

	d = (Ftp*)emalloc(sizeof(Ftp));
	d->u = url;
	d->ftpctl = nil;

	if(hello(d) == nil)
		return terminateftp(d);
	if(logon(d) < 0)
		return terminateftp(d);

	switch(url->type){
	case PLAIN:
	case HTML:
		if(xfertype(d, "A") < 0)
			return terminateftp(d);
		break;
	default:
		if(xfertype(d, "I") < 0)
			return terminateftp(d);
		break;
	}

	/* first try passive mode, then active */
	data = passive(d);
	if(data < 0){
		if(d->ftpctl == nil)
			return -1;
		data = active(d);
		if(data < 0)
			return -1;
	}

	if(pipe(pfd) < 0)
		return -1;

	switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
	case -1:
		werrstr("Can't fork");
		close(pfd[0]);
		close(pfd[1]);
		return terminateftp(d);
	case 0:
		close(pfd[0]);
		while((n=read(data, buf, sizeof(buf)))>0)
			write(pfd[1], buf, n);
		if(n<0)
			fprint(2, "ftp: %s: %r\n", url->fullname);
		_exits(0);
	default:
		close(pfd[1]);
		close(data);
		terminateftp(d);
		return pfd[0];
	}
	return -1;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].