/* Internet FTP Server server machine - see RFC 959
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Mods by KO4KS
 */
#include "global.h"
#include "ctype.h"
#include "commands.h"
#ifdef UNIX
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#include "mbuf.h"
#include "socket.h"
#include "ftp.h"
#include "ftpserv.h"
#include "dirutil.h"
#include "files.h"
#include "session.h"
#include "smtp.h"
#ifdef CALLSERVER
/* CD-ROM code by Fred Peachman KB7YW */
extern char *CDROM;		/* buckbook.c: defines CDROM drive letter e.g. "s:"  */

#endif /* #ifdef CALLSERVER  */
#ifdef LZW
#include "lzw.h"
#endif
#include "x.h"


#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: ftpserv.c,v 1.31 1997/09/14 14:37:46 root Exp $";
#endif

#ifdef UNIX
int ftpsecuritycheck (char *filename, int perms, int mode);
extern long ACCESSgid, ACCESSuid;
extern long CREATEgid, CREATEuid;
extern short CREATEmask;
extern int CREATEsecure;

#ifndef R_OK
#define R_OK 4
#endif
#ifndef W_OK
#define W_OK 2
#endif
#endif

static int isanonymous (char *name);
static void ftpserv (int s, void *unused, void *p);
static int pport (struct sockaddr_in *sock, char *arg);
static void ftplogin (struct ftpserv *ftp, char *pass);
static int sendit (struct ftpserv *ftp, const char *command, char *file);
static int recvit (struct ftpserv *ftp, const char *command, char *file);
static void sendmsgfile (int s, int num, char *buf, int size, FILE * fp);
static void SendPasv (int s, struct sockaddr_in *sock);
extern char *addroot (const char *root, const char *name);
extern char *defpath (struct cur_dirs *curdirs, char *path);


/* Command table */
static const char *commands[] =
{
	"user",
	"acct",
	"pass",
	"type",
	"list",
	"cwd",
	"dele",
	"help",
	"quit",
	"retr",
	"stor",
	"port",
	"nlst",
	"pwd",
	"xpwd",			/* For compatibility with 4.2BSD */
	"mkd ",
	"xmkd",			/* For compatibility with 4.2BSD */
	"xrmd",			/* For compatibility with 4.2BSD */
	"rmd ",
	"stru",
	"mode",
	"syst",
	"xmd5",
	"rsme",			/* Added by IW0CNB, for resuming interrupted trasnfers */
	"rput",
	"rnfr",
	"rnto",
	"cdup",
	"appe",
	"noop",			/* for OS/2 compatibility */
	"size",
	"pasv",			/*PASV mod by G4IDE*/
#ifdef LZW
	"xlzw",
#endif
	NULLCHAR
};


#if 0
static char challenge[] = "399 PASS challenge : %016lx\n";
static char lowmem[] = "421 System overloaded, try again later\n";
#endif

#ifdef CATALOG
#include "catalog.h"

#define CAT ftpserv_catalog

#define banner		__STR(0)
#define banner1		__STR(1)
#define banner2		__STR(2)
#define badcmd		__STR(3)
#define binwarn		__STR(4)
#define unsupp		__STR(5)
#define givepass	__STR(6)
#define anonokay	__STR(7)
#define logged		__STR(8)
#define loggeda		__STR(9)
#define typeok		__STR(10)
#define only8		__STR(11)
#define deleok		__STR(12)
#define mkdok		__STR(13)
#define delefail	__STR(14)
#define pwdmsg		__STR(15)
#define badtype		__STR(16)
#define badport		__STR(17)
#define unimp		__STR(18)
#define bye		__STR(19)
#define nodir		__STR(20)
#define cantopen	__STR(21)
#define sending		__STR(22)
#define cantmake	__STR(23)
#define writerr		__STR(24)
#define portok		__STR(25)
#define rxok		__STR(26)
#define txok		__STR(27)
#define noperm		__STR(28)
#define noconn		__STR(29)
#define badcheck	__STR(30)
#define notlog		__STR(31)
#define userfirst	__STR(32)
#define okay		__STR(33)
#define syst		__STR(34)
#define pendingto	__STR(35)
#define badseq		__STR(36)
#define norename	__STR(37)
#define help		__STR(38)
#define filesize	__STR(39)
#define notaplain	__STR(40)
#define nosuchfile	__STR(41)
#define pasvmodestr	__STR(42)
#else

/* Response messages */
static char banner[] = "220 %s, KA9Q-NOS FTP version %s\n";
static char banner1[] = "230- Ready on %s";
static char banner2[] = "230- Total active FTP sessions at %s: %d out of %d maximum\n";
static char badcmd[] = "500 Unknown command\n";
static char binwarn[] = "150- Warning: type is ASCII and %s appears to be binary\n";
static char unsupp[] = "500 Unsupported command or option\n";
static char givepass[] = "331 Enter PASS command\n";
static char anonokay[] = "331 Anonymous access, give email address as password\n";
static char logged[] = "230 Logged in\n";
static char loggeda[] = "230 Logged in as anonymous, restrictions apply\n";
static char typeok[] = "200 Type %s OK\n";
static char only8[] = "501 Only logical bytesize 8 supported\n";
static char deleok[] = "250 File deleted\n";
static char mkdok[] = "200 MKD ok\n";
static char delefail[] = "550 Delete failed: %s\n";
static char pwdmsg[] = "257 \"%s\" is current directory\n";
static char badtype[] = "501 Unknown type \"%s\"\n";
static char badport[] = "501 Bad port syntax\n";
static char unimp[] = "502 Command not yet implemented\n";
static char bye[] = "221 Goodbye!\n";
static char nodir[] = "553 Can't read directory \"%s\": %s\n";
static char cantopen[] = "550 Can't read file \"%s\": %s\n";
static char sending[] = "150 Opening data connection for %s %s %s\n";	/*N1BEE*/
static char cantmake[] = "553 Can't create \"%s\": %s\n";
static char writerr[] = "552 Write error: %s\n";
static char portok[] = "200 Port command okay\n";
static char rxok[] = "226 File received OK\n";
static char txok[] = "226 File sent OK\n";
static char noperm[] = "550 Permission denied\n";
static char noconn[] = "425 Data connection reset\n";
static char badcheck[] = "425 Bad checksum\n";
static char notlog[] = "530 Please log in with USER and PASS\n";
static char userfirst[] = "503 Login with USER first.\n";
static char okay[] = "200 Ok\n";
static char syst[] = "215 %s Type: L%d Version: %s\n";
static char pendingto[] = "350 Rename awaiting new name.\n";
static char badseq[] = "503 No prior RNFR received - RNTO ignored\n";
static char norename[] = "550 Can't rename: %s\n";
static char help[] = "214-The following commands are recognized.\n";
static char filesize[] = "213 %lu\n";
static char notaplain[] = "550 %s: not a plain file\n";
static char nosuchfile[] = "550 %s: No such file\n";
static char pasvmodestr[] = "227 Entering Passive Mode. %u,%u,%u,%u,%u,%u\n";
#endif


