Skip to content
Snippets Groups Projects
ftpsrvr.c 156 KiB
Newer Older
	if (file->from == NULL || stricmp(file->from, user->alias) != 0)
		return FALSE;
	return TRUE;
}

static BOOL can_delete(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
	if (user->rest&FLAG('D'))
		return FALSE;
	if (!chk_ar(&scfg,lib->ar,user,client))
		return FALSE;
	if (!chk_ar(&scfg,dir->ar,user,client))
		return FALSE;
	if (!dir_op(&scfg, user, client, dir->dirnum))
		return FALSE;
	if (!(user->exempt&FLAG('R')))
		return FALSE;
	return TRUE;
}

static BOOL can_download(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
	return can_user_download(&scfg, dir->dirnum, user, client,  /* reason */NULL);
}

static void get_fileperm(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file, char *permstr)
{
	char *p = permstr;

	if (can_append(lib, dir, user, client, file))
		*(p++) = 'a';	// File may be appended to
	//*(p++) = 'c';	// Files may be created in dir
	if (can_delete(lib, dir, user, client, file))
		*(p++) = 'd';	// Item may be depeted (dir or file)
	//*(p++) = 'e';	// Can change to the dir
	//*(p++) = 'f';	// Item may be renamed
	//*(p++) = 'l';	// Directory contents can be listed
	//*(p++) = 'm';	// New subdirectories may be created
	//*(p++) = 'p';	// Files/Dirs in directory may be deleted
	if (can_download(lib, dir, user, client, file))
		*(p++) = 'r';	// File may be retrieved
	//*(p++) = 'w';	// File may be overwritten
	*p = 0;
}

static void get_owner_name(file_t *file, char *namestr)
{
	char *p;

	if (file) {
		if (file->hdr.attr & MSG_ANONYMOUS)
			strcpy(namestr, ANONYMOUS);
		else
			strcpy(namestr, file->from);
	}
	else
		strcpy(namestr, scfg.sys_id);

	// Now ensure it's an RCHAR string.
	for (p=namestr; *p; p++) {
		if (*p >= '!' && *p <= ')')
			continue;
		else if (*p >= '+' && *p <= ':')
			continue;
		else if (*p >= '?' && *p <= 'Z')
			continue;
		else if (*p == '\\')
			continue;
		else if (*p == '^')
			continue;
		else if (*p == '_')
			continue;
		else if (*p >= 'a' && *p <= 'z')
			continue;
		else if (*p == ' ')
			*p = '.';
		else
			*p = '_';
	}
}

