Skip to content
Snippets Groups Projects
ftpsrvr.c 152 KiB
Newer Older
			*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 DATA socket %d opened",ctrl_sock,*data_sock);
		/* Use port-1 for all data connections */
		reuseaddr=TRUE;
		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 !ERROR %d (%d) getting address/port of command socket (%u)"
				,ctrl_sock,result,ERROR_VALUE,pasv_sock);
			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 !DATA ERROR %d (%d) binding socket %d"
				,ctrl_sock, 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))
				ftp_remove(ctrl_sock, __LINE__, filename);
			*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 !DATA ERROR %d (%d) connecting to client %s port %u on socket %d"
					,ctrl_sock,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))
				ftp_remove(ctrl_sock, __LINE__, filename);
			*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 DATA socket %d connected to %s port %u"
deuce's avatar
deuce committed
				,ctrl_sock,*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 !DATA ERROR activating TLS"
					,ctrl_sock,*data_sock,host_ip,inet_addrport(addr));
				sockprintf(ctrl_sock,ctrl_sess,"425 Error activating TLS");
				if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
					ftp_remove(ctrl_sock, __LINE__, filename);
				*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 !ERROR %d (%d) getting address/port of passive socket (%u)"
					,ctrl_sock,result,ERROR_VALUE,pasv_sock);
			else
				lprintf(LOG_DEBUG,"%04d PASV DATA socket %d listening on %s port %u"
deuce's avatar
deuce committed
					,ctrl_sock,pasv_sock,host_ip,inet_addrport(addr));

		/* Setup for select() */
		tv.tv_sec=TIMEOUT_SOCKET_LISTEN;
		tv.tv_usec=0;

		FD_ZERO(&socket_set);
		FD_SET(pasv_sock,&socket_set);

#if defined(SOCKET_DEBUG_SELECT)
		socket_debug[ctrl_sock]|=SOCKET_DEBUG_SELECT;
		result=select(pasv_sock+1,&socket_set,NULL,NULL,&tv);
#if defined(SOCKET_DEBUG_SELECT)
		socket_debug[ctrl_sock]&=~SOCKET_DEBUG_SELECT;
			lprintf(LOG_WARNING,"%04d !PASV select returned %d (error: %d)",ctrl_sock,result,ERROR_VALUE);
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))
				ftp_remove(ctrl_sock, __LINE__, filename);
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 !PASV DATA ERROR %d accepting connection on socket %d"
				,ctrl_sock,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))
				ftp_remove(ctrl_sock, __LINE__, filename);
			*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 PASV DATA socket %d connected to %s port %u"
deuce's avatar
deuce committed
				,ctrl_sock,*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 !PASV ERROR starting TLS", pasv_sock);
				sockprintf(ctrl_sock,ctrl_sess,"425 Error negotiating TLS", ERROR_VALUE);
				if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
					ftp_remove(ctrl_sock, __LINE__, filename);
				*inprogress=FALSE;
				return;
			}
		}
	do {

		l=1;

		if(ioctlsocket(*data_sock, FIONBIO, &l)!=0) {
			lprintf(LOG_ERR,"%04d !DATA ERROR %d disabling socket blocking"
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 !MALLOC FAILURE LINE %d",ctrl_sock,__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);
		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))
		ftp_remove(ctrl_sock, __LINE__, filename);
/* convert "user name" to "user.name" or "mr. user" to "mr._user" */
char* dotname(char* in, char* 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);
}

rswindell's avatar
rswindell committed
void parsepath(char** pp, user_t* user, client_t* client, int* curlib, int* curdir)
	SAFECOPY(path,*pp);
	p=path;

	if(*p=='/') {
		p++;
		lib=-1;
	}
	else if(!strncmp(p,"./",2))
		p+=2;

	if(!strncmp(p,"..",2)) {
		p+=2;
		if(dir>=0)
			dir=-1;
		else if(lib>=0)
			lib=-1;
		if(*p=='/')
			p++;
	}

	if(*p==0) {
		*curlib=lib;
		*curdir=dir;
		return;
	}

	if(lib<0) { /* root */
		tp=strchr(p,'/');
		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) { /* not found */
			*curlib=-1;
			return;
		}
		*curlib=lib;

			*pp+=tp-path;	/* skip "lib" or "lib/" */
	}

	tp=strchr(p,'/');
	if(tp!=NULL) {
		*tp=0;
		tp++;
	} else 
		tp=p+strlen(p);

	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))
	if(dir>=scfg.total_dirs) {  /* not found */
		*pp+=p-path;			/* skip /lib/filespec */

	*curdir=dir;

	*pp+=tp-path;	/* skip "lib/dir/" */
}