int Sftp = -1;			/* Prototype socket for service */
int FtpUsers = 0;
static int FtpMaxUsers = 255;

#ifdef FTPTDISC
static int32 Ftptdiscinit = 0;



/* Set ftp redundancy timer */
int
doftptdisc (int argc, char *argv[], void *p OPTIONAL)
{
	return setlong (&Ftptdiscinit, "Ftp redundancy timer (sec)", argc, argv);
}



static void
ftp_redundant (struct ftpserv *ftp)
{
	/* Clean up */
	(void) shutdown (ftp->control, 2);
	close_s (ftp->control);
	if (ftp->data != -1) {
		(void) shutdown (ftp->data, 2);
		close_s (ftp->data);
		ftp->data = -1;
	}
	return;
}
#endif



/* Set ftp Maximum number of connections */
int
doftpmaxclients (int argc, char *argv[], void *p OPTIONAL)
{
	return setint (&FtpMaxUsers, "Maximum Ftp incoming clients", argc, argv);
}



/* Start up FTP service */
int
ftpstart (int argc, char *argv[], void *p OPTIONAL)
{
	return (installserver (argc, argv, &Sftp, "FTP listener", IPPORT_FTP,
		INADDR_ANY, "ftpserv", ftpserv, 2048, NULL));
}



static void 
sendmsgfile (int s, int num, char *buf, int size, FILE *fp)
{

	while (fgets (buf, size, fp)) {
		rip (buf);
		usprintf (s, "%d- %s\n", num, buf);
	}
}



static int
isanonymous (char *name)
{
FILE *fp, *fpsave = NULLFILE;
int retval = 1;
char buf[128], *cp;

	if ((fp = fopen (Userfile, READ_TEXT)) != NULLFILE) {
		for ( ; ; ) {
			if (fgets (buf, 128, fp) == NULLCHAR) {
				if (fpsave) {
					(void) fclose (fp);
					fp = fpsave;
					fpsave = NULLFILE;
					continue;
				}
				break;
			}
			if (!strnicmp (buf, "#include", 8)) {
				rip (buf);
				cp = skipwhite (&buf[8]);
				fpsave = fp;
				if ((fp = fopen (cp, READ_TEXT)) == NULLFILE) {
					fp = fpsave;
					fpsave = NULLFILE;
				}
				continue;
			}
			if ((cp = strpbrk (buf, " \t")) == NULLCHAR)
				/* Bogus entry */
				continue;
			*cp++ = '\0';

			if (!stricmp (name, buf)) {
				retval = 0;
				break;	/* Found user */
			}
		}
		if (fpsave)
			(void) fclose (fpsave);
		(void) fclose (fp);
	}
	return (retval);
}



