Skip to content
Snippets Groups Projects
ftpsrvr.c 156 KiB
Newer Older
/* Synchronet FTP server */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

rswindell's avatar
rswindell committed
/* ANSI C Library headers */
#include <stdio.h>
#include <stdlib.h>			/* ltoa in GNU C lib */
#include <stdarg.h>			/* va_list, varargs */
#include <string.h>			/* strrchr */
#include <fcntl.h>			/* O_WRONLY, O_RDONLY, etc. */
#include <errno.h>			/* EACCES */
#include <ctype.h>			/* toupper */
#include <sys/types.h>
#include <sys/stat.h>

/* Synchronet-specific headers */
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "text.h"			/* TOTAL_TEXT */
rswindell's avatar
rswindell committed
#include "ftpsrvr.h"
#include "telnet.h"
deuce's avatar
deuce committed
#include "multisock.h"
deuce's avatar
deuce committed
#include "ssl.h"
#include "cryptlib.h"
#include "xpprintf.h"		// vasprintf
#include "git_branch.h"
#include "git_hash.h"
#define FTP_SERVER				"Synchronet FTP Server"

#define STATUS_WFC				"Listening"
#define ANONYMOUS				"anonymous"

#define BBS_VIRTUAL_PATH		"bbs:/""/"	/* this is actually bbs:<slash><slash> */
#define LOCAL_FSYS_DIR			"local:"
#define BBS_FSYS_DIR			"bbs:"
#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
#define TIMEOUT_SOCKET_LISTEN	30		/* Seconds */

#define XFER_REPORT_INTERVAL	60		/* Seconds */

#define INDEX_FNAME_LEN			15

#define	NAME_LEN				15		/* User name length for listings */

#define MLSX_TYPE	(1<<0)
#define MLSX_PERM	(1<<1)
#define MLSX_SIZE	(1<<2)
#define MLSX_MODIFY	(1<<3)
#define MLSX_OWNER	(1<<4)
#define MLSX_UNIQUE	(1<<5)
#define MLSX_CREATE	(1<<6)
static ftp_startup_t*	startup=NULL;
static scfg_t	scfg;
deuce's avatar
deuce committed
static struct xpms_set *ftp_set = NULL;
static protected_uint32_t active_clients;
static protected_uint32_t thread_count;
static volatile time_t	uptime=0;
static volatile ulong	served=0;
static volatile BOOL	terminate_server=FALSE;
static char 	*text[TOTAL_TEXT];
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
static link_list_t current_connections;
	static BYTE 	socket_debug[0x10000]={0};
	#define	SOCKET_DEBUG_CTRL		(1<<0)	/* 0x01 */
	#define SOCKET_DEBUG_SEND		(1<<1)	/* 0x02 */
	#define SOCKET_DEBUG_READLINE	(1<<2)	/* 0x04 */
	#define SOCKET_DEBUG_ACCEPT		(1<<3)	/* 0x08 */
	#define SOCKET_DEBUG_SENDTHREAD	(1<<4)	/* 0x10 */
	#define SOCKET_DEBUG_TERMINATE	(1<<5)	/* 0x20 */
	#define SOCKET_DEBUG_RECV_CHAR	(1<<6)	/* 0x40 */
	#define SOCKET_DEBUG_FILEXFER	(1<<7)	/* 0x80 */
char* genvpath(int lib, int dir, char* str);

typedef struct {
	SOCKET			socket;
deuce's avatar
deuce committed
	union xp_sockaddr	client_addr;
	socklen_t		client_addr_len;
static const char *ftp_mon[]={"Jan","Feb","Mar","Apr","May","Jun"
            ,"Jul","Aug","Sep","Oct","Nov","Dec"};

BOOL direxist(char *dir)
{
	if(access(dir,0)==0)
		return(TRUE);
	else
		return(FALSE);
}

rswindell's avatar
rswindell committed
BOOL dir_op(scfg_t* cfg, user_t* user, client_t* client, uint dirnum)
		|| (cfg->dir[dirnum]->op_ar[0] && chk_ar(cfg,cfg->dir[dirnum]->op_ar,user,client)));
#if defined(__GNUC__)	// Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
static int lprintf(int level, const char *fmt, ...)
{
	va_list argptr;
	char sbuf[1024];

    va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
		SAFEPRINTF(errmsg, "ftp  %s", sbuf);
		errorlog(&scfg, level, startup==NULL ? NULL:startup->host_name, errmsg);
		if(startup!=NULL && startup->errormsg!=NULL)
			startup->errormsg(startup->cbdata,level,errmsg);

    if(startup==NULL || startup->lputs==NULL || level > startup->log_level)
		return(0);

#if defined(_WIN32)
	if(IsBadCodePtr((FARPROC)startup->lputs))
		return(0);
#endif

    return startup->lputs(startup->cbdata,level,sbuf);
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription

static BOOL WSAInitialized=FALSE;

static BOOL winsock_startup(void)
{
	int		status;             /* Status Code */

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
#define winsock_startup()	(TRUE)
static char* server_host_name(void)
{
	return startup->host_name[0] ? startup->host_name : scfg.sys_inetaddr;
}

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
	    startup->status(startup->cbdata,str);
static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
		startup->clients(startup->cbdata,protected_uint32_value(active_clients));
static void client_on(SOCKET sock, client_t* client, BOOL update)
	if(!update)
		listAddNodeData(&current_connections, client->addr, strlen(client->addr) + 1, sock, LAST_NODE);
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
}

static void client_off(SOCKET sock)
{
	listRemoveTaggedNode(&current_connections, sock, /* free_data */TRUE);
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
static void thread_up(BOOL setuid)
{
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(startup->cbdata,TRUE, setuid);
static int32_t thread_down(void)
	int32_t count = protected_uint32_adjust_fetch(&thread_count,-1);
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(startup->cbdata,FALSE, FALSE);
deuce's avatar
deuce committed
static void ftp_open_socket_cb(SOCKET sock, void *cbdata)
deuce's avatar
deuce committed
	if(startup!=NULL && startup->socket_open!=NULL)
		startup->socket_open(startup->cbdata,TRUE);
deuce's avatar
deuce committed
	if(set_socket_options(&scfg, sock, "FTP", error, sizeof(error)))
		lprintf(LOG_ERR,"%04d !ERROR %s",sock, error);
}

static void ftp_close_socket_cb(SOCKET sock, void *cbdata)
{
	if(startup!=NULL && startup->socket_open!=NULL)
		startup->socket_open(startup->cbdata,FALSE);
}

static SOCKET ftp_open_socket(int domain, int type)
{
	SOCKET	sock;

	sock=socket(domain, type, IPPROTO_IP);
	if(sock != INVALID_SOCKET)
		ftp_open_socket_cb(sock, NULL);
	return(sock);
}

#ifdef __BORLANDC__
#pragma argsused
#endif
deuce's avatar
deuce committed
static int ftp_close_socket(SOCKET* sock, CRYPT_SESSION *sess, int line)
deuce's avatar
deuce committed
	if (*sess != -1) {
		cryptDestroySession(*sess);
		*sess = -1;
	}

	if((*sock)==INVALID_SOCKET) {
		lprintf(LOG_WARNING,"0000 !INVALID_SOCKET in close_socket from line %u",line);
	shutdown(*sock,SHUT_RDWR);	/* required on Unix */

	result=closesocket(*sock);
	if(startup!=NULL && startup->socket_open!=NULL) 
		startup->socket_open(startup->cbdata,FALSE);
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket from line %u",*sock,ERROR_VALUE,line);
	*sock=INVALID_SOCKET;

	return(result);
}

#define GCES(status, sock, session, estr, action) do {                  \
deuce's avatar
deuce committed
	int GCES_level;                                                     \
	get_crypt_error_string(status, session, &estr, action, &GCES_level);\
		lprintf(GCES_level, "%04d TLS %s", sock, estr);                 \
		free_crypt_attrstr(estr);										\
		estr=NULL;														\
deuce's avatar
deuce committed
	}                                                                   \
} while (0)


#if defined(__GNUC__)	// Catch printf-format errors with sockprintf
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
#endif
deuce's avatar
deuce committed
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...)
	int		result;
	va_list argptr;
	char	sbuf[1024];
deuce's avatar
deuce committed
	char	*estr;

    va_start(argptr,fmt);
    len=vsnprintf(sbuf,maxlen=sizeof(sbuf)-2,fmt,argptr);
    va_end(argptr);

	if(len<0 || len>maxlen) /* format error or output truncated */
	if(startup!=NULL && startup->options&FTP_OPT_DEBUG_TX)