rswindell's avatar
rswindell committed
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;

	sprintf(aliasfile,"%sftpalias.cfg",scfg.ctrl_dir);
	if((fp=fopen(aliasfile,"r"))==NULL) 
	SAFECOPY(alias,fullalias);
	p=strrchr(alias+1,'/');
	if(p) {
		*p=0;
		fname=p+1;
	}

	if(filename==NULL /* directory */ && *fname /* filename specified */) {
		fclose(fp);
	while(!feof(fp)) {
		if(!fgets(line,sizeof(line),fp))
			break;

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

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

		if(stricmp(p,alias))	/* Not a match */
			continue;

		p=tp+1;		/* filename */

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

		if(!strnicmp(p,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
rswindell's avatar
rswindell committed
			if((dir=getdir(p+strlen(BBS_VIRTUAL_PATH),user,client))<0)	{
				lprintf(LOG_WARNING,"0000 !Invalid virtual path (%s) for %s",p,user->alias);
				/* 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)
			strcpy(filename,p);

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

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) {
deuce's avatar
deuce committed
			inet_addrtop(addr, host_ip, sizeof(host_ip));
			filter_ip(&scfg, "FTP", "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
deuce's avatar
deuce committed
				,host, host_ip, user, /* fname: */NULL);
		}
		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(),clock(),ext);
	return(fname);
static void ctrl_thread(void* arg)
{
	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		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]="";
	char		html_index_ext[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;
#ifdef JAVASCRIPT
	JSContext*	js_cx=NULL;
	JSObject*	js_glob;
	JSString*	js_str;
	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->answer_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
		PlaySound(startup->answer_sound, 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));

	if(startup->options&FTP_OPT_NO_HOST_LOOKUP)
deuce's avatar
deuce committed
		strcpy(host_name,"<no name>");
	else {
		if(getnameinfo(&ftp.client_addr.addr, sizeof(ftp.client_addr), host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD)!=0)
			strcpy(host_name,"<no name>");
	}
	if(!(startup->options&FTP_OPT_NO_HOST_LOOKUP))
		lprintf(LOG_INFO,"%04d Hostname: %s", sock, host_name);
	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 (%u 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__);
	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";
	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 (%u login attempts)"
deuce's avatar
deuce committed
			,sock, host_ip, login_attempts);
rswindell's avatar
rswindell committed
		mswait(login_attempts*startup->login_attempt.throttle);
deuce's avatar
deuce committed
	sockprintf(sock,sess,"220-%s (%s)",scfg.sys_name, startup->host_name);
	sockprintf(sock,sess," Synchronet FTP Server %s-%s Ready"
		,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 Aborting transfer due to receive error",sock);
				transfer_aborted=TRUE;
		lastactive=time(NULL);
		cmd=buf;
		while(((BYTE)*cmd)==TELNET_IAC) {
			cmd++;
deuce's avatar
deuce committed
			lprintf(LOG_DEBUG,"%04d RX%s: Telnet cmd: %s",sock,sess == -1 ? "" : "S", telnet_cmd_desc(*cmd));
			cmd++;
		}
		while(*cmd && *cmd<' ') {
deuce's avatar
deuce committed
			lprintf(LOG_DEBUG,"%04d RX%s: %d (0x%02X)",sock,sess == -1 ? "" : "S", (BYTE)*cmd,(BYTE)*cmd);
			cmd++;
		}
		if(!(*cmd))
			continue;
		if(startup->options&FTP_OPT_DEBUG_RX)
deuce's avatar
deuce committed
			lprintf(LOG_DEBUG,"%04d RX%s: %s", sock, 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,"211 End");
			continue;
		}
		if(!strnicmp(cmd, "OPTS",4)) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"501 No options 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=matchuser(&scfg,user.alias,FALSE /*sysop_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=matchuser(&scfg,user.alias,FALSE /*sysop_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 !ERROR %d getting data for user #%d (%s)"
					,sock,i,user.number,user.alias);
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 !DELETED or INACTIVE user #%d (%s)"
					,sock,user.number,user.alias);
				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 !T RESTRICTED user #%d (%s)"
					,sock,user.number,user.alias);
				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 !MAXIMUM LOGONS (%d) reached for %s"
					,sock,scfg.level_callsperday[user.level],user.alias);
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 !L RESTRICTED user #%d (%s) already on today"
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 !BLOCKED e-mail address: %s",sock,password);
deuce's avatar
deuce committed
					if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
				lprintf(LOG_INFO,"%04d %s: <%s>",sock,user.alias,password);
				putuserrec(&scfg,user.number,U_NETMAIL,LEN_NETMAIL,password);
			}
			else if(user.level>=SYSOP_LEVEL && !stricmp(password,sys_pass)) {
				lprintf(LOG_INFO,"%04d Sysop access granted to %s", sock, user.alias);
				sysop=TRUE;
			}
			else if(stricmp(password,user.pass)) {
					lprintf(LOG_WARNING,"%04d !FAILED Password attempt for user %s: '%s' expected '%s'"
						,sock, user.alias, password, user.pass);
				else
					lprintf(LOG_WARNING,"%04d !FAILED Password attempt for user %s"
				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 */
				client.user=user.alias;
				loginSuccess(startup->login_attempt_list, &ftp.client_addr);
				sprintf(str,"%s <%.32s>",user.alias,password);
				client.user=str;
			}
			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);
			if(js_cx!=NULL) {
				if(js_CreateUserClass(js_cx, js_glob, &scfg)==NULL) 
					lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user class",sock);
rswindell's avatar
rswindell committed
				if(js_CreateUserObject(js_cx, js_glob, &scfg, "user", user.number, &client)==NULL) 
					lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user object",sock);
				if(js_CreateClientObject(js_cx, js_glob, "client", &client, sock, -1)==NULL) 
					lprintf(LOG_ERR,"%04d !JavaScript ERROR creating client object",sock);
				if(js_CreateFileAreaObject(js_cx, js_glob, &scfg, &user
					,startup->html_index_file)==NULL) 
					lprintf(LOG_ERR,"%04d !JavaScript ERROR creating file area object",sock);
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;
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))
					break;
				user.number=0;
				sysop=FALSE;
				filepos=0;
				got_pbsz = FALSE;
				protection = FALSE;
				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");