static void
ftpserv (
int s,				/* Socket with user connection */
void *unused OPTIONAL,
void *p OPTIONAL
) {
struct ftpserv ftp;
char const **cmdp, *cp, *mode = NULLCHAR;
char buf[512], *arg, *file, *cp2;
time_t t;
int cnt, i;
struct sockaddr_in thesocket;
struct cur_dirs dirs;
char *rnfrom = NULLCHAR;
FILE *fpm;
struct sockaddr_in lsocket;	/*PASV mod*/
struct sockaddr_in lcsocket;
#if 0
struct stat cwdstat;
char *cp1;
#endif

	(void) sockmode (s, SOCK_ASCII);
	memset ((char *) &ftp, 0, sizeof (ftp));	/* Start with clear slate */
	ftp.data = -1;

	(void) sockowner (s, Curproc);	/* We own it now */
	ftp.control = s;
	/* Set default data port */
	i = SOCKSIZE;
	if (getpeername (s, (char *) &thesocket, &i) != -1)	{
		thesocket.sin_port = IPPORT_FTPD;
		ASSIGN (ftp.port, thesocket);
	}

	if (++FtpUsers > FtpMaxUsers) {
		usprintf (s, "\n200- Sorry, too many FTP users at %s this time. Try again later!\n", Hostname);
		FtpUsers--;
		return;
	}

#ifdef FTPTDISC
	/* Set the timeout timer - WG7J */
	set_timer (&ftp.tdisc, Ftptdiscinit * 1000L);
	ftp.tdisc.func = (void (*)(void *)) ftp_redundant;
	ftp.tdisc.arg = &ftp;
	start_timer (&ftp.tdisc);
#endif

#ifdef STATS_USE
	STATS_adduse (1);
#endif
#ifdef XSERVER
	xnotify (X_FTP);
#endif
	log (s, "open FTP");
	strcpy (buf, ETCdir);
	if ((fpm = fopen (strcat (buf, "/banner.ftp"), "r")) != NULL) {
		sendmsgfile (s, 220, buf, sizeof (buf), fpm);
		(void) fclose (fpm);
	}
	usprintf (s, banner, Hostname, Version);
	(void) init_dirs (&dirs);
	ftp.curdirs = &dirs;
	(void) time (&t);
	cp = ctime (&t);
#if 0
	if ((cp1 = strchr (cp, '\n')) != NULLCHAR)
		*cp1 = '\0';
#endif

	/* Command interpreting loop */
loop:
	if ((cnt = recvline (s, (unsigned char *) buf, sizeof (buf))) == -1) {
		/* He closed on us */
		goto finish;
	}
#ifdef FTPTDISC
	/* Reset the timeout timer - WG7J */
	start_timer (&ftp.tdisc);
#endif
	if (cnt == 0) {
		/* Can't be a legal FTP command */
		usprintf (ftp.control, badcmd);
		goto loop;
	}
	rip (buf);
	/* Translate first word to lower case */
	for (cp2 = buf; *cp2 != ' ' && *cp2 != '\0'; cp2++)
		*cp2 = (char) tolower (*cp2);
	/* Find command in table; if not present, return syntax error */
	for (cmdp = commands; *cmdp != NULLCHAR; cmdp++)
		if (strnicmp (*cmdp, buf, strlen (*cmdp)) == 0)
			break;
	if (*cmdp == NULLCHAR) {
		usprintf (ftp.control, badcmd);
		goto loop;
	}
#if 0
	tcmdprintf ("Command received was: '%s'\n", buf);
#endif
	/* Allow only USER, PASS and QUIT before logging in */
	if (ftp.path == NULLCHAR) {
		switch (cmdp - commands) {
			case USER_CMD:
			case PASS_CMD:
			case QUIT_CMD:
				break;
			default:
				usprintf (ftp.control, notlog);
				goto loop;
		}
	}
	arg = &buf[strlen (*cmdp)];
	while (*arg == ' ')
		arg++;

	/* Execute specific command */
	switch (cmdp - commands) {
		case USER_CMD:
			free (ftp.username);
			ftp.username = strdup (arg);
#if 0
			if(sp_user(ftp.username)) {
				time(&ftp.ttim);
				usprintf(ftp.control,challenge,ftp.ttim);
			} else
#endif
			if (isanonymous (arg))
				usprintf (ftp.control, anonokay);
			else
				usprintf (ftp.control, givepass);
			break;
		case TYPE_CMD:
			switch (arg[0]) {
				case 'A':
				case 'a':	/* Ascii */
					ftp.type = ASCII_TYPE;
					usprintf (ftp.control, typeok, arg);
					break;
				case 'l':
				case 'L':
					while (*arg != ' ' && *arg != '\0')
						arg++;
					if (*arg == '\0' || *++arg != '8') {
						usprintf (ftp.control, only8);
						break;
					}
					ftp.type = LOGICAL_TYPE;
					ftp.logbsize = 8;
					usprintf (ftp.control, typeok, arg);
					break;
				case 'B':
				case 'b':	/* Binary */
				case 'I':
				case 'i':	/* Image */
					ftp.type = IMAGE_TYPE;
					usprintf (ftp.control, typeok, arg);
					break;
				default:	/* Invalid */
					usprintf (ftp.control, badtype, arg);
					break;
			}
			break;
		case QUIT_CMD:
			usprintf (ftp.control, bye);
			goto finish;
		case RNFR_CMD:
			file = addroot (ftp.curdirs->dir, arg);
			if (!permcheck (ftp.path, ftp.perms, RNFR_CMD, file))
				usprintf (ftp.control, noperm);
			else if (access (file, 6))
				usprintf (ftp.control, cantopen, file, SYS_ERRLIST(errno));
			else {
				usprintf (ftp.control, pendingto);
				rnfrom = strdup (file);
			}
			free (file);
			break;
		case RNTO_CMD:
			file = addroot (ftp.curdirs->dir, arg);
			if (rnfrom == NULLCHAR)
				usprintf (ftp.control, badseq);
			else {
				if (!permcheck (ftp.path, ftp.perms, RNTO_CMD, file))
					usprintf (ftp.control, noperm);
				else {
					if (rename (rnfrom, file) == -1)
						usprintf (ftp.control, norename, SYS_ERRLIST(errno));
					else
						usprintf (ftp.control, okay);
				}
				free (rnfrom);
				rnfrom = NULLCHAR;
			}
			free (file);
			break;
		case RETR_CMD:
		case RSME_CMD:
			file = addroot (ftp.curdirs->dir, arg);
			switch (ftp.type) {
				case IMAGE_TYPE:
				case LOGICAL_TYPE:
					mode = READ_BINARY;
					break;
				default:
				case ASCII_TYPE:
					mode = READ_TEXT;
					break;
			}
			if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
				usprintf (ftp.control, noperm);
#ifdef UNIX
			else if (!ftpsecuritycheck (file, ftp.perms, R_OK) || (ftp.fp = fopen (file, mode)) == NULLFILE)
#else
			else if ((ftp.fp = fopen (file, mode)) == NULLFILE)
#endif
				usprintf (ftp.control, cantopen, file, SYS_ERRLIST(errno));
			else {
				if ((cmdp - commands) == RSME_CMD) {
					log (ftp.control, "RSME %s", file);
					if (ftp.type == ASCII_TYPE && isbinary (ftp.fp))
						usprintf (ftp.control, binwarn, file);

					(void) sendit (&ftp, "RSME", file);
				} else {
					log (ftp.control, "RETR %s", file);
					if (ftp.type == ASCII_TYPE && isbinary (ftp.fp))
						usprintf (ftp.control, binwarn, file);

					(void) sendit (&ftp, "RETR", file);
				}
			}
			free (file);
			break;
		case SIZE_CMD:
			file = addroot (ftp.curdirs->dir, arg);
			if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
				usputs (ftp.control, noperm);
			else if ((ftp.fp = fopen (file, READ_BINARY)) != NULLFILE) {
				usprintf (ftp.control, filesize, filelength (fileno (ftp.fp)));
				(void) fclose (ftp.fp);
			} else if (!access (file, 0))
				usprintf (ftp.control, notaplain, file);
			else
				usprintf (ftp.control, nosuchfile, file);

			free (file);
			break;
		case STOR_CMD:
			cp = "STOR";
			goto store2;
		case APPE_CMD:
			cp = "APPE";
			goto store2;
		case RPUT_CMD:
			cp = "RPUT";
store2:
			file = addroot (ftp.curdirs->dir, arg);
			switch (ftp.type) {
				case IMAGE_TYPE:
				case LOGICAL_TYPE:
					if (cmdp - commands != STOR_CMD)
						mode = APPEND_BINARY;
					else
						mode = WRITE_BINARY;
					break;
				default:
				case ASCII_TYPE:
					if (cmdp - commands != STOR_CMD)
						mode = APPEND_TEXT;
					else
						mode = WRITE_TEXT;
					break;
			}
			if (!permcheck (ftp.path, ftp.perms, cmdp - commands, file))
				usprintf (ftp.control, noperm);
#ifdef UNIX
			else if (!ftpsecuritycheck (file, ftp.perms, W_OK) || (ftp.fp = fopen (file, mode)) == NULLFILE)
#else
			else if ((ftp.fp = fopen (file, mode)) == NULLFILE)
#endif
				usprintf (ftp.control, cantmake, file, SYS_ERRLIST(errno));
			else {
				log (ftp.control, "%s %s", cp, file);
				(void) recvit (&ftp, cp, file);
#ifdef UNIX
				if (CREATEsecure) {
					(void) chown (file, (uid_t) CREATEuid, (gid_t) CREATEgid);
					(void) chmod (file, (mode_t) CREATEmask);
				}
#endif
			}
			free (file);
			break;
		case PORT_CMD:
			if (pport (&ftp.port, arg) == -1)
				usprintf (ftp.control, badport);
			else
				usprintf (ftp.control, portok);

			break;
#ifndef CPM
		case LIST_CMD:
		case NLST_CMD:
			file = addroot (ftp.curdirs->dir, defpath (ftp.curdirs, arg));
			if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
				usprintf (ftp.control, noperm);
			else {
#ifdef UNIX
				if (ftpsecuritycheck (file, ftp.perms, R_OK))	{

#endif
					cp = Command->curdirs->dir;
					Command->curdirs->dir = ftp.curdirs->dir;
					if ((ftp.fp = dir (file, ((cmdp - commands) == LIST_CMD) ? 2 : 0)) == NULLFILE)
						usprintf (ftp.control, nodir, file, SYS_ERRLIST(errno));
					else
						(void) sendit (&ftp, ((cmdp - commands) == LIST_CMD) ? "LIST" : "NLST", file);
					Command->curdirs->dir = cp;
#ifdef UNIX
				} else
					usprintf (ftp.control, noperm);
#endif
			}
			free (file);
			break;
		case CDUP_CMD:
			sprintf (arg, "..");	/* and fall through */
		case CWD_CMD:
#ifdef old_CALLSERVER
			/* if the requested path contains the CROM drive letter:  */
			if (CDROM != NULLCHAR && strnicmp (CDROM, arg, 2) == 0) {
				if (strchr (arg, '/') == NULLCHAR) {
					file = (char *) mallocw (strlen (arg) + 2);
					sprintf (file, "%s/", arg);
				} else
					file = strdup (arg);
				if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file)) {
					usprintf (ftp.control, noperm);
					free (file);
#ifdef MSDOS
					/* Don'tcha just LOVE %%$#@!! MS-DOS? - which is what we are running */
				} else if (file[2] == '/' || access (file, 0) == 0) {
#else
				} else if (access (file, 0) == 0) {	/* See if it exists */
#endif
					/* Succeeded, record in control block */
					free (ftp.cd);
					ftp.cd = file;
					usprintf (ftp.control, "You may return to your default drive & directory by entering:\n\t\t\"cd %s\"\n\n", ftp.path);
					usprintf (ftp.control, pwdmsg, file);
				} else {
					/* Failed, don't change anything */
					usprintf (ftp.control, nodir, file, SYS_ERRLIST(errno));
					free (file);
				}
				break;
			}
			/* requested path does not contain CDROM drive letter:                      */
			/* if current dir is in CDROM - and a "off-root" is requested:
			   go back to default path.		*/
			if ((CDROM != NULLCHAR && strnicmp (ftp.cd, CDROM, 2) == 0) && (arg[0] == '/')) {
				free (ftp.cd);
				ftp.cd = strdup (ftp.path);	/* go back to default path            */
			}
