1124 lines · 20.8 KB · C
RawDownload
#include <u.h>#include <libc.h>#include <bio.h>#include <ip.h>#include <libsec.h>#include <auth.h> #include <String.h>#include "glob.h" enum {	Tascii,	Timage, 	Maxpath = 512,	Maxwait = 1000 * 60 * 30, /* 30 minutes */}; typedef struct Passive Passive;typedef struct Ftpd Ftpd;typedef struct Cmd Cmd; struct Passive {	int inuse;	char adir[40];	int afd;	int port;	uchar ipaddr[IPaddrlen];}; struct Ftpd {	Biobuf *in, *out; 	struct conn {		int tlson, tlsondata;		NetConnInfo *nci;		TLSconn *tls;		uchar *cert;		int certlen;		char data[64];		Passive pasv;	} conn; 	struct user {		char cwd[Maxpath];		char name[Maxpath];		int loggedin;		int isnone;	} user; 	int type;	vlong offset;	int cmdpid;	char *renamefrom;}; struct Cmd {	char *name;	int (*fn)(Ftpd *, char *);	int needlogin;	int needtls;	int asproc;}; char *certpath;char *namespace = "/lib/namespace.ftp";int implicittls;int debug;int anonok;int anononly;int anonall; void dprint(char *fmt, ...){	char *msg;	va_list arg; 	if(!debug) return; 	va_start(arg, fmt);	msg = vsmprint(fmt, arg);	va_end(arg); 	syslog(0, "ftp", msg);	free(msg);} void logit(char *fmt, ...){	char *msg;	va_list arg; 	va_start(arg, fmt);	msg = vsmprint(fmt, arg);	va_end(arg); 	syslog(0, "ftp", msg);	free(msg);} int reply(Biobuf *bio, char *fmt, ...){	va_list arg;	char buf[Maxpath], *s; 	va_start(arg, fmt);	s = vseprint(buf, buf + sizeof(buf) - 3, fmt, arg);	va_end(arg); 	dprint("rpl: %s", buf); 	*s++ = '\r';	*s++ = '\n';	Bwrite(bio, buf, s - buf);	Bflush(bio); 	return 0;} voidasproc(Ftpd *ftpd, int (*f)(Ftpd *, char *), char *arg){	int i; 	if(ftpd->cmdpid) {		for(;;) {			i = waitpid();			if(i == ftpd->cmdpid || i < 0)				break;		}	} 	switch(ftpd->cmdpid = rfork(RFFDG|RFPROC|RFNOTEG)){	case -1:		reply(ftpd->out, "450 Out of processes: %r");		return;	case 0:		(*f)(ftpd, arg);		dprint("proc exiting");		exits(nil);	default:		break;	}} int mountnet(Ftpd *ftpd){	if(bind("#/", "/", MAFTER) == -1) {		reply(ftpd->out, "500 can't bind #/ to /: %r");		return -1;	} 	if(bind(ftpd->conn.nci->spec, "/net", MBEFORE) == -1) {		reply(ftpd->out, "500 can't bind %s to /net: %r", ftpd->conn.nci->spec);		unmount("#/", "/");		return -1;	} 	return 0;} void unmountnet(void){	unmount(nil, "/net");	unmount("#/", "/");} Biobuf *dialdata(Ftpd *ftpd, int read){	Biobuf *bio;	TLSconn *tls;	int fd, cfd;	char ldir[40]; 	if(mountnet(ftpd) < 0)		return nil; 	if(!ftpd->conn.pasv.inuse) {		fd = dial(ftpd->conn.data, "20", 0, 0);	} else {		fd = -1;		alarm(30 * 1000); /* wait 30 seconds */		dprint("dbg: waiting for passive connection");		cfd = listen(ftpd->conn.pasv.adir, ldir);		alarm(0); 		if(cfd >= 0) {			fd = accept(cfd, ldir);			close(cfd);		}	} 	if(fd < 0) {		reply(ftpd->out, "425 Error opening data connection");		unmountnet();		return nil;	} 	reply(ftpd->out, "150 Opened data connection"); 	tls = nil;	if(ftpd->conn.tlsondata) {		dprint("dbg: using tls on data channel"); 		tls = mallocz(sizeof(TLSconn), 1);		tls->cert = malloc(ftpd->conn.certlen);		memcpy(tls->cert, ftpd->conn.cert, ftpd->conn.certlen);		tls->certlen = ftpd->conn.certlen;		fd = tlsServer(fd, tls); 		if(fd < 0) {			reply(ftpd->out, "425 TLS on data connection failed");			unmountnet();			return nil;		} 		dprint("dbg: tlsserver done");	} 	unmountnet();	if(read)		bio = Bfdopen(fd, OREAD);	else		bio = Bfdopen(fd, OWRITE);	bio->aux = tls; 	return bio;} void closedata(Ftpd *ftpd, Biobuf *bio, int fail){	TLSconn *conn; 	conn = bio->aux; 	Bflush(bio);	Bterm(bio);	if(!fail)		reply(ftpd->out, "226 Transfer complete"); 	if(conn) {		free(conn->cert);		free(conn);	}} int starttls(Ftpd *ftpd){	int fd; 	fd = tlsServer(0, ftpd->conn.tls);	if(fd < 0)		return -1; 	dup(fd, 0);	dup(fd, 1);	ftpd->conn.tlson = 1; 	return 0;} intabortcmd(Ftpd *ftpd, char *arg){	USED(arg); 	if(ftpd->cmdpid){		if(postnote(PNPROC, ftpd->cmdpid, "kill") == 0)			reply(ftpd->out, "426 Command aborted");		else			logit("postnote pid %d %r", ftpd->cmdpid);	}	return reply(ftpd->out, "226 Abort processed");} int authcmd(Ftpd *ftpd, char *arg){	if((cistrcmp(arg, "TLS") == 0) || (cistrcmp(arg, "TLS-C") == 0) || (cistrcmp(arg, "SSL") == 0)) { 		if(!ftpd->conn.tls)			return reply(ftpd->out, "431 tls not enabled"); 		reply(ftpd->out, "234 starting tls");		if(starttls(ftpd) < 0)			return reply(ftpd->out, "431 tls failed");	} else {		return reply(ftpd->out, "502 security method %s not understood", arg);	} 	return 0;} int cwdcmd(Ftpd *ftpd, char *arg){	char buf[Maxpath]; 	if(!arg || *arg == '\0') {		if(ftpd->user.isnone)			snprint(buf, Maxpath, "/");		else			snprint(buf, Maxpath, "/usr/%s", ftpd->user.name);	} else {		strncpy(buf, arg, Maxpath);		cleanname(buf);	} 	if(chdir(buf) < 0)		return reply(ftpd->out, "550 CWD failed: %r"); 	getwd(ftpd->user.cwd, Maxpath);	return reply(ftpd->out, "200 Directory changed to %s", ftpd->user.cwd);} int deletecmd(Ftpd *ftpd, char *arg){	if(!arg)		return reply(ftpd->out, "501 Rmdir/Delete command needs an argument");	if(ftpd->user.isnone)		return reply(ftpd->out, "550 Permission denied");	if(remove(cleanname(arg)) < 0)		return reply(ftpd->out, "550 Can't remove %s: %r", arg);	else		return reply(ftpd->out, "226 \"%s\" removed", arg);} int featcmd(Ftpd *ftpd, char *arg){	USED(arg);	reply(ftpd->out, "211-Features supported");	reply(ftpd->out, " UTF8");	reply(ftpd->out, " PBSZ");	reply(ftpd->out, " PROT");	reply(ftpd->out, " AUTH TLS");	reply(ftpd->out, " MLST Type*;Size*;Modify*;Unix.groupname*;UNIX.ownername*;");	return reply(ftpd->out, "211 End");} int dircmp(void *va, void *vb){	Dir *a, *b; 	a = va;	b = vb; 	return strcmp(a->name, b->name);} voidlistdir(Ftpd *ftpd, Biobuf *data, char *path, void (*fn)(Biobuf *, Dir *d, char *dirname)){	Dir *dirbuf;	int fd;	long ndirs;	long i; 	fd = open(path, OREAD);	if(!fd)		return; 	ndirs = dirreadall(fd, &dirbuf);	if(ndirs < 1)		return;	close(fd); 	qsort(dirbuf, ndirs, sizeof(Dir), dircmp);	for(i=0;i<ndirs;i++)		(*fn)(data, &dirbuf[i], (strcmp(path, ftpd->user.cwd) == 0 ? nil : path)); 	free(dirbuf);} intlist(Ftpd *ftpd, char *arg, void (*fn)(Biobuf *, Dir *d, char *dirname)){	Biobuf *data;	int argc, i;	char *argv[32];	Globlist *gl;	char *path;	Dir *d; 	if(arg) {		argc = getfields(arg, argv, sizeof(argv)-1, 1, " \t");	} else {		argc = 1;		argv[0] = ftpd->user.cwd;	} 	data = dialdata(ftpd, 0);	if(!data)		return reply(ftpd->out, "500 List failed: couldn't dial data"); 	for(i=0;i<argc;i++) {		gl = glob(argv[i]);		if(!gl)			continue; 		while(path = globiter(gl)) {			cleanname(path); 			logit("list: path %s user %s", path, ftpd->user.name); 			d = dirstat(path);			if(d->mode & DMDIR)				listdir(ftpd, data, path, fn);			else				(*fn)(data, d, nil); 			free(d);		}	} 	closedata(ftpd, data, 0); 	return 0;} char *mode2asc(int m){	char *asc;	char *p; 	asc = strdup("----------");	if(DMDIR & m)		asc[0] = 'd';	if(DMAPPEND & m)		asc[0] = 'a';	else if(DMEXCL & m)		asc[3] = 'l'; 	for(p = asc + 1; p < asc + 10; p += 3, m <<= 3) {		if(m & 0400)			p[0] = 'r';		if(m & 0200)			p[1] = 'w';		if(m & 0100)			p[2] = 'x';	} 	return asc;} void listprint(Biobuf *data, Dir *d, char *dirname){	char *ts, *mode; 	ts = strdup(ctime(d->mtime));	ts[16] = '\0';	if(time(0) - d->mtime > 6 * 30 * 24 * 60 * 60)		memmove(ts + 11, ts + 23, 5); 	mode = mode2asc(d->mode); 	if(dirname)		reply(data, "%s %3d %-8s %-8s %7lld %s %s/%s", 			mode, 1, d->uid, d->gid, d->length, ts + 4, dirname, d->name);	else		reply(data, "%s %3d %-8s %-8s %7lld %s %s",			mode, 1, d->uid, d->gid, d->length, ts + 4, d->name); 	free(mode);	free(ts);} int listcmd(Ftpd *ftpd, char *arg){	return list(ftpd, arg, listprint);} int loginuser(Ftpd *ftpd, char *pass, char *nsfile){	char *user; 	user = ftpd->user.name; 	putenv("service", "ftp");	if(!ftpd->user.isnone) {		if(login(user, pass, nsfile) < 0)			return reply(ftpd->out, "530 Not logged in: bad password");	} else {		if(newns(user, nsfile) < 0)			return reply(ftpd->out, "530 Not logged in: user out of service");	}

Lines 1–500 of 1124.