static void ctrl_thread(void* arg)
{
	unsigned	mlsx_feats = (MLSX_TYPE | MLSX_PERM | MLSX_SIZE | MLSX_MODIFY | MLSX_OWNER | MLSX_UNIQUE | MLSX_CREATE);
	char		buf[512];
	char		str[128];
	char*		cmd;
	char*		p;
	char*		np;
	char*		tp;
	char		password[64];
	char		fname[MAX_PATH+1];
	char		qwkfile[MAX_PATH+1];
	char		aliasfile[MAX_PATH+1];
	char		mls_path[MAX_PATH+1];
	char		*mls_fname;
	char		permstr[11];
	char		aliasline[512];
	char		desc[501]="";
	char		sys_pass[128];
deuce's avatar
deuce committed
	char		host_name[256];
	char		host_ip[INET6_ADDRSTRLEN];
	char		data_ip[INET6_ADDRSTRLEN];
	uint16_t	data_port;
	char		path[MAX_PATH+1];
	char		local_dir[MAX_PATH+1];
	char		ren_from[MAX_PATH+1]="";
	uint32_t	ip_addr;
	socklen_t	addr_len;
	u_short		p1,p2;	/* For PORT command */
	int			i;
	int			rd;
	int			result;
	int			lib;
	int			dir;
	int			curlib=-1;
	int			curdir=-1;
	int			orglib;
	int			orgdir;
	long		filepos=0L;
	long		timeleft;
	ulong		l;
	ulong		login_attempts=0;
	ulong		avail;	/* disk space */
	BOOL		detail;
	BOOL		success;
	BOOL		getdate;
	BOOL		getsize;
	BOOL		delfile;
	BOOL		tmpfile;
	BOOL		credits;
	BOOL		transfer_inprogress;
	BOOL		transfer_aborted;
	BOOL		sysop=FALSE;
	BOOL		local_fsys=FALSE;
	BOOL		alias_dir;
	FILE*		fp;
	FILE*		alias_fp;
	SOCKET		sock;
	SOCKET		pasv_sock=INVALID_SOCKET;
deuce's avatar
deuce committed
	CRYPT_SESSION	pasv_sess=-1;
	SOCKET		data_sock=INVALID_SOCKET;
deuce's avatar
deuce committed
	CRYPT_SESSION	data_sess=-1;
	HOSTENT*	host;
deuce's avatar
deuce committed
	union xp_sockaddr	addr;
	union xp_sockaddr	data_addr;
	union xp_sockaddr	pasv_addr;
	ftp_t		ftp=*(ftp_t*)arg;
	user_t		user;
	time_t		t;
	time_t		now;
	time_t		lastactive;
	node_t		node;
	client_t	client;
	struct tm	tm;
	struct tm 	cur_tm;
	login_attempt_t attempted;
deuce's avatar
deuce committed
	CRYPT_SESSION	sess = -1;
	BOOL		got_pbsz = FALSE;
	BOOL		protection = FALSE;
	SetThreadName("sbbs/ftpControl");

	lastactive=time(NULL);

	sock=ftp.socket;
deuce's avatar
deuce committed
	memcpy(&data_addr, &ftp.client_addr, ftp.client_addr_len);
	/* Default data port is ctrl port-1 */
deuce's avatar
deuce committed
	data_port = inet_addrport(&data_addr)-1;
	lprintf(LOG_DEBUG,"%04d CTRL thread started", sock);
rswindell's avatar
rswindell committed
#ifdef _WIN32
	if(startup->sound.answer[0] && !sound_muted(&scfg)) 
		PlaySound(startup->sound.answer, NULL, SND_ASYNC|SND_FILENAME);
rswindell's avatar
rswindell committed
#endif
	l=1;

	if((i=ioctlsocket(sock, FIONBIO, &l))!=0) {
		lprintf(LOG_ERR,"%04d !ERROR %d (%d) disabling socket blocking"
			,sock, i, ERROR_VALUE);
deuce's avatar
deuce committed
		sockprintf(sock,sess,"425 Error %d disabling socket blocking"
			,ERROR_VALUE);
deuce's avatar
deuce committed
		ftp_close_socket(&sock,&sess,__LINE__);
		thread_down();
		return;
	}

	memset(&user,0,sizeof(user));

deuce's avatar
deuce committed
	inet_addrtop(&ftp.client_addr, host_ip, sizeof(host_ip));
	lprintf(LOG_INFO,"%04d CTRL connection accepted from: %s port %u"
deuce's avatar
deuce committed
		,sock, host_ip, inet_addrport(&ftp.client_addr));
	SAFECOPY(host_name, STR_NO_HOSTNAME);
	if(!(startup->options&FTP_OPT_NO_HOST_LOOKUP)) {
		getnameinfo(&ftp.client_addr.addr, sizeof(ftp.client_addr), host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD);
		lprintf(LOG_INFO,"%04d Hostname: %s [%s]", sock, host_name, host_ip);
	ulong banned = loginBanned(&scfg, startup->login_attempt_list, sock, host_name, startup->login_attempt, &attempted);
	if(banned || trashcan(&scfg,host_ip,"ip")) {
		if(banned) {
			char ban_duration[128];
			lprintf(LOG_NOTICE, "%04d !TEMPORARY BAN of %s (%lu login attempts, last: %s) - remaining: %s"
				,sock, host_ip, attempted.count-attempted.dupes, attempted.user, seconds_to_str(banned, ban_duration));
		} else
			lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", sock, host_ip);
deuce's avatar
deuce committed
		sockprintf(sock,sess,"550 Access denied.");
		ftp_close_socket(&sock,&sess,__LINE__);
	if(trashcan(&scfg,host_name,"host")) {
		lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", sock, host_name);
deuce's avatar
deuce committed
		sockprintf(sock,sess,"550 Access denied.");
		ftp_close_socket(&sock,&sess,__LINE__);
		thread_down();
		return;
	}

	/* For PASV mode */
	addr_len=sizeof(pasv_addr);
deuce's avatar
deuce committed
	if((result=getsockname(sock, &pasv_addr.addr,&addr_len))!=0) {
		lprintf(LOG_ERR,"%04d !ERROR %d (%d) getting address/port", sock, result, ERROR_VALUE);
deuce's avatar
deuce committed
		sockprintf(sock,sess,"425 Error %d getting address/port",ERROR_VALUE);
		ftp_close_socket(&sock,&sess,__LINE__);
	(void)protected_uint32_adjust(&active_clients, 1);

	/* Initialize client display */
	client.size=sizeof(client);
	SAFECOPY(client.addr,host_ip);
	SAFECOPY(client.host,host_name);
deuce's avatar
deuce committed
	client.port=inet_addrport(&ftp.client_addr);
	client.protocol="FTP";
	SAFECOPY(client.user, STR_UNKNOWN_USER);
	client_on(sock,&client,FALSE /* update */);
rswindell's avatar
rswindell committed
	if(startup->login_attempt.throttle
		&& (login_attempts=loginAttempts(startup->login_attempt_list, &ftp.client_addr)) > 1) {
		lprintf(LOG_DEBUG,"%04d Throttling suspicious connection from: %s (%lu login attempts)"
deuce's avatar
deuce committed
			,sock, host_ip, login_attempts);
rswindell's avatar
rswindell committed
		mswait(login_attempts*startup->login_attempt.throttle);
	sockprintf(sock,sess,"220-%s (%s)",scfg.sys_name, server_host_name());
	sockprintf(sock,sess," Synchronet FTP Server %s%c-%s Ready"
		,VERSION, REVISION, PLATFORM_DESC);
	if((fp=fopen(str,"rb"))!=NULL) {
		while(!feof(fp)) {
			if(!fgets(buf,sizeof(buf),fp))
				break;
			truncsp(buf);
deuce's avatar
deuce committed
			sockprintf(sock,sess," %s",buf);
deuce's avatar
deuce committed
	sockprintf(sock,sess,"220 Please enter your user name.");
#ifdef SOCKET_DEBUG_CTRL
	socket_debug[sock]|=SOCKET_DEBUG_CTRL;
#ifdef SOCKET_DEBUG_READLINE
		socket_debug[sock]|=SOCKET_DEBUG_READLINE;
deuce's avatar
deuce committed
		rd = sockreadline(sock, sess, buf, sizeof(buf), &lastactive);
#ifdef SOCKET_DEBUG_READLINE
		socket_debug[sock]&=~SOCKET_DEBUG_READLINE;
				lprintf(LOG_WARNING,"%04d <%s> !Aborting transfer due to CTRL socket receive error", sock, user.number ? user.alias : host_ip);
				transfer_aborted=TRUE;
		lastactive=time(NULL);
		cmd=buf;
		while(((BYTE)*cmd)==TELNET_IAC) {
			cmd++;
			lprintf(LOG_DEBUG,"%04d <%s> RX%s: Telnet cmd: %s", sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", telnet_cmd_desc(*cmd));
			cmd++;
		}
		while(*cmd && *cmd<' ') {
			lprintf(LOG_DEBUG,"%04d <%s> RX%s: %d (0x%02X)",sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", (BYTE)*cmd,(BYTE)*cmd);
			cmd++;
		}
		if(!(*cmd))
			continue;
		if(startup->options&FTP_OPT_DEBUG_RX)
			lprintf(LOG_DEBUG,"%04d <%s> RX%s: %s", sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", cmd);
		if(!stricmp(cmd, "NOOP")) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"200 NOOP command successful.");
			continue;
		}
		if(!stricmp(cmd, "HELP SITE") || !stricmp(cmd, "SITE HELP")) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"214-The following SITE commands are recognized (* => unimplemented):");
			sockprintf(sock,sess," HELP    VER     WHO     UPTIME");
			if(user.level>=SYSOP_LEVEL)
deuce's avatar
deuce committed
				sockprintf(sock,sess,
deuce's avatar
deuce committed
				sockprintf(sock,sess,
deuce's avatar
deuce committed
			sockprintf(sock,sess,"214 Direct comments to sysop@%s.",scfg.sys_inetaddr);
			continue;
		}
		if(!strnicmp(cmd, "HELP",4)) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"214-The following commands are recognized (* => unimplemented, # => extension):");
			sockprintf(sock,sess," USER    PASS    CWD     XCWD    CDUP    XCUP    PWD     XPWD");
			sockprintf(sock,sess," QUIT    REIN    PORT    PASV    LIST    NLST    NOOP    HELP");
			sockprintf(sock,sess," SIZE    MDTM    RETR    STOR    REST    ALLO    ABOR    SYST");
			sockprintf(sock,sess," TYPE    STRU    MODE    SITE    RNFR*   RNTO*   DELE*   DESC#");
			sockprintf(sock,sess," FEAT#   OPTS#   EPRT    EPSV    AUTH#   PBSZ#   PROT#   CCC#");
deuce's avatar
deuce committed
			sockprintf(sock,sess,"214 Direct comments to sysop@%s.",scfg.sys_inetaddr);
			continue;
		}
		if(!stricmp(cmd, "FEAT")) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211-The following additional (post-RFC949) features are supported:");
			sockprintf(sock,sess," DESC");
			sockprintf(sock,sess," MDTM");
			sockprintf(sock,sess," SIZE");
			sockprintf(sock,sess," REST STREAM");
			sockprintf(sock,sess," AUTH TLS");
			sockprintf(sock,sess," PBSZ");
			sockprintf(sock,sess," PROT");
			sockprintf(sock,sess," MLST Type%s;Perm%s;Size%s;Modify%s;UNIX.ownername%s;Unique%s;Create%s",
				(mlsx_feats & MLSX_TYPE) ? "*" : "",
				(mlsx_feats & MLSX_PERM) ? "*" : "",
				(mlsx_feats & MLSX_SIZE) ? "*" : "",
				(mlsx_feats & MLSX_MODIFY) ? "*" : "",
				(mlsx_feats & MLSX_OWNER) ? "*" : "",
				(mlsx_feats & MLSX_UNIQUE) ? "*" : "",
				(mlsx_feats & MLSX_CREATE) ? "*" : ""
			sockprintf(sock,sess," TVFS");
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211 End");
		if(!strnicmp(cmd, "OPTS MLST",9)) {
			if (cmd[9] == 0) {
				mlsx_feats = 0;
				continue;
			}
			if (cmd[9] != ' ') {
				sockprintf(sock,sess,"501 Option not supported.");
				continue;
			}
			mlsx_feats = 0;
			for (p = cmd; *p; p++)
				*p = toupper(*p);
			if (strstr(cmd, "TYPE;"))
				mlsx_feats |= MLSX_TYPE;
			if (strstr(cmd, "PERM;"))
				mlsx_feats |= MLSX_PERM;
			if (strstr(cmd, "SIZE;"))
				mlsx_feats |= MLSX_SIZE;
			if (strstr(cmd, "MODIFY;"))
				mlsx_feats |= MLSX_MODIFY;
			if (strstr(cmd, "UNIX.OWNERNAME;"))
				mlsx_feats |= MLSX_OWNER;
				mlsx_feats |= MLSX_UNIQUE;
			if (strstr(cmd, "CREATE;"))
				mlsx_feats |= MLSX_CREATE;
			sockprintf(sock,sess,"200 %s%s%s%s%s%s%s",
				(mlsx_feats & MLSX_TYPE) ? "Type;" : "",
				(mlsx_feats & MLSX_PERM) ? "Perm;" : "",
				(mlsx_feats & MLSX_SIZE) ? "Size;" : "",
				(mlsx_feats & MLSX_MODIFY) ? "Modify;" : "",
				(mlsx_feats & MLSX_OWNER) ? "UNIX.ownername;" : "",
				(mlsx_feats & MLSX_UNIQUE) ? "Unique;" : "",
				(mlsx_feats & MLSX_CREATE) ? "Create;" : ""
		if(!strnicmp(cmd, "OPTS",4)) {
			sockprintf(sock,sess,"501 Option not supported.");
			continue;
		}
		if(!stricmp(cmd, "QUIT")) {
deuce's avatar
deuce committed
			ftp_printfile(sock,sess,"bye",221);
			sockprintf(sock,sess,"221 Goodbye. Closing control connection.");
			break;
		}
		if(!strnicmp(cmd, "USER ",5)) {
			sysop=FALSE;
			user.number=0;
			p=cmd+5;
			SAFECOPY(user.alias,p);
			user.number = find_login_id(&scfg, user.alias);
			if(!user.number && (stricmp(user.alias,"anonymous") == 0 || stricmp(user.alias, "ftp") == 0))
				user.number=matchuser(&scfg,"guest",FALSE);
			if(user.number && getuserdat(&scfg, &user)==0 && user.pass[0]==0) 
deuce's avatar
deuce committed
				sockprintf(sock,sess,"331 User name okay, give your full e-mail address as password.");
deuce's avatar
deuce committed
				sockprintf(sock,sess,"331 User name okay, need password.");
			user.number=0;
			continue;
		}
		if(!strnicmp(cmd, "PASS ",5) && user.alias[0]) {
			user.number=0;
			p=cmd+5;
			SAFECOPY(password,p);
			user.number = find_login_id(&scfg, user.alias);
			if(!user.number) {
				if(scfg.sys_misc&SM_ECHO_PW)
					lprintf(LOG_WARNING,"%04d !UNKNOWN USER: '%s' (password: %s)",sock,user.alias,p);
					lprintf(LOG_WARNING,"%04d !UNKNOWN USER: '%s'",sock,user.alias);
deuce's avatar
deuce committed
				if(badlogin(sock, sess, &login_attempts, user.alias, p, host_name, &ftp.client_addr))
				continue;
			}
			if((i=getuserdat(&scfg, &user))!=0) {
				lprintf(LOG_ERR,"%04d <%s> !ERROR %d getting data for user #%d"
					,sock, user.alias, i, user.number);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"530 Database error %d",i);
				user.number=0;
				continue;
			}
			if(user.misc&(DELETED|INACTIVE)) {
				lprintf(LOG_WARNING,"%04d <%s> !DELETED or INACTIVE user #%d"
					,sock,user.alias,user.number);
				user.number=0;
deuce's avatar
deuce committed
				if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
				continue;
			}
			if(user.rest&FLAG('T')) {
				lprintf(LOG_WARNING,"%04d <%s> !T RESTRICTED user #%d"
					,sock,user.alias,user.number);
				user.number=0;
deuce's avatar
deuce committed
				if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
			if(user.ltoday>=scfg.level_callsperday[user.level]
				&& !(user.exempt&FLAG('L'))) {
				lprintf(LOG_WARNING,"%04d <%s> !MAXIMUM LOGONS (%d) reached for level %u"
					,sock,user.alias,scfg.level_callsperday[user.level], user.level);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"530 Maximum logons per day reached.");
				user.number=0;
				continue;
			}
			if(user.rest&FLAG('L') && user.ltoday>=1) {
				lprintf(LOG_WARNING,"%04d <%s> !L RESTRICTED user already on today"
					,sock,user.alias);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"530 Maximum logons per day reached.");
			SAFEPRINTF2(sys_pass,"%s:%s",user.pass,scfg.sys_pass);
			if(!user.pass[0]) {	/* Guest/Anonymous */
				if(trashcan(&scfg,password,"email")) {
					lprintf(LOG_NOTICE,"%04d <%s> !BLOCKED e-mail address: %s", sock, user.alias, password);
deuce's avatar
deuce committed
					if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
				lprintf(LOG_INFO,"%04d <%s> identity: %s",sock,user.alias,password);
				putuserstr(&scfg, user.number, USER_NETMAIL, password);
			}
			else if(user.level>=SYSOP_LEVEL && !stricmp(password,sys_pass)) {
				if(scfg.sys_misc&SM_R_SYSOP) {
					lprintf(LOG_INFO,"%04d <%s> Sysop access granted", sock, user.alias);
					sysop=TRUE;
				} else
					lprintf(LOG_NOTICE, "%04d <%s> Remote sysop access disabled", sock, user.alias);
			}
			else if(stricmp(password,user.pass)) {
					lprintf(LOG_WARNING,"%04d <%s> !FAILED Password attempt: '%s' expected '%s'"
						,sock, user.alias, password, user.pass);
				else
					lprintf(LOG_WARNING,"%04d <%s> !FAILED Password attempt"
				user.number=0;
deuce's avatar
deuce committed
				if(badlogin(sock, sess, &login_attempts, user.alias, password, host_name, &ftp.client_addr))
				continue;
			}

			/* Update client display */
				SAFECOPY(client.user, user.alias);
				loginSuccess(startup->login_attempt_list, &ftp.client_addr);
				SAFEPRINTF2(client.user, "%s <%.32s>", user.alias, password);
			client_on(sock,&client,TRUE /* update */);
			lprintf(LOG_INFO,"%04d <%s> logged in (%u today, %u total)"
				,sock,user.alias,user.ltoday+1, user.logons+1);
			logintime=time(NULL);
			timeleft=(long)gettimeleft(&scfg,&user,logintime);
deuce's avatar
deuce committed
			ftp_printfile(sock,sess,"hello",230);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"230-Sysop access granted.");
			sockprintf(sock,sess,"230-%s logged in.",user.alias);
			if(!(user.exempt&FLAG('D')) && (user.cdt+user.freecdt)>0)
deuce's avatar
deuce committed
				sockprintf(sock,sess,"230-You have %lu download credits."
					,user.cdt+user.freecdt);
deuce's avatar
deuce committed
			sockprintf(sock,sess,"230 You are allowed %lu minutes of use for this session."
				,timeleft/60);
			sprintf(qwkfile,"%sfile/%04d.qwk",scfg.data_dir,user.number);

			/* Adjust User Total Logons/Logons Today */
			user.logons++;
			user.ltoday++;
			SAFECOPY(user.modem,"FTP");
			SAFECOPY(user.comp,host_name);
deuce's avatar
deuce committed
			SAFECOPY(user.ipaddr,host_ip);
			user.logontime=(time32_t)logintime;
			if(startup->sound.login[0] && !sound_muted(&scfg)) 
				PlaySound(startup->sound.login, NULL, SND_ASYNC|SND_FILENAME);
#endif
deuce's avatar
deuce committed
		if (!strnicmp(cmd, "AUTH ", 5)) {
			if(!stricmp(cmd, "AUTH TLS")) {
				if (sess != -1) {
					sockprintf(sock,sess,"534 Already in TLS mode");
					continue;
				}
				if (start_tls(&sock, &sess, TRUE) || sess == -1) {
					lprintf(LOG_WARNING, "%04d <%s> failed to initialize TLS successfully", sock, host_ip);
deuce's avatar
deuce committed
					break;
deuce's avatar
deuce committed
				user.number=0;
				sysop=FALSE;
				filepos=0;
				got_pbsz = FALSE;
				protection = FALSE;
				lprintf(LOG_INFO, "%04d <%s> initialized TLS successfully", sock, host_ip);
				client.protocol = "FTPS";
				client_on(sock, &client, /* update: */TRUE);
deuce's avatar
deuce committed
				continue;
			}
			sockprintf(sock,sess,"504 TLS is the only AUTH supported");
			continue;
		}
		if (!strnicmp(cmd, "PBSZ ", 5)) {
			if(!stricmp(cmd, "PBSZ 0") && sess != -1) {
				got_pbsz = TRUE;
				sockprintf(sock,sess,"200 OK");
				continue;
			}
			if (sess == -1) {
				sockprintf(sock,sess,"503 Need AUTH TLS first");
				continue;
			}
			if (strspn(cmd+5, "0123456789") == strlen(cmd+5)) {
				sockprintf(sock,sess,"200 PBSZ=0");
				continue;
			}
			sockprintf(sock,sess,"501 Unable to parse buffer size");
			continue;
		}
		if (!strnicmp(cmd, "PROT ", 5)) {
			if (sess == -1) {
				sockprintf(sock,sess,"503 No AUTH yet");
				continue;
			}
			if(!strnicmp(cmd, "PROT P",6) && sess != -1 && got_pbsz) {
				protection = TRUE;
				sockprintf(sock,sess,"200 Accepted");
				continue;
			}
			if(!strnicmp(cmd, "PROT C",6) && sess != -1 && got_pbsz) {
				protection = FALSE;
				sockprintf(sock,sess,"200 Accepted");
				continue;
			}
			sockprintf(sock,sess,"536 Only C and P are supported in TLS mode");
			continue;
		}
		if(!stricmp(cmd, "CCC")) {
			if (sess == -1) {
				sockprintf(sock,sess,"533 Not in TLS mode");
				continue;
			}
			sockprintf(sock,sess,"200 Accepted");
			cryptDestroySession(sess);
			sess = -1;
			continue;
		}
deuce's avatar
deuce committed
			sockprintf(sock,sess,"530 Please login with USER and PASS.");
		if(!(user.rest&FLAG('G')))
			getuserdat(&scfg, &user);	/* get current user data */
		if((timeleft=(long)gettimeleft(&scfg,&user,logintime))<1L) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"421 Sorry, you've run out of time.");
			lprintf(LOG_WARNING,"%04d <%s> Out of time, disconnecting",sock, user.alias);
			break;
		}

		/********************************/
		/* These commands require login */
		/********************************/

		if(!stricmp(cmd, "REIN")) {
			lprintf(LOG_INFO,"%04d <%s> reinitialized control session",sock,user.alias);
			user.number=0;
			sysop=FALSE;
			filepos=0;
deuce's avatar
deuce committed
			sockprintf(sock,sess,"220 Control session re-initialized. Ready for re-login.");
			if (sess != -1) {
				cryptDestroySession(sess);
				sess = -1;
			}
			got_pbsz = FALSE;
			protection = FALSE;
			continue;
		}

		if(!stricmp(cmd, "SITE WHO")) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211-Active Telnet Nodes:");
			for(i=0;i<scfg.sys_nodes && i<scfg.sys_lastnode;i++) {
				if((result=getnodedat(&scfg, i+1, &node, FALSE, NULL))!=0) {
deuce's avatar
deuce committed
					sockprintf(sock,sess," Error %d getting data for Telnet Node %d",result,i+1);
					continue;
				}
				if(node.status==NODE_INUSE)
deuce's avatar
deuce committed
					sockprintf(sock,sess," Node %3d: %s",i+1, username(&scfg,node.useron,str));
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211 End (%d active FTP clients)", protected_uint32_value(active_clients));
			continue;
		}
		if(!stricmp(cmd, "SITE VER")) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211 %s",ftp_ver());
		if(!stricmp(cmd, "SITE UPTIME")) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211 %s (%lu served)",sectostr((uint)(time(NULL)-uptime),str),served);
			continue;
		}
		if(!stricmp(cmd, "SITE RECYCLE") && user.level>=SYSOP_LEVEL) {
			startup->recycle_now=TRUE;
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211 server will recycle when not in-use");
			continue;
		}
		if(!stricmp(cmd, "SITE RECYCLE ALL") && user.level>=SYSOP_LEVEL) {
			refresh_cfg(&scfg);
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211 ALL servers/nodes will recycle when not in-use");
		if(!strnicmp(cmd,"SITE EXEC ",10) && sysop) {
			p=cmd+10;
#ifdef __unix__
			fp=popen(p,"r");
			if(fp==NULL)
deuce's avatar
deuce committed
				sockprintf(sock,sess,"500 Error %d opening pipe to: %s",errno,p);
			else {
				while(!feof(fp)) {
					if(fgets(str,sizeof(str),fp)==NULL)
						break;
deuce's avatar
deuce committed
					sockprintf(sock,sess,"200-%s",str);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"200 %s returned %d",p,pclose(fp));
deuce's avatar
deuce committed
			sockprintf(sock,sess,"200 system(%s) returned %d",p,system(p));
#ifdef SOCKET_DEBUG_CTRL
		if(!stricmp(cmd, "SITE DEBUG")) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"211-Debug");
			for(i=0;i<sizeof(socket_debug);i++) 
				if(socket_debug[i]!=0)
deuce's avatar
deuce committed
					sockprintf(sock,sess,"211-socket %d = 0x%X",i,socket_debug[i]);
			sockprintf(sock,sess,"211 End");
deuce's avatar
deuce committed
		if(strnicmp(cmd, "PORT ",5)==0 || strnicmp(cmd, "EPRT ",5)==0 || strnicmp(cmd, "LPRT ",5)==0) {
deuce's avatar
deuce committed
			if(pasv_sock!=INVALID_SOCKET)  {
				ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
			}
			memcpy(&data_addr, &ftp.client_addr, ftp.client_addr_len);
			if(strnicmp(cmd, "PORT ",5)==0) {
				sscanf(p,"%u,%u,%u,%u,%hd,%hd",&h1,&h2,&h3,&h4,&p1,&p2);
deuce's avatar
deuce committed
				data_addr.in.sin_family=AF_INET;
				data_addr.in.sin_addr.s_addr=htonl((h1<<24)|(h2<<16)|(h3<<8)|h4);
				data_port = (p1<<8)|p2;
deuce's avatar
deuce committed
			} else if(strnicmp(cmd, "EPRT ", 5)==0) { /* EPRT */
				char	delim = *p;
				int		prot;
deuce's avatar
deuce committed
				char	addr_str[INET6_ADDRSTRLEN];
deuce's avatar
deuce committed
				memset(&data_addr, 0, sizeof(data_addr));
				if(*p)
					p++;
				prot=strtol(p,NULL,/* base: */10);
deuce's avatar
deuce committed
				switch(prot) {
					case 1:
						FIND_CHAR(p,delim);
						if(*p)
							p++;
deuce's avatar
deuce committed
						FIND_CHAR(p,delim);
						old_char = *p;
						*p = 0;
						data_addr.in.sin_addr.s_addr=inet_addr(ap);
						*p = old_char;
						if (*p)
deuce's avatar
deuce committed
							p++;
						data_port=atoi(p);
						data_addr.in.sin_family=AF_INET;
						break;
					case 2:
						FIND_CHAR(p,delim);
						if(*p)
							p++;
						strncpy(addr_str, p, sizeof(addr_str));
						addr_str[sizeof(addr_str)-1]=0;
						tp=addr_str;
						FIND_CHAR(tp, delim);
						*tp=0;
						if(inet_ptoaddr(addr_str, &data_addr, sizeof(data_addr))==NULL) {
							lprintf(LOG_WARNING,"%04d <%s> !Unable to parse IPv6 address: %s",sock, user.alias, addr_str);
deuce's avatar
deuce committed
							sockprintf(sock,sess,"522 Unable to parse IPv6 address (1)");
deuce's avatar
deuce committed
							continue;
						}
						FIND_CHAR(p,delim);
						if(*p)
							p++;
						data_port=atoi(p);
						data_addr.in6.sin6_family=AF_INET6;
						break;
					default:
						lprintf(LOG_WARNING,"%04d <%s> !UNSUPPORTED protocol: %d", sock, user.alias, prot);
deuce's avatar
deuce committed
						sockprintf(sock,sess,"522 Network protocol not supported, use (1)");
deuce's avatar
deuce committed
						continue;
				}
			}
			else {	/* LPRT */
				if(sscanf(p,"%u,%u",&h1, &h2)!=2) {
					lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, p);
deuce's avatar
deuce committed
					sockprintf(sock,sess, "521 Address family not supported");
deuce's avatar
deuce committed
				FIND_CHAR(p,',');
				if(*p)
					p++;
				FIND_CHAR(p,',');
				if(*p)
					p++;
				switch(h1) {
					case 4:	/* IPv4 */
						if(h2 != 4) {
							lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, p);
deuce's avatar
deuce committed
							sockprintf(sock,sess, "501 IPv4 Address is the wrong length");
deuce's avatar
deuce committed
							continue;
						}
						for(h1 = 0; h1 < h2; h1++) {
							((unsigned char *)(&data_addr.in.sin_addr))[h1]=atoi(p);
							FIND_CHAR(p,',');
							if(*p)
								p++;
						}
						if(atoi(p)!=2) {
							lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT %s", sock, user.alias, p);
deuce's avatar
deuce committed
							sockprintf(sock,sess, "501 IPv4 Port is the wrong length");
deuce's avatar
deuce committed
							continue;
						}
						FIND_CHAR(p,',');
						if(*p)
							p++;
						for(h1 = 0; h1 < 2; h1++) {
							((unsigned char *)(&data_port))[1-h1]=atoi(p);
							FIND_CHAR(p,',');
							if(*p)
								p++;
						}
						data_addr.in.sin_family=AF_INET;
						break;
					case 6:	/* IPv6 */
						if(h2 != 16) {
							lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s",sock, user.alias, p);
deuce's avatar
deuce committed
							sockprintf(sock,sess, "501 IPv6 Address is the wrong length");
deuce's avatar
deuce committed
							continue;
						}
						for(h1 = 0; h1 < h2; h1++) {
							((unsigned char *)(&data_addr.in6.sin6_addr))[h1]=atoi(p);
							FIND_CHAR(p,',');
							if(*p)
								p++;
						}
						if(atoi(p)!=2) {
							lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s",sock, user.alias, p);
deuce's avatar
deuce committed
							sockprintf(sock,sess, "501 IPv6 Port is the wrong length");
deuce's avatar
deuce committed
							continue;
						}
						FIND_CHAR(p,',');
						if(*p)
							p++;
						for(h1 = 0; h1 < 2; h1++) {
							((unsigned char *)(&data_port))[1-h1]=atoi(p);
							FIND_CHAR(p,',');
							if(*p)
								p++;
						}
						data_addr.in6.sin6_family=AF_INET6;
						break;
					default:
						lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s",sock, user.alias, p);
deuce's avatar
deuce committed
						sockprintf(sock,sess, "521 Address family not supported");
deuce's avatar
deuce committed
						continue;
				}
deuce's avatar
deuce committed

			inet_addrtop(&data_addr, data_ip, sizeof(data_ip));
			bool bounce_allowed = (startup->options & FTP_OPT_ALLOW_BOUNCE) && !(user.rest & FLAG('G'));
			if(data_port < IPPORT_RESERVED
				|| (memcmp(&data_addr, &ftp.client_addr, ftp.client_addr_len) != 0 && !bounce_allowed)) {
				lprintf(LOG_WARNING,"%04d <%s> !SUSPECTED BOUNCE ATTACK ATTEMPT to %s port %u"
deuce's avatar
deuce committed
					,data_ip,data_port);
				ftp_hacklog("FTP BOUNCE", user.alias, cmd, host_name, &ftp.client_addr);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"504 Bad port number.");	
				continue; /* As recommended by RFC2577 */
			}
deuce's avatar
deuce committed
			inet_setaddrport(&data_addr, data_port);
deuce's avatar
deuce committed
			sockprintf(sock,sess,"200 PORT Command successful.");
		if(stricmp(cmd, "PASV")==0 || stricmp(cmd, "P@SW")==0	/* Kludge required for SMC Barricade V1.2 */
			|| stricmp(cmd, "EPSV")==0 || strnicmp(cmd, "EPSV ", 5)==0 || stricmp(cmd, "LPSV")==0) {
deuce's avatar
deuce committed
			if(pasv_sock!=INVALID_SOCKET)
				ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
deuce's avatar
deuce committed
			if((pasv_sock=ftp_open_socket(pasv_addr.addr.sa_family, SOCK_STREAM))==INVALID_SOCKET) {
				lprintf(LOG_WARNING,"%04d <%s> !PASV ERROR %d opening socket", sock, user.alias, ERROR_VALUE);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d opening PASV data socket", ERROR_VALUE);
			reuseaddr=FALSE;
			if((result=setsockopt(pasv_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr)))!=0) {
				lprintf(LOG_WARNING,"%04d <%s> !PASV ERROR %d disabling REUSEADDR socket option"
					,sock, user.alias, ERROR_VALUE);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d disabling REUSEADDR socket option", ERROR_VALUE);
			if(startup->options&FTP_OPT_DEBUG_DATA)
				lprintf(LOG_DEBUG,"%04d <%s> PASV DATA socket %d opened",sock, user.alias, pasv_sock);
			for(port=startup->pasv_port_low; port<=startup->pasv_port_high; port++) {
				if(startup->options&FTP_OPT_DEBUG_DATA)
					lprintf(LOG_DEBUG,"%04d <%s> PASV DATA trying to bind socket to port %u"
						,sock, user.alias, port);
deuce's avatar
deuce committed
				inet_setaddrport(&pasv_addr, port);
deuce's avatar
deuce committed
				if((result=bind(pasv_sock, &pasv_addr.addr,xp_sockaddr_len(&pasv_addr)))==0)
			if(result!= 0) {
				lprintf(LOG_ERR,"%04d <%s> !PASV ERROR %d (%d) binding socket to port %u"
					,sock, user.alias, result, ERROR_VALUE, port);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d binding data socket",ERROR_VALUE);
				ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
			if(startup->options&FTP_OPT_DEBUG_DATA)
				lprintf(LOG_DEBUG,"%04d <%s> PASV DATA socket %d bound to port %u",sock, user.alias, pasv_sock, port);

			addr_len=sizeof(addr);
deuce's avatar
deuce committed
			if((result=getsockname(pasv_sock, &addr.addr,&addr_len))!=0) {
				lprintf(LOG_ERR,"%04d <%s> !PASV ERROR %d (%d) getting address/port"
					,sock, user.alias, result, ERROR_VALUE);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d getting address/port",ERROR_VALUE);
				ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
				continue;
			} 

			if((result=listen(pasv_sock, 1))!= 0) {
				lprintf(LOG_ERR,"%04d <%s> !PASV ERROR %d (%d) listening on port %u"
					,sock, user.alias, result, ERROR_VALUE,port);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d listening on data socket",ERROR_VALUE);
				ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
deuce's avatar
deuce committed
			port=inet_addrport(&addr);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"229 Entering Extended Passive Mode (|||%hu|)", port);
deuce's avatar
deuce committed
			else if (stricmp(cmd,"LPSV")==0) {
				switch(addr.addr.sa_family) {
					case AF_INET:
deuce's avatar
deuce committed
						sockprintf(sock,sess, "228 Entering Long Passive Mode (4, 4, %d, %d, %d, %d, 2, %d, %d)"
deuce's avatar
deuce committed
							,((unsigned char *)&(addr.in.sin_addr))[0]
							,((unsigned char *)&(addr.in.sin_addr))[1]
							,((unsigned char *)&(addr.in.sin_addr))[2]
							,((unsigned char *)&(addr.in.sin_addr))[3]
							,((unsigned char *)&(addr.in.sin_port))[0]
							,((unsigned char *)&(addr.in.sin_port))[1]);
						break;
					case AF_INET6:
deuce's avatar
deuce committed
						sockprintf(sock,sess, "228 Entering Long Passive Mode (6, 16, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, 2, %d, %d)"
deuce's avatar
deuce committed
							,((unsigned char *)&(addr.in6.sin6_addr))[0]
							,((unsigned char *)&(addr.in6.sin6_addr))[1]
							,((unsigned char *)&(addr.in6.sin6_addr))[2]
							,((unsigned char *)&(addr.in6.sin6_addr))[3]
							,((unsigned char *)&(addr.in6.sin6_addr))[4]
							,((unsigned char *)&(addr.in6.sin6_addr))[5]
							,((unsigned char *)&(addr.in6.sin6_addr))[6]
							,((unsigned char *)&(addr.in6.sin6_addr))[7]
							,((unsigned char *)&(addr.in6.sin6_addr))[8]
							,((unsigned char *)&(addr.in6.sin6_addr))[9]
							,((unsigned char *)&(addr.in6.sin6_addr))[10]
							,((unsigned char *)&(addr.in6.sin6_addr))[11]
							,((unsigned char *)&(addr.in6.sin6_addr))[12]
							,((unsigned char *)&(addr.in6.sin6_addr))[13]
							,((unsigned char *)&(addr.in6.sin6_addr))[14]
							,((unsigned char *)&(addr.in6.sin6_addr))[15]
							,((unsigned char *)&(addr.in6.sin6_port))[0]
							,((unsigned char *)&(addr.in6.sin6_port))[1]);
						break;
				}
			}
			else {
				/* Choose IP address to use in passive response */
				ip_addr=0;
				/* TODO: IPv6 this here lookup */
				if(startup->options&FTP_OPT_LOOKUP_PASV_IP
					&& (host=gethostbyname(server_host_name()))!=NULL) 
deuce's avatar
deuce committed
					ip_addr=ntohl(*((ulong*)host->h_addr_list[0]));