#endif /* #ifdef CALLSERVER  */
			if (*arg == '/' || *arg == '\\')
				file = addroot (ftp.path, &arg[1]);
			else
				file = addroot (ftp.curdirs->dir, arg);
			if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
				usprintf (ftp.control, noperm);
			else if (dir_ok (file, ftp.curdirs)) {
				/* Succeeded */
				/* If exists, send the contents of 'desc.ftp' in the new
				 * directory...
				 */
				strncpy (buf, file, 512);
				if ((fpm = fopen (strcat (buf, "/desc.ftp"), "r")) != NULL) {
					sendmsgfile (ftp.control, 257, buf, sizeof (buf), fpm);
					(void) fclose (fpm);
				}
#ifndef MSDOS
				/* If exists, send the contents of '.message' in the new
				 * directory...
				 */
				strncpy (buf, file, 512);
				if ((fpm = fopen (strcat (buf, "/.message"), "r")) != NULL) {
					sendmsgfile (ftp.control, 257, buf, sizeof (buf), fpm);
					(void) fclose (fpm);
				}
#endif

				usprintf (ftp.control, pwdmsg,
					(!strcmp (ftp.root, ftp.curdirs->dir)) ? "/"
					: (!strncmp (ftp.root, ftp.curdirs->dir, strlen (ftp.root)))
					? &ftp.curdirs->dir[strlen (ftp.root)]
					: ftp.curdirs->dir);
			} else
				/* Failed, nothing changed */
				usprintf (ftp.control, nodir, arg, SYS_ERRLIST(errno));
			free (file);
			break;
		case XPWD_CMD:
		case PWD_CMD:
			usprintf (ftp.control, pwdmsg,
				(!strcmp (ftp.root, ftp.curdirs->dir)) ? "/"
				: (!strncmp (ftp.root, ftp.curdirs->dir, strlen (ftp.root)))
				? &ftp.curdirs->dir[strlen (ftp.root)]
				: ftp.curdirs->dir);
			break;