deuce's avatar
deuce committed
		lprintf(LOG_DEBUG,"%04d TX%s: %.*s", sock, sess != -1 ? "S" : "", len, sbuf);
	if(sock==INVALID_SOCKET) {
		lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockprintf");
Deucе's avatar
Deucе committed
	/* Check socket for writability */
	if(!socket_writable(sock, 300000)) {
		lprintf(LOG_WARNING,"%04d !WARNING socket not ready for write" ,sock);
deuce's avatar
deuce committed

	if (sess != -1) {
		int tls_sent;
		int sent = 0;
		while (sent < len) {
			result = cryptPushData(sess, sbuf+sent, len-sent, &tls_sent);
			if (result == CRYPT_OK)
				sent += tls_sent;
			else {
deuce's avatar
deuce committed
				GCES(result, sock, sess, estr, "sending data");
				if (result != CRYPT_ERROR_TIMEOUT)
					return 0;
deuce's avatar
deuce committed
			result = cryptFlushData(sess);
			if (result != CRYPT_OK) {
deuce's avatar
deuce committed
				GCES(result, sock, sess, estr, "flushing data");
deuce's avatar
deuce committed
				return 0;
			}
		}
	}
	else {
		while((result=sendsocket(sock,sbuf,len))!=len) {
			if(result==SOCKET_ERROR) {
				if(ERROR_VALUE==EWOULDBLOCK) {
					YIELD();
					continue;
				}
				if(ERROR_VALUE==ECONNRESET) 
					lprintf(LOG_WARNING,"%04d Connection reset by peer on send",sock);
				else if(ERROR_VALUE==ECONNABORTED)
					lprintf(LOG_WARNING,"%04d Connection aborted by peer on send",sock);
				else
					lprintf(LOG_WARNING,"%04d !ERROR %d sending",sock,ERROR_VALUE);
				return(0);
			}
			lprintf(LOG_WARNING,"%04d !ERROR: short send: %u instead of %u",sock,result,len);
/* Returns the directory index of a virtual lib/dir path (e.g. main/games/filename) */
rswindell's avatar
rswindell committed
int getdir(char* p, user_t* user, client_t* client)
rswindell's avatar
rswindell committed
	uint	dir;
	uint	lib;
	SAFECOPY(path,p);
	p=path;

	if(*p=='/') 
		p++;
	else if(!strncmp(p,"./",2))
		p+=2;

	tp=strchr(p,'/');
	if(tp) *tp=0;
	for(lib=0;lib<scfg.total_libs;lib++) {
rswindell's avatar
rswindell committed
		if(!chk_ar(&scfg,scfg.lib[lib]->ar,user,client))
			continue;
		if(!stricmp(scfg.lib[lib]->sname,p))
			break;
	}
	if(lib>=scfg.total_libs) 
		return(-1);

	if(tp!=NULL)
		p=tp+1;

	tp=strchr(p,'/');
	if(tp) *tp=0;
	for(dir=0;dir<scfg.total_dirs;dir++) {
		if(scfg.dir[dir]->lib!=lib)
			continue;
		if(dir!=scfg.sysop_dir && dir!=scfg.upload_dir 
rswindell's avatar
rswindell committed
			&& !chk_ar(&scfg,scfg.dir[dir]->ar,user,client))
		if(!stricmp(scfg.dir[dir]->code_suffix,p))
			break;
	}
	if(dir>=scfg.total_dirs) 
		return(-1);

	return(dir);
}

void recverror(SOCKET socket, int rd, int line)
		lprintf(LOG_NOTICE,"%04d Socket closed by peer on receive (line %u)"
			,socket, line);
	else if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
		if(ERROR_VALUE==ECONNRESET) 
			lprintf(LOG_NOTICE,"%04d Connection reset by peer on receive (line %u)"
				,socket, line);
		else if(ERROR_VALUE==ECONNABORTED) 
			lprintf(LOG_NOTICE,"%04d Connection aborted by peer on receive (line %u)"
				,socket, line);
			lprintf(LOG_NOTICE,"%04d !ERROR %d receiving on socket (line %u)"
				,socket, ERROR_VALUE, line);
		lprintf(LOG_WARNING,"%04d !ERROR: recv on socket returned unexpected value: %d (line %u)"
			,socket, rd, line);
deuce's avatar
deuce committed
static int sock_recvbyte(SOCKET sock, CRYPT_SESSION sess, char *buf, time_t *lastactive)
deuce's avatar
deuce committed
	int len=0;
	int ret;
	int i;
deuce's avatar
deuce committed
	char *estr;
	BOOL first = TRUE;
deuce's avatar
deuce committed
	if(ftp_set==NULL || terminate_server) {
		sockprintf(sock,sess,"421 Server downed, aborting.");
		lprintf(LOG_WARNING,"%04d Server downed, aborting",sock);
deuce's avatar
deuce committed
	if (sess > -1) {
		/* Try a read with no timeout first. */
deuce's avatar
deuce committed
		if ((ret = cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, 0)) != CRYPT_OK)
			GCES(ret, sock, sess, estr, "setting read timeout");
deuce's avatar
deuce committed
		while (1) {
			ret = cryptPopData(sess, buf, 1, &len);
Deucе's avatar
Deucе committed
			/* Successive reads will be with the full timeout after a socket_readable() */
deuce's avatar
deuce committed
			cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity);
			switch(ret) {
				case CRYPT_OK:
					break;
				case CRYPT_ERROR_TIMEOUT:
					if (!first) {
						GCES(ret, sock, sess, estr, "popping data");
						return -1;
					}
					break;
deuce's avatar
deuce committed
				case CRYPT_ERROR_COMPLETE:
					return 0;
				default:
deuce's avatar
deuce committed
					GCES(ret, sock, sess, estr, "popping data");
deuce's avatar
deuce committed
					if (ret < -1)
						return ret;
					return -2;
			}
			first = FALSE;
deuce's avatar
deuce committed
			if (len)
				return len;
			
			if((time(NULL)-(*lastactive))>startup->max_inactivity) {
				lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock);
				sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)."
					,startup->max_inactivity);
				return(0);
			}
Deucе's avatar
Deucе committed
			if (!socket_readable(sock, startup->max_inactivity * 1000)) {
				if((time(NULL)-(*lastactive))>startup->max_inactivity) {
					lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock);
					sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)."
						,startup->max_inactivity);
					return(0);
deuce's avatar
deuce committed
				}
			}
deuce's avatar
deuce committed
	}
	else {
		while (1) {
Deucе's avatar
Deucе committed
			if (!socket_readable(sock, startup->max_inactivity * 1000)) {
				if((time(NULL)-(*lastactive))>startup->max_inactivity) {
					lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock);
					sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)."
						,startup->max_inactivity);
					return(0);
Deucе's avatar
Deucе committed
				continue;
deuce's avatar
deuce committed
	#ifdef SOCKET_DEBUG_RECV_CHAR
			socket_debug[sock]|=SOCKET_DEBUG_RECV_CHAR;
	#endif
			i=recv(sock, buf, 1, 0);
	#ifdef SOCKET_DEBUG_RECV_CHAR
			socket_debug[sock]&=~SOCKET_DEBUG_RECV_CHAR;
	#endif
			return i;
deuce's avatar
deuce committed
	}
}