#else
		case LIST_CMD:
		case NLST_CMD:
		case CWD_CMD:
		case XPWD_CMD:
		case PWD_CMD:
#endif
		case ACCT_CMD:
			usprintf (ftp.control, unimp);
			break;
		case HELP_CMD:
			usputs (ftp.control, help);
			for (cmdp = commands, i = buf[0] = 0; *cmdp != NULLCHAR; cmdp++) {
				strcat (buf, "   ");
				strcat (buf, *cmdp);
				if (strlen (*cmdp) == 3)
					strcat (buf, " ");
				if (i++ == 9) {
					(void) strupr (buf);
					usprintf (ftp.control, "%s\n", buf);
					i = buf[0] = 0;
				}
			}
			if (i) {
				(void) strupr (buf);
				usprintf (ftp.control, "%s\n", buf);
			}
			usprintf (ftp.control, "214 Report problems to sysop@%s\n", Hostname);
			break;
		case NOOP_CMD:
			usputs (ftp.control, okay);
			break;
		case DELE_CMD:
			file = addroot (ftp.curdirs->dir, arg);
			if (!permcheck (ftp.path, ftp.perms, DELE_CMD, file))
				usprintf (ftp.control, noperm);
			else if (unlink (file) == 0) {
				log (ftp.control, "DELE %s", file);
				usprintf (ftp.control, deleok);
			} else
				usprintf (ftp.control, delefail, SYS_ERRLIST(errno));

			free (file);
			break;
		case PASS_CMD:
			if (ftp.username == NULLCHAR)
				usprintf (ftp.control, userfirst);
			else
				ftplogin (&ftp, arg);
			break;
#ifndef	CPM
		case XMKD_CMD:
		case MKD_CMD:
			file = addroot (ftp.curdirs->dir, arg);
			if (!permcheck (ftp.path, ftp.perms, MKD_CMD, file))
				usprintf (ftp.control, noperm);
			else if (mkdir (file, (mode_t) 0777) == 0) {
				log (ftp.control, "MKD %s", file);
				usprintf (ftp.control, mkdok);
			} else
				usprintf (ftp.control, cantmake, file, SYS_ERRLIST(errno));

			free (file);
			break;
		case XRMD_CMD:
		case RMD_CMD:
			file = addroot (ftp.curdirs->dir, arg);
			if (!permcheck (ftp.path, ftp.perms, RMD_CMD, file))
				usprintf (ftp.control, noperm);
			else if (rmdir (file) == 0) {
				log (ftp.control, "RMD %s", file);
				usprintf (ftp.control, deleok);
			} else
				usprintf (ftp.control, delefail, SYS_ERRLIST(errno));

			free (file);
			break;
		case STRU_CMD:
			if (tolower (arg[0]) != 'f')
				usprintf (ftp.control, unsupp);
			else
				usprintf (ftp.control, okay);
			break;
		case MODE_CMD:
			if (tolower (arg[0]) != 's')
				usprintf (ftp.control, unsupp);
			else
				usprintf (ftp.control, okay);
			break;
		case SYST_CMD:
			usprintf (ftp.control, syst, System, NBBY, Version);
			break;
		case PASV_CMD:		/*PASV mod*/
			/* Send the PASV message. Use the IP address
			 * on the local end of our control connection. */
			if (ftp.data != -1)	/* left over error - kill the socket first */
				close_s (ftp.data);

			ftp.data = socket (AF_INET, SOCK_STREAM, 0);
			(void) listen (ftp.data, 0);
			i = SOCKSIZE;
			(void) getsockname (ftp.data, (char *) &lsocket, &i);
			if (!i)
				break;
			i = SOCKSIZE;
			(void) getsockname (ftp.control, (char *) &lcsocket, &i);
			if (!i)
				break;
			lsocket.sin_addr.s_addr = lcsocket.sin_addr.s_addr;
			/* send the address to the client.  */
			SendPasv (ftp.control, &lsocket);
			break;
		case XMD5_CMD:		/*PASV mod*/
			file = addroot (ftp.curdirs->dir, arg);
			switch (ftp.type) {
				case IMAGE_TYPE:
				case LOGICAL_TYPE:
					mode = READ_BINARY;
					break;
				default:
				case ASCII_TYPE:
					mode = READ_TEXT;
					break;
			}
			if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
				usprintf (ftp.control, noperm);
#ifdef UNIX
			else if (!ftpsecuritycheck (file, ftp.perms, R_OK) || (ftp.fp = fopen (file, mode)) == NULLFILE)
#else
			else if ((ftp.fp = fopen (file, mode)) == NULLFILE)
#endif
				usprintf (ftp.control, cantopen, file, SYS_ERRLIST(errno));
			else {
				char hash[16];

				log (ftp.control, "XMD5 %s", file);
				if (ftp.type == ASCII_TYPE && isbinary (ftp.fp))
					usprintf (ftp.control, binwarn, file);

				(void) md5hash (ftp.fp, hash, ftp.type == ASCII_TYPE);
				(void) fclose (ftp.fp);
				ftp.fp = NULLFILE;
				usprintf (ftp.control, "200 ");
				for (i = 0; i < 16; i++)
					usprintf (ftp.control, "%02x", hash[i] & 0xff);
				usprintf (ftp.control, " %s\n", file);
			}
			free (file);
			break;
#ifdef LZW
		case XLZW_CMD:
			if (ftp.lzw)
				usprintf (ftp.control, "550 Already using LZW compression\n");
			else {
				usprintf (ftp.control, okay);
				sscanf (&buf[5], "%d %d", &ftp.lzwbits, &ftp.lzwmode);
				ftp.lzw = 1;
			}
			break;
		default:
			break;
#endif
	}
#endif
	goto loop;

finish:

#ifdef FTPTDISC
	stop_timer (&ftp.tdisc);
#endif

	log (ftp.control, "close FTP from '%s'", ftp.username);
	FtpUsers--;
#ifdef XSERVER
	xnotify (X_FTP);
#endif
	/* Clean up */
	close_s (ftp.control);
	if (ftp.data != -1)
		close_s (ftp.data);
	if (ftp.fp != NULLFILE)
		(void) fclose (ftp.fp);
	free (ftp.username);
	free (ftp.path);
	free_dirs (&dirs);
	free (rnfrom);
}



/* Shut down FTP server */
int
ftp0 (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
	return (deleteserver (&Sftp));
}



static
int
pport (struct sockaddr_in *sock, char *arg)
{
uint32 n;
int i;

	n = 0;
	for (i = 0; i < 4; i++) {
		n = (uint32) atoi (arg) + (n << 8);
		if ((arg = strchr (arg, ',')) == NULLCHAR)
			return -1;
		arg++;
	}
	sock->sin_addr.s_addr = n;
	n = (uint32) atoi (arg);
	if ((arg = strchr (arg, ',')) == NULLCHAR)
		return -1;
	arg++;
	n = (uint32) atoi (arg) + (n << 8);
	sock->sin_port = (int16) n;
	return 0;
}



/* Attempt to log in the user whose name is in ftp->username and password
 * in pass
 */
static void
ftplogin (struct ftpserv *ftp, char *pass)
{
char *path, buf[128], *cp;
char *p, *cp1;
time_t t;
FILE *fp;
int anony = 0;

	path = mallocw (200);
	if ((ftp->perms = userlogin (ftp->username, pass, &path, 200, &anony)) == -1) {
		log (ftp->control, "FTP login refused - '%s'", ftp->username);
		usprintf (ftp->control, noperm);
		free (path);
		return;
	}
	/* Set up current directory and path prefix */
	ftp->path = strdup (path);
	cp = strdup (path);
	if ((cp1 = strchr (cp, ';')) != NULLCHAR)
		*cp1 = '\0';
	if ((cp1 = strchr (cp, '=')) != NULLCHAR)
		*cp1 = '\0';
#ifdef MSDOS
	if (*cp == '/' || *cp == '\\')	{
		sprintf (buf, "%c:%s", ftp->curdirs->drv + '`', cp);
		ftp->root = strdup (buf);
	} else
#endif
		ftp->root = strdup (cp);	/*lint !e539 */
	free (cp);

	{
	FILE *out;

		sprintf (buf, "%s/ftp.log", LOGdir);
		if ((out = fopen (buf, APPEND_TEXT)) != NULLFILE) {
			time_t tt;

			(void) time (&tt);
			fprintf (out, "FTP from %s (%s) on %s", ftp->username, pass, ptime (&tt));
			(void) fclose (out);
		}
	}

	(void) time (&t);
	cp = ctime (&t);
	usprintf (ftp->control, banner1, cp);
	usprintf (ftp->control, banner2, Hostname, FtpUsers, FtpMaxUsers);

	/* everyone gets the ftpmotd file, if it exists */
	if ((fp = fopen (Ftpmotd, "r")) != NULL) {
		sendmsgfile (ftp->control, 230, buf, sizeof (buf), fp);
		(void) fclose (fp);
	}
	/* if there is a message.ftp file in the login dir, send it too */
	sprintf (buf, "%s%s%s", path, (path[strlen (path) - 1] == '/') ? "" : "/", "message.ftp");
	if ((fp = fopen (buf, "r")) != NULL) {
		sendmsgfile (ftp->control, 230, buf, sizeof (buf), fp);
		(void) fclose (fp);
	}
	path = strdup (ftp->path);
	if ((p = strpbrk (path, "=;")) != NULLCHAR)
		*p = 0;
	if (dir_ok (path, ftp->curdirs)) {
		/* Succeeded */
		/* If exists, send the contents of 'desc.ftp' in the new directory... */
		sprintf (buf, "%s/desc.ftp", path);
		if ((fp = fopen (buf, "r")) != NULL) {
			sendmsgfile (ftp->control, 230, buf, sizeof (buf), fp);
			(void) fclose (fp);
		}
	}
	free (path);
	if (!anony) {
		usprintf (ftp->control, logged);
		log (ftp->control, "FTP login - '%s'", ftp->username);
	} else {
		usprintf (ftp->control, loggeda);
		log (ftp->control, "FTP anonymous login - '%s' (%s)", ftp->username, pass);
	}
	usflush (ftp->control);
	kwait (NULL);
}



#ifdef	MSDOS
/* Illegal characters in a DOS filename */
static char badchars[] = "\"[]|<>+=;,";
#endif