int sockreadline(SOCKET socket, CRYPT_SESSION sess, char* buf, int len, time_t* lastactive)
{
	char	ch;
	int		i,rd=0;

	buf[0]=0;

	if(socket==INVALID_SOCKET) {
		lprintf(LOG_WARNING,"INVALID SOCKET in call to sockreadline");
		return(0);
	}

	while(rd<len-1) {
		i = sock_recvbyte(socket, sess, &ch, lastactive);

		if(i<1) {
			if (sess != -1)
				recverror(socket,i,__LINE__);
		if(ch=='\n' /* && rd>=1 */) { /* Mar-9-2003: terminate on sole LF */
	if(rd>0 && buf[rd-1]=='\r')
		buf[rd-1]=0;
	else
		buf[rd]=0;
deuce's avatar
deuce committed

void ftp_terminate(void)
deuce's avatar
deuce committed
   	lprintf(LOG_INFO,"FTP Server terminate");
int ftp_remove(SOCKET sock, int line, const char* fname, const char* username)
	if(fexist(fname) && (ret=remove(fname))!=0) {
		if(fexist(fname))	// In case there was a race condition (other host deleted file first)
			lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) (line %d) removing file: %s", sock, username, errno, STRERROR(errno), line, fname);
rswindell's avatar
rswindell committed
	SOCKET		ctrl_sock;
deuce's avatar
deuce committed
	CRYPT_SESSION	ctrl_sess;
rswindell's avatar
rswindell committed
	SOCKET*		data_sock;
deuce's avatar
deuce committed
	CRYPT_SESSION*	data_sess;
rswindell's avatar
rswindell committed
	BOOL*		inprogress;
	BOOL*		aborted;
	BOOL		delfile;
	BOOL		tmpfile;
	BOOL		credits;
	BOOL		append;
rswindell's avatar
rswindell committed
	char		filename[MAX_PATH+1];
	time_t*		lastactive;
	user_t*		user;
	client_t*	client;
	int			dir;
	char*		desc;
} xfer_t;

static void send_thread(void* arg)
{
	char		buf[8192];
deuce's avatar
deuce committed
	char		host_ip[INET6_ADDRSTRLEN];
	ulong		dur;
	ulong		cps;
	BOOL		error=FALSE;
	FILE*		fp;
	file_t		f;
	xfer_t		xfer;
	time_t		now;
	time_t		start;
	time_t		last_report;
deuce's avatar
deuce committed
	union xp_sockaddr	addr;
deuce's avatar
deuce committed
	char		*estr;

	xfer=*(xfer_t*)arg;
	length=flength(xfer.filename);

		if(xfer.tmpfile) {
			if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES))
				ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 No files");
		} else {
			lprintf(LOG_WARNING, "%04d <%s> !DATA cannot send file (%s) with size of %"PRIdOFF" bytes"
				,xfer.ctrl_sock, xfer.user->alias, xfer.filename, length);
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 Invalid file size: %"PRIdOFF, length);
		}
		ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
		*xfer.inprogress=FALSE;
		thread_down();
		return;
	}

	if((fp=fnopen(NULL,xfer.filename,O_RDONLY|O_BINARY))==NULL	/* non-shareable open failed */
		&& (fp=fopen(xfer.filename,"rb"))==NULL) {				/* shareable open failed */
		lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
			,xfer.ctrl_sock, xfer.user->alias, errno, strerror(errno), __LINE__, xfer.filename);
rswindell's avatar
rswindell committed
		sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 ERROR %d (%s) opening %s", errno, strerror(errno), xfer.filename);
		if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
			(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
deuce's avatar
deuce committed
		ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
		*xfer.inprogress=FALSE;
#ifdef SOCKET_DEBUG_SENDTHREAD
rswindell's avatar
rswindell committed
			socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SENDTHREAD;
#endif

	*xfer.aborted=FALSE;
	if(startup->options&FTP_OPT_DEBUG_DATA || xfer.filepos)
		lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d sending %s from offset %"PRIdOFF
			,xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos);
	fseeko(fp,xfer.filepos,SEEK_SET);
	last_report=start=time(NULL);
	while((xfer.filepos+total)<length) {
		now=time(NULL);

		/* Periodic progress report */
		if(total && now>=last_report+XFER_REPORT_INTERVAL) {
			if(xfer.filepos)
				sprintf(str," from offset %"PRIdOFF,xfer.filepos);
			lprintf(LOG_INFO,"%04d <%s> DATA Sent %"PRIdOFF" bytes (%"PRIdOFF" total) of %s (%lu cps)%s"
				,xfer.ctrl_sock, xfer.user->alias, total,length,xfer.filename
				,(ulong)((total-last_total)/(now-last_report))
				,str);
			last_total=total;
			last_report=now;
		}

		if(*xfer.aborted==TRUE) {
			lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);
deuce's avatar
deuce committed
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer aborted.");
deuce's avatar
deuce committed
		if(ftp_set==NULL || terminate_server) {
			lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer locally aborted",xfer.ctrl_sock, xfer.user->alias);
deuce's avatar
deuce committed
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer locally aborted.");
Deucе's avatar
Deucе committed
		/* Check socket for writability */
		if (!socket_writable(*xfer.data_sock, 1000))
		fseeko(fp,xfer.filepos+total,SEEK_SET);
		rd=fread(buf,sizeof(char),sizeof(buf),fp);
		if(rd<1) /* EOF or READ error */
#ifdef SOCKET_DEBUG_SEND
		socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SEND;
#endif
deuce's avatar
deuce committed
		if (*xfer.data_sess != -1) {
			int status = cryptPushData(*xfer.data_sess, buf, rd, &wr);
			if (status != CRYPT_OK) {
deuce's avatar
deuce committed
				GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "pushing data");
deuce's avatar
deuce committed
				wr = -1;
			}
			else {
				status = cryptFlushData(*xfer.data_sess);
				if (status != CRYPT_OK) {
deuce's avatar
deuce committed
					GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "flushing data");
deuce's avatar
deuce committed
					wr = -1;
				}
			}
		}
		else
			wr=sendsocket(*xfer.data_sock,buf,rd);
#ifdef SOCKET_DEBUG_SEND
		socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SEND;
#endif
			if(wr==SOCKET_ERROR) {
					/*lprintf(LOG_WARNING,"%04d DATA send would block, retrying",xfer.ctrl_sock);*/
					continue;
				}
				else if(ERROR_VALUE==ECONNRESET) 
					lprintf(LOG_WARNING,"%04d <%s> DATA Connection reset by peer, sending on socket %d"
						,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
				else if(ERROR_VALUE==ECONNABORTED) 
					lprintf(LOG_WARNING,"%04d <%s> DATA Connection aborted by peer, sending on socket %d"
						,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
					lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d sending on data socket %d"
						,xfer.ctrl_sock, xfer.user->alias,ERROR_VALUE,*xfer.data_sock);
deuce's avatar
deuce committed
				sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Error %d sending on DATA channel"
				error=TRUE;
				break;
			}
			if(wr==0) {
				lprintf(LOG_WARNING,"%04d <%s> !DATA socket %d disconnected",xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
deuce's avatar
deuce committed
				sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 DATA channel disconnected");
			lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) sending on socket %d"
				,xfer.ctrl_sock, xfer.user->alias, wr, ERROR_VALUE, *xfer.data_sock);
deuce's avatar
deuce committed
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"451 DATA send error");
			error=TRUE;
			break;
		}
		total+=wr;
	if((i=ferror(fp))!=0) 
		lprintf(LOG_ERR,"%04d <%s> !DATA FILE ERROR %d (%d, %s)"
			,xfer.ctrl_sock, xfer.user->alias, i, errno, strerror(errno));
deuce's avatar
deuce committed
	ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);	/* Signal end of file */
	if(startup->options&FTP_OPT_DEBUG_DATA)
		lprintf(LOG_DEBUG,"%04d <%s> DATA socket closed",xfer.ctrl_sock, xfer.user->alias);
		dur=(long)(time(NULL)-start);
		cps=(ulong)(dur ? total/dur : total*2);
		lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %"PRIdOFF" bytes sent in %lu seconds (%lu cps)"
			,xfer.ctrl_sock
			,total,dur,cps);