/* Return 1 if the file operation is allowed, 0 otherwise */
int
permcheck (char *path, long perms, int op, char *file)
{
char *cp, *cp1;
int newperms;
int match = FALSE;
int notabove = FALSE;

	if (file == NULLCHAR || path == NULLCHAR)
		return 0;	/* Probably hasn't logged in yet */

	/* To get to the CDROM - EVERYBODY gets read privs, regardless of what
	   /ftpusers has to say about it!!! - kb7yw */

#ifdef CALLSERVER
	if (CDROM != NULLCHAR && strnicmp (file, CDROM, 2) == 0) {
		/* Check for characters illegal in MS-DOS file names */
		for (cp = badchars; *cp != '\0'; cp++) {
			if (strchr (&file[2], *cp) != NULLCHAR)
				return 0;
		}

		switch (op) {	/* What to do when the user is on the cd-rom drive      */
			case RETR_CMD:	/* Everybody gets read privs regardless of ftpusers  */
				/* User has permission to read files */
				return 1;
			case DELE_CMD:
			case RMD_CMD:
				/* User must not have permission to (over)write files */
			case STOR_CMD:
			case MKD_CMD:
				/* User must NOT have permission to (over)write files */
				return 0;
		}		/* switch(op) */
	}			/* if strncmp(....  */
# endif				/* #ifdef CALLSERVER  */
#ifndef MAC
	/* The target file must be under the user's allowed search path */
	/* We let them specify multiple paths using path;path... -russ */
	/* Allow /nos/public;/nos/public/incoming=3 style path statements -bruce */
	for (cp = path; *cp != '\0'; cp = cp1 + 1) {
		char *cp2;

		newperms = perms;
		if ((cp1 = strchr (cp, ';')) == NULLCHAR)
			cp1 = &cp[strlen(cp) - 1];
		cp2 = strchr (cp, '=');
		if (!cp2 || (cp2 > cp1))
			cp2 = cp1;
		else
			newperms = atoi (cp2 + 1);
		/* Take care of the case when we have a path statement in ftpusers
		   like:  /nos/public;/nos/public/incoming=3 and the user cwd's to
	           /nos.  Make sure the user is not above the smallest length path
		   so that we can have path statements like: /nos/public;/nos=1;/nos...
		*/
		if ((int) strlen (file) >= (cp2 - cp))
			notabove = TRUE;
		if (!strnicmp (file, cp, (unsigned) (cp2 - cp))
#ifdef MSDOS
		    || !strnicmp (file + 2, cp, cp2 - cp)
#endif
		    ) {
			match = TRUE;
			perms = newperms;
		}
	}
	/* We must have both a match and not be above the smallest level to
	   continue */
	if (!match || !notabove)
		return 0;
#endif

#ifdef	MSDOS
	/* Check for characters illegal in MS-DOS file names */
	for (cp = badchars; *cp != '\0'; cp++) {
		if (strchr (file, *cp) != NULLCHAR)
			return 0;
	}
#endif

	switch (op) {
		case RETR_CMD:
			/* User must have permission to read files */
			if (perms & FTP_READ)
				return 1;
			return 0;
		case DELE_CMD:
		case RMD_CMD:
		case RPUT_CMD:
		case APPE_CMD:
			/* User must have permission to (over)write files */
			if (perms & FTP_WRITE)
				return 1;
			return 0;
		case RNFR_CMD:
		case RNTO_CMD:
		case STOR_CMD:
		case MKD_CMD:
			/* User must have permission to (over)write files, or permission
			 * to create them if the file doesn't already exist
			 */
			if (perms & FTP_WRITE)
				return 1;
			if (access (file, 2) == -1 && (perms & FTP_CREATE))
				return 1;
			return 0;
		default:
			break;
	}
	return 0;		/* "can't happen" -- keep lint happy */
}



static int
sendit (struct ftpserv *ftp, const char *command, char *file)
{
long total, starting;
unsigned long check;
struct sockaddr_in dport;
char *cp, *cp2;
char fsizetext[20];	/* N1BEE */
int pasv = 0;

	if (ftp->data != -1)
		pasv = 1;

	fsizetext[0] = 0;
	if (!pasv) {
		ftp->data = socket (AF_INET, SOCK_STREAM, 0);
		dport.sin_family = AF_INET;
		dport.sin_addr.s_addr = INADDR_ANY;
		dport.sin_port = IPPORT_FTPD;
		(void) bind (ftp->data, (char *) &dport, SOCKSIZE);
	}
	sprintf (fsizetext, "(%lu bytes)", filelength (fileno (ftp->fp)));
	usprintf (ftp->control, sending, command, (strlen (file) == strlen (ftp->path)) ? "/" : &file[strlen (ftp->path)], fsizetext);	/* N1BEE */
	if (!pasv) {
		if (connect (ftp->data, (char *) &ftp->port, SOCKSIZE) == -1) {
			(void) fclose (ftp->fp);
			ftp->fp = NULLFILE;
			close_s (ftp->data);
			ftp->data = -1;
			usprintf (ftp->control, noconn);
			return -1;
		}
	} else {		/* PASV mode */
		/* wait for the client to open the connection */
		/* ftp->data has been setup already */
		(void) accept (ftp->data, NULLCHAR, (int *) NULL);
	}
	if (strcmp (command, "RSME") == 0) {
		total = -1;
		cp = mallocw (40);
		if (recvline (ftp->control, (unsigned char *) cp, 40) == -1) {
			free (cp);
			goto send_err;
		}
		starting = atol (cp);
		/* If checksum field is not present go on anyway, for compatibility
		 * with previous scheme. If present check it and barf if wrong.
		 */
		cp2 = strchr (cp, ' ');
		if (cp2 != NULLCHAR) {
			check = (unsigned long) atol (cp2);
			check -= checksum (ftp->fp, starting);
			if (check != 0) {
				free (cp);
				usprintf (ftp->control, badcheck);
				(void) shutdown (ftp->data, 1);	/* Blow away data connection */
				goto send_err;
			}
		} else if (fseek (ftp->fp, starting, SEEK_SET) != 0) {
			free (cp);
			usprintf (ftp->control, noconn);
			(void) shutdown (ftp->data, 2);	/* Blow away data connection */
			goto send_err;
		}
	}
#ifdef FTPTDISC
	/* Turn off the timeout timer here, some ftp's could
	 * take a long time with sloooow packet channels - WG7J
	 */
	stop_timer (&ftp->tdisc);
#endif

#ifdef LZW
	if (ftp->lzw)
		lzwinit (ftp->data, ftp->lzwbits, ftp->lzwmode);
#endif

	/* Do the actual transfer */
	total = sendfile (ftp->fp, ftp->data, ftp->type, 0);

#ifdef FTPTDISC
	/* And turn it back on now */
	start_timer (&ftp->tdisc);
#endif

	if (total == -1) {
		/* An error occurred on the data connection */
		usprintf (ftp->control, noconn);
		(void) shutdown (ftp->data, 2);	/* Blow away data connection */
	} else
		usprintf (ftp->control, txok);

send_err:
	(void) fclose (ftp->fp);
	ftp->fp = NULLFILE;
	close_s (ftp->data);
	ftp->data = -1;
#ifdef LZW
	ftp->lzw = 0;
#endif
	if (total == -1)
		return -1;
	else
		return 0;
}