deuce's avatar
deuce committed
		sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"226 Download complete (%lu cps).",cps);

		if(xfer.dir>=0) {
			memset(&f,0,sizeof(f));
			if(loadfile(&scfg, xfer.dir, xfer.filename, &f, file_detail_normal) == TRUE) {
				f.hdr.times_downloaded++;
				f.hdr.last_downloaded = time32(NULL);
				updatefile(&scfg, &f);
				lprintf(LOG_INFO,"%04d <%s> DATA downloaded: %s (%u times total)"
					,xfer.ctrl_sock
					,xfer.user->alias
					,xfer.filename
				/**************************/
				/* Update Uploader's Info */
				/**************************/
				uploader.number = 0;
				if(f.from_ext != NULL)
					uploader.number = atoi(f.from_ext);
				if(uploader.number == 0)
					uploader.number=matchuser(&scfg, f.from, TRUE /*sysop_alias*/);
				if(uploader.number
					&& uploader.number!=xfer.user->number 
					&& getuserdat(&scfg,&uploader)==0
					&& uploader.firston < (time_t)f.hdr.when_imported.time) {
					l=f.cost;
					if(!(scfg.dir[f.dir]->misc&DIR_CDTDL))	/* Don't give credits on d/l */
						l=0;
					if(scfg.dir[f.dir]->misc&DIR_CDTMIN && cps) { /* Give min instead of cdt */
						mod=((ulong)(l*(scfg.dir[f.dir]->dn_pct/100.0))/cps)/60;
						adjustuserrec(&scfg,uploader.number,U_MIN,10,mod);
						sprintf(tmp,"%lu minute",mod);
					} else {
						mod=(ulong)(l*(scfg.dir[f.dir]->dn_pct/100.0));
						adjustuserrec(&scfg,uploader.number,U_CDT,10,mod);
						ultoac(mod,tmp);
					}
					if(!(scfg.dir[f.dir]->misc&DIR_QUIET)) {
						addr_len = sizeof(addr);
						if(uploader.level>=SYSOP_LEVEL
deuce's avatar
deuce committed
							&& getpeername(xfer.ctrl_sock,&addr.addr,&addr_len)==0
							&& inet_addrtop(&addr, host_ip, sizeof(host_ip))!=NULL)
							SAFEPRINTF2(username,"%s [%s]",xfer.user->alias,host_ip);
						else
							SAFECOPY(username,xfer.user->alias);
						/* Inform uploader of downloaded file */
						safe_snprintf(str,sizeof(str),text[DownloadUserMsg]
							,getfname(xfer.filename)
							,xfer.filepos ? "partially FTP-" : "FTP-"
			if(!xfer.tmpfile && !xfer.delfile && !(scfg.dir[f.dir]->misc&DIR_NOSTAT))
				inc_sys_download_stats(&scfg, 1, (ulong)total);
			user_downloaded(&scfg, xfer.user, 1, total);
rswindell's avatar
rswindell committed
			if(xfer.dir>=0 && !is_download_free(&scfg,xfer.dir,xfer.user,xfer.client))
				subtract_cdt(&scfg, xfer.user, xfer.credits);
		}
	}

	fclose(fp);
deuce's avatar
deuce committed
	if(ftp_set!=NULL && !terminate_server)
		*xfer.inprogress=FALSE;
	if(xfer.tmpfile) {
		if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES))
			(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
	} 
	else if(xfer.delfile && !error)
		(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
#if defined(SOCKET_DEBUG_SENDTHREAD)
rswindell's avatar
rswindell committed
			socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SENDTHREAD;
#endif

	thread_down();
}

static void receive_thread(void* arg)
{
	char		buf[8192];
	char		extdesc[LEN_EXTDESC + 1] = "";
	char		tmp[MAX_PATH+1];
	ulong		dur;
	ulong		cps;
	BOOL		error=FALSE;
	xfer_t		xfer;
	time_t		now;
	time_t		start;
	time_t		last_report;
deuce's avatar
deuce committed
	CRYPT_SESSION	sess = -1;
deuce's avatar
deuce committed
	char		*estr;

	xfer=*(xfer_t*)arg;
	SetThreadName("sbbs/ftpReceive");
	if((fp=fopen(xfer.filename,xfer.append ? "ab" : "wb"))==NULL) {
		lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
			,xfer.ctrl_sock, xfer.user->alias, errno, strerror(errno), __LINE__, xfer.filename);
rswindell's avatar
rswindell committed
		sockprintf(xfer.ctrl_sock,sess,"450 ERROR %d (%s) opening %s", errno, strerror(errno), xfer.filename);
deuce's avatar
deuce committed
		ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
rswindell's avatar
rswindell committed
		*xfer.inprogress=FALSE;
	if(xfer.append)
		xfer.filepos=filelength(fileno(fp));

	*xfer.aborted=FALSE;
	if(xfer.filepos || startup->options&FTP_OPT_DEBUG_DATA)
		lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d receiving %s from offset %"PRIdOFF
			,xfer.ctrl_sock,xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos);
	fseeko(fp,xfer.filepos,SEEK_SET);
	last_report=start=time(NULL);
	while(1) {

		now=time(NULL);
		if(total && now>=last_report+XFER_REPORT_INTERVAL) {
				sprintf(str," from offset %"PRIdOFF,xfer.filepos);
			lprintf(LOG_INFO,"%04d <%s> DATA Received %"PRIdOFF" bytes of %s (%lu cps)%s"
				,xfer.ctrl_sock
				,xfer.user->alias
				,total,xfer.filename
				,(ulong)((total-last_total)/(now-last_report))
			last_report=now;
		}
		if(startup->max_fsize && (xfer.filepos+total) > startup->max_fsize) {
			lprintf(LOG_WARNING,"%04d <%s> !DATA received %"PRIdOFF" bytes of %s exceeds maximum allowed (%"PRIu64" bytes)"
				,xfer.ctrl_sock, xfer.user->alias, xfer.filepos+total, xfer.filename, startup->max_fsize);
			sockprintf(xfer.ctrl_sock,sess,"552 File size exceeds maximum allowed (%"PRIu64" bytes)", startup->max_fsize);
		if(*xfer.aborted==TRUE) {
			lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);
deuce's avatar
deuce committed
			sockprintf(xfer.ctrl_sock,sess,"426 Transfer aborted.");
deuce's avatar
deuce committed
		if(ftp_set==NULL || terminate_server) {
			lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer locally aborted",xfer.ctrl_sock, xfer.user->alias);
deuce's avatar
deuce committed
			sockprintf(xfer.ctrl_sock,sess,"426 Transfer locally aborted.");
Deucе's avatar
Deucе committed
		/* Check socket for readability */
		if (!socket_readable(*xfer.data_sock, 1000))
#if defined(SOCKET_DEBUG_RECV_BUF)
		socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_RECV_BUF;
deuce's avatar
deuce committed
		if (*xfer.data_sess != -1) {
			int status = cryptPopData(*xfer.data_sess, buf, sizeof(buf), &rd);
deuce's avatar
deuce committed
				GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "popping data");
deuce's avatar
deuce committed
				rd = -1;
deuce's avatar
deuce committed
		}
		else {
			rd=recv(*xfer.data_sock,buf,sizeof(buf),0);
		}
#if defined(SOCKET_DEBUG_RECV_BUF)
		socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_RECV_BUF;
		if(rd<1) {
			if(rd==0) { /* Socket closed */
				if(startup->options&FTP_OPT_DEBUG_DATA)
					lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d closed by client"
						,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
				break;
			}
			if(rd==SOCKET_ERROR) {
					/*lprintf(LOG_WARNING,"%04d DATA recv would block, retrying",xfer.ctrl_sock);*/
					lprintf(LOG_WARNING,"%04d <%s> DATA Connection reset by peer, receiving on socket %d"
						,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
				else if(ERROR_VALUE==ECONNABORTED) 
					lprintf(LOG_WARNING,"%04d <%s> DATA Connection aborted by peer, receiving on socket %d"
						,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
					lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d receiving on data socket %d"
						,xfer.ctrl_sock, xfer.user->alias,ERROR_VALUE,*xfer.data_sock);
deuce's avatar
deuce committed
				sockprintf(xfer.ctrl_sock,sess,"426 Error %d receiving on DATA channel"
					,ERROR_VALUE);
				error=TRUE;
				break;
			}
			lprintf(LOG_ERR,"%04d <%s> !DATA ERROR recv returned %d on socket %d"
				,xfer.ctrl_sock, xfer.user->alias,rd,*xfer.data_sock);
deuce's avatar
deuce committed
			sockprintf(xfer.ctrl_sock,sess,"451 Unexpected socket error: %d",rd);
			error=TRUE;
			break;
		}
		fwrite(buf,1,rd,fp);
		total+=rd;
		*xfer.lastactive=time(NULL);
deuce's avatar
deuce committed
	ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
	if(error && startup->options&FTP_OPT_DEBUG_DATA)
		lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d closed",xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
	if(xfer.filepos+total < startup->min_fsize) {
		lprintf(LOG_WARNING,"%04d <%s> DATA received %"PRIdOFF" bytes for %s, less than minimum required (%"PRIu64" bytes)"
			,xfer.ctrl_sock, xfer.user->alias, xfer.filepos+total, xfer.filename, startup->min_fsize);
		sockprintf(xfer.ctrl_sock,sess,"550 File size less than minimum required (%"PRIu64" bytes)"
			(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
		dur=(long)(time(NULL)-start);
		cps=(ulong)(dur ? total/dur : total*2);
		lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %"PRIdOFF" bytes received in %lu seconds (%lu cps)"
			,xfer.ctrl_sock
			,total,dur,cps);

		if(xfer.dir>=0) {
			memset(&f,0,sizeof(f));
			smb_hfield_str(&f, SMB_FILENAME, getfname(xfer.filename));
			smb_hfield_str(&f, SENDER, xfer.user->alias);

			filedat=findfile(&scfg, xfer.dir, f.name, NULL);
			if(scfg.dir[f.dir]->misc&DIR_AONLY)  /* Forced anonymous */
				f.hdr.attr |= MSG_ANONYMOUS;
			off_t cdt = flength(xfer.filename);
			smb_hfield_bin(&f, SMB_COST, cdt);
			char fdesc[LEN_FDESC + 1] = "";
			/* Description specified with DESC command? */
			if(xfer.desc != NULL)	
				SAFECOPY(fdesc, xfer.desc);
			/* Necessary for DIR and LIB ARS keyword support in subsequent chk_ar()'s */
			SAFECOPY(xfer.user->curdir, scfg.dir[f.dir]->code);

			/* FILE_ID.DIZ support */
			if(scfg.dir[f.dir]->misc&DIR_DIZ) {
				lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ from: %s",xfer.ctrl_sock, xfer.user->alias,xfer.filename);
				if(extract_diz(&scfg, &f, /* diz_fnames */NULL, tmp, sizeof(tmp))) {
					lprintf(LOG_DEBUG,"%04d <%s> DATA Parsing DIZ: %s",xfer.ctrl_sock, xfer.user->alias,tmp);
					str_list_t lines = read_diz(tmp, /* max_line_len: */80);
					format_diz(lines, extdesc, sizeof(extdesc), /* allow_ansi: */false);
					strListFree(&lines);
					if(!fdesc[0]) {						/* use for normal description */
						prep_file_desc(extdesc, fdesc);	/* strip control chars and dupe chars */
					ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias);
				} else
					lprintf(LOG_DEBUG,"%04d <%s> DATA DIZ does not exist in: %s",xfer.ctrl_sock, xfer.user->alias ,xfer.filename);
			} /* FILE_ID.DIZ support */

			smb_hfield_str(&f, SMB_FILEDESC, fdesc);
					lprintf(LOG_ERR,"%04d <%s> !DATA ERROR updating file (%s) in database"
						,xfer.ctrl_sock, xfer.user->alias, f.name);
				/* need to update the index here */
			} else {
				if(!addfile(&scfg, xfer.dir, &f, extdesc))
					lprintf(LOG_ERR,"%04d <%s> !DATA ERROR adding file (%s) to database"
						,xfer.ctrl_sock, xfer.user->alias, f.name);
			if(scfg.dir[f.dir]->upload_sem[0])
				ftouch(scfg.dir[f.dir]->upload_sem);
			/**************************/
			/* Update Uploader's Info */
			/**************************/
			user_uploaded(&scfg, xfer.user, (!xfer.append && xfer.filepos==0) ? 1:0, total);
			if(scfg.dir[f.dir]->up_pct && scfg.dir[f.dir]->misc&DIR_CDTUL) { /* credit for upload */
				if(scfg.dir[f.dir]->misc&DIR_CDTMIN && cps)    /* Give min instead of cdt */
					xfer.user->min=adjustuserrec(&scfg,xfer.user->number,U_MIN,10
						,((ulong)(total*(scfg.dir[f.dir]->up_pct/100.0))/cps)/60);
				else
					xfer.user->cdt=adjustuserrec(&scfg,xfer.user->number,U_CDT,10
						,(ulong)(cdt*(scfg.dir[f.dir]->up_pct/100.0))); 
			if(!(scfg.dir[f.dir]->misc&DIR_NOSTAT))
				inc_sys_upload_stats(&scfg, 1, (ulong)total);
deuce's avatar
deuce committed
		sockprintf(xfer.ctrl_sock,sess,"226 Upload complete (%lu cps).",cps);
deuce's avatar
deuce committed
	if(ftp_set!=NULL && !terminate_server)
deuce's avatar
deuce committed
static BOOL start_tls(SOCKET *sock, CRYPT_SESSION *sess, BOOL resp)
{
	BOOL nodelay;
	ulong nb;
	int status;
deuce's avatar
deuce committed
	int level;
deuce's avatar
deuce committed

deuce's avatar
deuce committed
	if (get_ssl_cert(&scfg, &estr, &level) == -1) {
		if (estr) {
			lprintf(level, "%04d TLS %s", *sock, estr);
			free_crypt_attrstr(estr);
		if (resp)
			sockprintf(*sock, *sess, "431 TLS not available");
deuce's avatar
deuce committed
		return FALSE;
	}
	if ((status = cryptCreateSession(sess, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
deuce's avatar
deuce committed
		GCES(status, *sock, CRYPT_UNUSED, estr, "creating session");
		if (resp)
			sockprintf(*sock, *sess, "431 TLS not available");
deuce's avatar
deuce committed
		return FALSE;
	}
	if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
deuce's avatar
deuce committed
		GCES(status, *sock, *sess, estr, "disabling certificate verification");
deuce's avatar
deuce committed
		cryptDestroySession(*sess);
		*sess = -1;
		if(resp)
			sockprintf(*sock, *sess, "431 TLS not available");
deuce's avatar
deuce committed
		return FALSE;
	}
	if ((status=cryptSetAttribute(*sess, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
deuce's avatar
deuce committed
		GCES(status, *sock, *sess, estr, "setting private key");
deuce's avatar
deuce committed
		cryptDestroySession(*sess);
		*sess = -1;
		if (resp)
			sockprintf(*sock, *sess, "431 TLS not available");
deuce's avatar
deuce committed
		return FALSE;
	}
	nodelay = TRUE;
	(void)setsockopt(*sock,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
deuce's avatar
deuce committed
	nb=0;
	ioctlsocket(*sock,FIONBIO,&nb);
	if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_NETWORKSOCKET, *sock)) != CRYPT_OK) {
deuce's avatar
deuce committed
		GCES(status, *sock, *sess, estr, "setting network socket");
deuce's avatar
deuce committed
		cryptDestroySession(*sess);
		*sess = -1;
		if (resp)
			sockprintf(*sock, *sess, "431 TLS not available");
deuce's avatar
deuce committed
		return TRUE;
	}
	if (resp)
		sockprintf(*sock, -1, "234 Ready to start TLS");
	if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
deuce's avatar
deuce committed
		GCES(status, *sock, *sess, estr, "setting session active");
deuce's avatar
deuce committed
		return TRUE;
	}
deuce's avatar
deuce committed
	if (startup->max_inactivity) {
		if ((status = cryptSetAttribute(*sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity)) != CRYPT_OK) {
deuce's avatar
deuce committed
			GCES(status, *sock, *sess, estr, "setting read timeout");
deuce's avatar
deuce committed
			return TRUE;
		}
	}
	return FALSE;
}
deuce's avatar
deuce committed
static void filexfer(union xp_sockaddr* addr, SOCKET ctrl_sock, CRYPT_SESSION ctrl_sess, SOCKET pasv_sock, CRYPT_SESSION pasv_sess, SOCKET* data_sock
					,CRYPT_SESSION *data_sess, char* filename, off_t filepos, BOOL* inprogress, BOOL* aborted
					,BOOL delfile, BOOL tmpfile
					,time_t* lastactive
					,user_t* user
rswindell's avatar
rswindell committed
					,client_t* client
					,int dir
					,BOOL receiving
					,BOOL credits
					,BOOL append
deuce's avatar
deuce committed
					,char* desc,BOOL protected)
	socklen_t	addr_len;
deuce's avatar
deuce committed
	union xp_sockaddr	server_addr;
deuce's avatar
deuce committed
	char		host_ip[INET6_ADDRSTRLEN];

	if((*inprogress)==TRUE) {
		lprintf(LOG_WARNING,"%04d <%s> !DATA TRANSFER already in progress",ctrl_sock, user->alias);
deuce's avatar
deuce committed
		sockprintf(ctrl_sock,ctrl_sess,"425 Transfer already in progress.");
		if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
			(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
rswindell's avatar
rswindell committed
	if(*data_sock!=INVALID_SOCKET)
deuce's avatar
deuce committed
		ftp_close_socket(data_sock,data_sess,__LINE__);
rswindell's avatar
rswindell committed

deuce's avatar
deuce committed
	inet_addrtop(addr, host_ip, sizeof(host_ip));
	if(pasv_sock==INVALID_SOCKET) {	/* !PASV */

deuce's avatar
deuce committed
		if((*data_sock=socket(addr->addr.sa_family, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
			lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d opening socket", ctrl_sock, user->alias, ERROR_VALUE);
deuce's avatar
deuce committed
			sockprintf(ctrl_sock,ctrl_sess,"425 Error %d opening socket",ERROR_VALUE);
			if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
				(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
			*inprogress=FALSE;
			return;
		}
		if(startup->socket_open!=NULL)
			startup->socket_open(startup->cbdata,TRUE);
		if(startup->options&FTP_OPT_DEBUG_DATA)
			lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d opened",ctrl_sock, user->alias,*data_sock);
		/* Use port-1 for all data connections */
		reuseaddr=TRUE;
		(void)setsockopt(*data_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr));
deuce's avatar
deuce committed
		addr_len = sizeof(server_addr);
		if((result=getsockname(ctrl_sock, &server_addr.addr,&addr_len))!=0) {
			lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) getting address/port of command socket (%u)"
				,ctrl_sock, user->alias,result,ERROR_VALUE,pasv_sock);
deuce's avatar
deuce committed
			return;
		}
deuce's avatar
deuce committed
		inet_setaddrport(&server_addr, inet_addrport(&server_addr)-1);	/* 20? */
deuce's avatar
deuce committed
		result=bind(*data_sock, &server_addr.addr,addr_len);
deuce's avatar
deuce committed
			inet_setaddrport(&server_addr, 0);	/* any user port */
			result=bind(*data_sock, &server_addr.addr,addr_len);
		if(result!=0) {
			lprintf(LOG_ERR,"%04d <%s> DATA ERROR %d (%d) binding socket %d"
				,ctrl_sock, user->alias, result, ERROR_VALUE, *data_sock);
deuce's avatar
deuce committed
			sockprintf(ctrl_sock,ctrl_sess,"425 Error %d binding socket",ERROR_VALUE);
			if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
				(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
			*inprogress=FALSE;
deuce's avatar
deuce committed
			ftp_close_socket(data_sock,data_sess,__LINE__);
deuce's avatar
deuce committed
		result=connect(*data_sock, &addr->addr,xp_sockaddr_len(addr));
			lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d (%d) connecting to client %s port %u on socket %d"
					,ctrl_sock, user->alias,result,ERROR_VALUE
deuce's avatar
deuce committed
					,host_ip,inet_addrport(addr),*data_sock);
deuce's avatar
deuce committed
			sockprintf(ctrl_sock,ctrl_sess,"425 Error %d connecting to socket",ERROR_VALUE);
			if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
				(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
			*inprogress=FALSE;
deuce's avatar
deuce committed
			ftp_close_socket(data_sock,data_sess,__LINE__);
			return;
		}
		if(startup->options&FTP_OPT_DEBUG_DATA)
			lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d connected to %s port %u"
				,ctrl_sock, user->alias,*data_sock,host_ip,inet_addrport(addr));
deuce's avatar
deuce committed
		if (protected) {
			if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
				lprintf(LOG_DEBUG,"%04d <%s> !DATA ERROR activating TLS"
					,ctrl_sock, user->alias);
deuce's avatar
deuce committed
				sockprintf(ctrl_sock,ctrl_sess,"425 Error activating TLS");
				if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
					(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
deuce's avatar
deuce committed
				*inprogress=FALSE;
				ftp_close_socket(data_sock,data_sess,__LINE__);
				return;
			}
		}
	} else {	/* PASV */
		if(startup->options&FTP_OPT_DEBUG_DATA) {
deuce's avatar
deuce committed
			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 !DATA ERROR %d (%d) getting address/port of passive socket (%u)"
					,ctrl_sock, user->alias,result,ERROR_VALUE,pasv_sock);
				lprintf(LOG_DEBUG,"%04d <%s> PASV DATA socket %d listening on %s port %u"
					,ctrl_sock, user->alias,pasv_sock,host_ip,inet_addrport(addr));
Deucе's avatar
Deucе committed
		if (!socket_readable(pasv_sock, TIMEOUT_SOCKET_LISTEN * 1000)) {
			lprintf(LOG_WARNING,"%04d <%s> PASV !WARNING socket not readable"
				,ctrl_sock, user->alias);
deuce's avatar
deuce committed
			sockprintf(ctrl_sock,ctrl_sess,"425 Error %d selecting socket for connection",ERROR_VALUE);
			if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
				(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
deuce's avatar
deuce committed
		addr_len=sizeof(*addr);
#ifdef SOCKET_DEBUG_ACCEPT
		socket_debug[ctrl_sock]|=SOCKET_DEBUG_ACCEPT;
deuce's avatar
deuce committed
		*data_sock=accept(pasv_sock,&addr->addr,&addr_len);
#ifdef SOCKET_DEBUG_ACCEPT
		socket_debug[ctrl_sock]&=~SOCKET_DEBUG_ACCEPT;
		if(*data_sock==INVALID_SOCKET) {
			lprintf(LOG_WARNING,"%04d <%s> PASV !DATA ERROR %d accepting connection on socket %d"
				,ctrl_sock, user->alias,ERROR_VALUE,pasv_sock);
deuce's avatar
deuce committed
			sockprintf(ctrl_sock,ctrl_sess,"425 Error %d accepting connection",ERROR_VALUE);
			if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
				(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
			*inprogress=FALSE;
			return;
		}
		if(startup->socket_open!=NULL)
			startup->socket_open(startup->cbdata,TRUE);
		if(startup->options&FTP_OPT_DEBUG_DATA)
			lprintf(LOG_DEBUG,"%04d <%s> PASV DATA socket %d connected to %s port %u"
				,ctrl_sock, user->alias,*data_sock,host_ip,inet_addrport(addr));
deuce's avatar
deuce committed
		if (protected) {
			if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
				lprintf(LOG_WARNING,"%04d <%s> PASV !DATA ERROR starting TLS", pasv_sock, user->alias);
				sockprintf(ctrl_sock,ctrl_sess,"425 Error negotiating TLS");
deuce's avatar
deuce committed
				if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
					(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
deuce's avatar
deuce committed
				*inprogress=FALSE;
				return;
			}
		}
	do {

		l=1;

		if(ioctlsocket(*data_sock, FIONBIO, &l)!=0) {
			lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d disabling socket blocking"
				,ctrl_sock, user->alias, ERROR_VALUE);
deuce's avatar
deuce committed
			sockprintf(ctrl_sock,ctrl_sess,"425 Error %d disabling socket blocking"
				,ERROR_VALUE);
			break;
		}

		if((xfer=malloc(sizeof(xfer_t)))==NULL) {
			lprintf(LOG_CRIT,"%04d <%s> !DATA MALLOC FAILURE LINE %d",ctrl_sock, user->alias,__LINE__);
deuce's avatar
deuce committed
			sockprintf(ctrl_sock,ctrl_sess,"425 MALLOC FAILURE");
			break;
		}
		memset(xfer,0,sizeof(xfer_t));
		xfer->ctrl_sock=ctrl_sock;
deuce's avatar
deuce committed
		xfer->ctrl_sess=ctrl_sess;
deuce's avatar
deuce committed
		xfer->data_sess=data_sess;
		xfer->inprogress=inprogress;
		xfer->aborted=aborted;
		xfer->delfile=delfile;
		xfer->tmpfile=tmpfile;
		xfer->append=append;
		xfer->filepos=filepos;
		xfer->credits=credits;
		xfer->lastactive=lastactive;
		xfer->user=user;
rswindell's avatar
rswindell committed
		xfer->client=client;
		xfer->dir=dir;
		xfer->desc=desc;
		SAFECOPY(xfer->filename,filename);
		(void)protected_uint32_adjust(&thread_count,1);
		if(receiving)
			result=_beginthread(receive_thread,0,(void*)xfer);
		else
			result=_beginthread(send_thread,0,(void*)xfer);

		if(result!=-1)
			return;	/* success */

	} while(0);

	/* failure */
	if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
		(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
/* convert "user name" to "user.name" or "mr. user" to "mr._user" */
char* dotname(char* in, char* out)
{
	if(in == NULL) {
		strcpy(out, "(null)");
		return out;
	}
	if(strchr(in,'.')==NULL)
		ch='.';
	else
		ch='_';
	for(i=0;in[i];i++)
		if(in[i]<=' ')
		else
			out[i]=in[i];
	out[i]=0;
	return(out);
}

static BOOL can_list(lib_t *lib, dir_t *dir, user_t *user, client_t *client)
	if (!chk_ar(&scfg,lib->ar,user,client))
		return FALSE;
	if (dir->dirnum == scfg.sysop_dir)
		return TRUE;
	if (dir->dirnum == scfg.upload_dir)
		return TRUE;
	if (chk_ar(&scfg, dir->ar, user, client))
		return TRUE;
	return FALSE;
}
static BOOL ftpalias(char* fullalias, char* filename, user_t* user, client_t* client, int* curdir)
{
	char*	p;
	char*	tp;
	char*	fname="";
	char	line[512];
	char	alias[512];
	char	aliasfile[MAX_PATH+1];
	int		dir=-1;
	FILE*	fp;
	BOOL	result=FALSE;

	SAFECOPY(alias,fullalias);
rswindell's avatar
rswindell committed
		if(*p) {
			if(filename == NULL && p != alias)	// CWD command and a filename specified
				return FALSE;
			fname = p;
		}
rswindell's avatar
rswindell committed
	SAFEPRINTF(aliasfile,"%sftpalias.cfg",scfg.ctrl_dir);
	if((fp=fopen(aliasfile,"r"))==NULL) 
		return FALSE;

	while(!feof(fp)) {
		if(!fgets(line,sizeof(line),fp))
			break;

		p=line;	/* alias */
		SKIP_WHITESPACE(p);
		if(*p==';')	/* comment */
			continue;

		tp=p;		/* terminator */
		FIND_WHITESPACE(tp);
		if(*tp) *tp=0;

rswindell's avatar
rswindell committed
		if(stricmp(p, alias))	/* Not a match */
			continue;

		p=tp+1;		/* filename */
		SKIP_WHITESPACE(p);

		tp=p;		/* terminator */
		FIND_WHITESPACE(tp);
		if(*tp) *tp=0;

rswindell's avatar
rswindell committed
		if(filename == NULL /* CWD? */ && (*lastchar(p) != '/' || (*fname != 0 && strcmp(fname, alias)))) {
		if(!strnicmp(p,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
			if((dir=getdir(p+strlen(BBS_VIRTUAL_PATH),user,client))<0)	{
				lprintf(LOG_WARNING,"0000 <%s> !Invalid virtual path: %s",user->alias, p);
				/* invalid or no access */
				continue;
			}
			p=strrchr(p,'/');
			if(p!=NULL) p++;
			if(p!=NULL && filename!=NULL) {
				if(*p)
					sprintf(filename,"%s%s",scfg.dir[dir]->path,p);
				else
					sprintf(filename,"%s%s",scfg.dir[dir]->path,fname);
			}
		} else if(filename!=NULL)

		result=TRUE;	/* success */
		break;
	}
	fclose(fp);
	if(curdir!=NULL)
		*curdir=dir;
	return(result);
}

/*
 * Parses a path into *curlib, *curdir, and sets *pp to point to the filename
 */
static int parsepath(char** pp, user_t* user, client_t* client, int* curlib, int* curdir)
{
	int lib = *curlib;
	int dir = *curdir;
	char *p = *pp;
	char *fname = strchr(p, 0);
	int ret = 0;
	size_t len;

	if (*p == '/') {
		lib = -1;
		dir = -1;
	while (*p) {
		/* Relative path stuff */
		if (strcmp(p, "..") == 0) {
			if (dir >= 0)
				dir = -1;
			else if (lib >= 0)
				lib = -1;
			else
				ret = -1;
			p += 2;
		}
		else if(strncmp(p, "../", 3) == 0) {
			if (dir >= 0)
				dir = -1;
			else if (lib >= 0)
				lib = -1;
			else
				ret = -1;
			p += 3;
		}
		else if(strcmp(p, ".") == 0)
		else if(strncmp(p, "./", 2) == 0)
			p += 2;
		/* Path component */
		else if (lib < 0) {
			for(lib=0;lib<scfg.total_libs;lib++) {
				if(!chk_ar(&scfg,scfg.lib[lib]->ar,user,client))
					continue;
				len = strlen(scfg.lib[lib]->sname);
				if (strlen(p) < len)
					continue;
				if (p[len] != 0 && p[len] != '/')
					continue;
				if(!strnicmp(scfg.lib[lib]->sname,p,len)) {
					p += len;
					if (*p)
						p++;
					break;
				}
			}
			if (lib == scfg.total_libs) {
				SAFECOPY(filename, p);
				tmp = strchr(filename, '/');
				if (tmp != NULL)
					*tmp = 0;
				if (ftpalias(filename, filename, user, client, &dir) == TRUE) {
					lib = scfg.dir[dir]->lib;
					if (strchr(p, '/') != NULL) {
						p = strchr(p, '/');
						p++;
					}
					else
						p = strchr(p, 0);
				}
				else {
					ret = -1;
					lib = -1;
					if (strchr(p, '/') != NULL) {
						p = strchr(p, '/');
						p++;
					}
					else
						p = strchr(p, 0);
		else if (dir < 0) {
			for(dir=0;dir<scfg.total_dirs;dir++) {
				if(scfg.dir[dir]->lib!=lib)
					continue;
				if (!can_list(scfg.lib[lib], scfg.dir[dir], user, client))
					continue;
				len = strlen(scfg.dir[dir]->code_suffix);
				if (strlen(p) < len)
					continue;
				if (p[len] != 0 && p[len] != '/')
					continue;
				if(!strnicmp(scfg.dir[dir]->code_suffix,p,len)) {
					p += len;
					if (*p)
						p++;
					break;
				}
			}
			if (dir == scfg.total_dirs) {
				ret = -1;
				dir = -1;
				if (strchr(p, '/') != NULL) {
					p = strchr(p, '/');
					p++;
				}
				else
					p = strchr(p, 0);
		else {	// Filename
			if (strchr(p, '/') != NULL) {
				ret = -1;
				p = strchr(p, '/');
				p++;
			}
			else {
				fname = p;
				p += strlen(fname);
			}
	*curdir = dir;
	*curlib = lib;
	*pp = fname;
}

char* root_dir(char* path)
{
	char*	p;
	static char	root[MAX_PATH+1];
	SAFECOPY(root,path);

	if(!strncmp(root,"\\\\",2)) {	/* network path */
		p=strchr(root+2,'\\');
		if(p) p=strchr(p+1,'\\');
		if(p) *(p+1)=0;				/* truncate at \\computer\sharename\ */
	} 
	else if(!strncmp(root+1,":/",2) || !strncmp(root+1,":\\",2))
		root[3]=0;
	else if(*root=='/' || *root=='\\')
		root[1]=0;

	return(root);
}

char* genvpath(int lib, int dir, char* str)
{
	strcpy(str,"/");
	if(lib<0)
		return(str);
	strcat(str,scfg.lib[lib]->sname);
	strcat(str,scfg.dir[dir]->code_suffix);
deuce's avatar
deuce committed
void ftp_printfile(SOCKET sock, CRYPT_SESSION sess, const char* name, unsigned code)
{
	char	path[MAX_PATH+1];
	char	buf[512];
	FILE*	fp;
	unsigned i;

	SAFEPRINTF2(path,"%sftp%s.txt",scfg.text_dir,name);
	if((fp=fopen(path,"rb"))!=NULL) {
		i=0;
		while(!feof(fp)) {
			if(!fgets(buf,sizeof(buf),fp))
				break;
			truncsp(buf);
			if(!i)
deuce's avatar
deuce committed
				sockprintf(sock,sess,"%u-%s",code,buf);
deuce's avatar
deuce committed
				sockprintf(sock,sess," %s",buf);
deuce's avatar
deuce committed
static BOOL ftp_hacklog(char* prot, char* user, char* text, char* host, union xp_sockaddr* addr)
{
#ifdef _WIN32
	if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
		PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif

	return hacklog(&scfg, prot, user, text, host, addr);
}

/****************************************************************************/
/* Consecutive failed login (possible password hack) attempt tracking		*/
/****************************************************************************/

deuce's avatar
deuce committed
static BOOL badlogin(SOCKET sock, CRYPT_SESSION sess, ulong* login_attempts, char* user, char* passwd, char* host, union xp_sockaddr* addr)
deuce's avatar
deuce committed
	char	host_ip[INET6_ADDRSTRLEN];
		count=loginFailure(startup->login_attempt_list, addr, "FTP", user, passwd);
rswindell's avatar
rswindell committed
		if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold)
			ftp_hacklog("FTP LOGIN", user, passwd, host, addr);
rswindell's avatar
rswindell committed
		if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold) {
			char reason[128];
			SAFEPRINTF(reason, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS (%lu)", count);
deuce's avatar
deuce committed
			inet_addrtop(addr, host_ip, sizeof(host_ip));
			filter_ip(&scfg, "FTP", reason, host, host_ip, user, /* fname: */NULL);
deuce's avatar
deuce committed
		}
		if(count > *login_attempts)
			*login_attempts=count;
	} else
		(*login_attempts)++;
rswindell's avatar
rswindell committed
	mswait(startup->login_attempt.delay);	/* As recommended by RFC2577 */
	if((*login_attempts)>=3) {
deuce's avatar
deuce committed
		sockprintf(sock,sess,"421 Too many failed login attempts.");
deuce's avatar
deuce committed
	ftp_printfile(sock,sess,"badlogin",530);
	sockprintf(sock,sess,"530 Invalid login.");
static char* ftp_tmpfname(char* fname, char* ext, SOCKET sock)
	safe_snprintf(fname,MAX_PATH,"%sSBBS_FTP.%x%x%x%lx.%s"
		,scfg.temp_dir,getpid(),sock,rand(),(ulong)clock(),ext);
#if defined(__GNUC__)	// Catch printf-format errors 
static BOOL send_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, const char *format, ...) __attribute__ ((format (printf, 4, 5)));
#endif
static BOOL send_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, const char *format, ...)
{
	va_list va;
	char *str;
Deucе's avatar
Deucе committed
	int rval;

	if (fp == NULL && sock == INVALID_SOCKET)
		return FALSE;
	va_start(va, format);
Deucе's avatar
Deucе committed
	rval = vasprintf(&str, format, va);
	va_end(va);
	if (rval == -1)
		return FALSE;
	if (fp != NULL)
		fprintf(fp, "%s\r\n", str);
	else
		sockprintf(sock, sess, " %s", str);
	free(str);
	return TRUE;
}

static char *get_unique(const char *path, char *uniq)
{
	BYTE digest[MD5_DIGEST_SIZE];

	if (path == NULL)
		return NULL;

	MD5_calc(digest, path, strlen(path));
static BOOL send_mlsx_entry(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned feats, const char *type, const char *perm, uint64_t size, time_t modify, const char *owner, const char *unique, time_t ul, const char *fname)
{
	char line[1024];
	char *end;
	BOOL need_owner = FALSE;
	struct tm t;

	end=line;
	*end=0;
	if (type != NULL && (feats & MLSX_TYPE))
		end += sprintf(end, "Type=%s;", type);
	if (perm != NULL && (feats & MLSX_PERM))
		end += sprintf(end, "Perm=%s;", perm);
	if (size != UINT64_MAX && (feats & MLSX_SIZE))
		end += sprintf(end, "Size=%" PRIu64 ";", size);
deuce's avatar
deuce committed
	if (modify != 0 && (feats & MLSX_MODIFY)) {
		t = *gmtime(&modify);
deuce's avatar
deuce committed
		end += sprintf(end, "Modify=%04d%02d%02d%02d%02d%02d;",
		    t.tm_year+1900, t.tm_mon+1, t.tm_mday,
		    t.tm_hour, t.tm_min, t.tm_sec);
deuce's avatar
deuce committed
	}
	if (unique != NULL && (feats & MLSX_UNIQUE))
		end += sprintf(end, "Unique=%s;", unique);
	if (ul != 0 && (feats & MLSX_CREATE)) {
Deucе's avatar
Deucе committed
		t = *gmtime(&ul);
		end += sprintf(end, "Create=%04d%02d%02d%02d%02d%02d;",
		    t.tm_year+1900, t.tm_mon+1, t.tm_mday,
		    t.tm_hour, t.tm_min, t.tm_sec);
	}
	// Owner can contain percents, so let send_mlsx() deal with it
	if (owner != NULL && (feats & MLSX_OWNER)) {
		strcat(end, "UNIX.ownername=%s;");
		need_owner = TRUE;
	}
	strcat(end, " %s");
	if (need_owner)
		return send_mlsx(fp, sock, sess, line, owner, fname==NULL ? "" : fname);
	return send_mlsx(fp, sock, sess, line, fname==NULL ? "" : fname);
}

deuce's avatar
deuce committed
static BOOL write_local_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned feats, const char *path, BOOL full_path)
{
	const char *type;
	char permstr[11];
	char *p;
	BOOL is_file = FALSE;
	if(stat(path, &st) != 0)
		return FALSE;
	if (!strcmp(path, "."))
		type="cdir";
	else if (!strcmp(path, ".."))
		type="pdir";
	else if (*lastchar(path) == '/')	/* is directory */
		type="dir";
	else {
		is_file = TRUE;
		type="file";
	}
	// TODO: Check for deletability 'd'
	// TODO: Check for renamability 'f'
	p = permstr;
	if (is_file) {
		if (access(path, W_OK) == 0) {
			// Can append ('a') and write ('w')
			*(p++)='a';
			*(p++)='w';
		}
		if (access(path, R_OK) == 0) {
			// Can read ('r')
			*(p++)='r';
		}
	}
	else {
		// TODO: Check these on Windows...
		if (access(path, W_OK) == 0) {
			// Can create files ('c'), directories ('m') and delete files ('p')
			*(p++)='c';
			*(p++)='m';
			*(p++)='p';
		}
		if (access(path, R_OK) == 0) {
			// Can change to the directory ('e'), and list files ('l')
			*(p++)='e';
			*(p++)='l';
		}
	}
	*p=0;
deuce's avatar
deuce committed
	if (is_file)
		full_path = FALSE;
	return send_mlsx_entry(fp, sock, sess, feats, type, permstr, (uint64_t)st.st_size, st.st_mtime, NULL, NULL, st.st_ctime, full_path ? path : getfname(path));
}

/*
 * Nobody can do anything but list files and change to dirs.
 */
static void get_libperm(lib_t *lib, user_t *user, client_t *client, char *permstr)
{
	char *p = permstr;

	if (chk_ar(&scfg,lib->ar,user,client)) {
		//*(p++) = 'a';	// File may be appended to
		//*(p++) = 'c';	// Files may be created in dir
		//*(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
		//*(p++) = 'r';	// File may be retrieved
		//*(p++) = 'w';	// File may be overwritten
	}
	*p=0;
}

static BOOL can_upload(lib_t *lib, dir_t *dir, user_t *user, client_t *client)
{
	if (!chk_ar(&scfg,lib->ar,user,client))
		return FALSE;
	if (user->rest & FLAG('U'))
		return FALSE;
	if (dir_op(&scfg, user, client, dir->dirnum))
		return TRUE;
	// The rest can only upload if there's room
	if(dir->maxfiles && getfiles(&scfg,dir->dirnum)>=dir->maxfiles)
		return FALSE;
	if (dir->dirnum == scfg.sysop_dir)
		return TRUE;
	if (dir->dirnum == scfg.upload_dir)
		return TRUE;
	if (chk_ar(&scfg, dir->ul_ar,user,client))
		return TRUE;
	if ((user->exempt & FLAG('U')))
		return TRUE;
	return FALSE;
}

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

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

	//*(p++) = 'a';	// File may be appended to
	if (can_upload(lib, dir, user, client))
		*(p++) = 'c';	// Files may be created in dir
	//*(p++) = 'd';	// Item may be depeted (dir or file)
	if (can_list(lib, dir, user, client)) {
		*(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
	if (can_delete_files(lib, dir, user, client))
		*(p++) = 'p';	// Files/Dirs in directory may be deleted
	//*(p++) = 'r';	// File may be retrieved
	//*(p++) = 'w';	// File may be overwritten
	*p=0;
}

static BOOL can_append(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
	if (!chk_ar(&scfg,lib->ar,user,client))
		return FALSE;
	if (user->rest&FLAG('U'))
		return FALSE;
	if (dir->dirnum != scfg.sysop_dir && dir->dirnum != scfg.upload_dir && !chk_ar(&scfg,dir->ar,user,client))
		return FALSE;
	if(!dir_op(&scfg,user,client,dir->dirnum) && !(user->exempt&FLAG('U'))) {
		if(!chk_ar(&scfg,dir->ul_ar,user,client))
			return FALSE;
	}
	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)
{
	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 (!chk_ar(&scfg,dir->dl_ar,user,client))
		return FALSE;
	// TODO: Verify credits
	return TRUE;
}

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		aliaspath[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
Loading
Loading full blame...