static int
recvit (struct ftpserv *ftp, const char *command, char *file)
{
struct sockaddr_in dport;
long total, starting;
int pasv = 0;

	if (ftp->data != -1)
		pasv = 1;

	if (!pasv) {
		ftp->data = socket (AF_INET, SOCK_STREAM, 0);
		dport.sin_family = AF_INET;
		dport.sin_addr.s_addr = INADDR_ANY;
		dport.sin_port = IPPORT_FTPD;
		(void) bind (ftp->data, (char *) &dport, SOCKSIZE);
	}
	usprintf (ftp->control, sending, command, file, "");

	if (!pasv) {
		if (connect (ftp->data, (char *) &ftp->port, SOCKSIZE) == -1) {
			(void) fclose (ftp->fp);
			ftp->fp = NULLFILE;
			close_s (ftp->data);
			ftp->data = -1;
			usprintf (ftp->control, noconn);
			return -1;
		}
	} else {		/* PASV mode */
		/* wait for the client to open the connection */
		/* ftp->data has been setup already */
		(void) accept (ftp->data, NULLCHAR, (int *) NULL);
	}
	if (strcmp (command, "APPE") == 0)
		fseek (ftp->fp, 0, SEEK_END);
	if (strcmp (command, "RPUT") == 0) {
		if ((starting = (long) getsize (ftp->fp)) == -1)
			starting = 0L;
		usprintf (ftp->control, "%lu %lu\n", starting, checksum (ftp->fp, starting));
		fseek (ftp->fp, starting, SEEK_SET);
	}
#ifdef FTPTDISC
	/* Turn of the timeout timer here; some ftp's could
	 * take a long time with sloooow packet channels - WG7J
	 */
	stop_timer (&ftp->tdisc);
#endif

#ifdef LZW
	if (ftp->lzw)
		lzwinit (ftp->data, ftp->lzwbits, ftp->lzwmode);
#endif

	/* Do the actual transfer */
	total = recvfile (ftp->fp, ftp->data, ftp->type, 0);

#ifdef FTPTDISC
	/* And turn it back on now */
	start_timer (&ftp->tdisc);
#endif

#ifdef	CPM
	if (ftp->type == ASCII_TYPE)
		putc (CTLZ, ftp->fp);
#endif
	if (total == -1) {
		/* An error occurred while writing the file */
		usprintf (ftp->control, writerr, SYS_ERRLIST(errno));
		(void) shutdown (ftp->data, 2);	/* Blow it away */
	} else
		usprintf (ftp->control, rxok);
	close_s (ftp->data);
	ftp->data = -1;
	(void) fclose (ftp->fp);
	ftp->fp = NULLFILE;
#ifdef LZW
	ftp->lzw = 0;
#endif
	if (total == -1)
		return -1;
	else
		return 0;
}



#ifdef UNIX
#if 0
extern uid_t geteuid (void);
extern gid_t getegid (void);
#endif

int
ftpsecuritycheck (char *filename, int perms, int mode)
{
int accval;		/*lint -esym(550, accval) */
int retval;
int fd;
char *buf, *cp;
uid_t ruid = getuid ();
gid_t rgid = getgid ();
#if 0
uid_t euid = geteuid ();
gid_t egid = getegid ();
#endif

	if (perms & SYSOP_CMD)
		return (1);
#if 0
	log (-1, "ftpsecuritycheck: file - %s", filename);
	log (-1, "ftpsecuritycheck: mode - %d", mode);
	log (-1, "ftpsecuritycheck: ftpgid - %d", ACCESSgid);
	log (-1, "ftpsecuritycheck: ftpuid - %d", ACCESSuid);
	log (-1, "ftpsecuritycheck: ruid - %d", ruid);
	log (-1, "ftpsecuritycheck: rgid - %d", rgid);
	log (-1, "ftpsecuritycheck: euid - %d", euid);
	log (-1, "ftpsecuritycheck: egid - %d", egid);
#endif
	(void) setregid ((int16) ACCESSgid, (unsigned short) -1);
	(void) setreuid ((int16) ACCESSuid, (unsigned short) -1);
#if 0
	log (-1, "ftpsecuritycheck: uid - %d", getuid ());
	log (-1, "ftpsecuritycheck: gid - %d", getgid ());
	log (-1, "ftpsecuritycheck: euid - %d", geteuid ());
	log (-1, "ftpsecuritycheck: egid - %d", getegid ());
#endif
	retval = ((accval = access (filename, mode)) == 0);

	/* for write permissions, you can't JUST use access, if the
	   file is a new file being created. */
	if (mode == W_OK && errno == 2) {
		/* if the file doesn't already exist, it takes two steps */
		if (access (filename, 0)) {
			/* first we see if the file CAN be created */
			fd = open (filename, O_CREAT | O_WRONLY | O_TRUNC);
			if (fd != -1) {
				close (fd);
				unlink (filename);
				retval = 1;
			}
		}
	}
	/* now we PROBABLY know whether we can do this, but we must also
	   check the directory, to see if we have write permissions, if it
	   is a write
	 */
	if (mode == W_OK) {
		buf = strdup (filename);
		cp = strrchr (buf, '/');
		if (cp)
			*cp = 0;
		else
			strcpy (buf, ".");
		/* set retval to whether we have write permissions to the directory */
		if (access (buf, mode))
			retval = 0;
		free (buf);
	}
	(void) setreuid (ruid, (unsigned short) -1);
	(void) setregid (rgid, (unsigned short) -1);
#if 0
	log (-1, "ftpsecuritycheck: neweuid - %d", geteuid ());
	log (-1, "ftpsecuritycheck: newegid - %d", getegid ());
	log (-1, "ftpsecuritycheck: newuid - %d", getuid ());
	log (-1, "ftpsecuritycheck: newgid - %d", getgid ());
	log (-1, "ftpsecuritycheck: accval = %d", accval);
	log (-1, "ftpsecuritycheck: retval = %d", retval);
	log (-1, "ftpsecuritycheck: errno = %d", errno);
#endif
	if (!retval)
		errno = EACCES;
	return (retval);
}
#endif



/* PASV mod */
static void
SendPasv (int s, struct sockaddr_in *sock)
{
	/* Send PORT a,a,a,a,p,p message */
	usprintf (s, pasvmodestr,
		hibyte (hiword (sock->sin_addr.s_addr)),
		lobyte (hiword (sock->sin_addr.s_addr)),
		hibyte (loword (sock->sin_addr.s_addr)),
		lobyte (loword (sock->sin_addr.s_addr)),
		hibyte (sock->sin_port),
		lobyte (sock->sin_port));
}
