Skip to content
Snippets Groups Projects
ftpsrvr.c 124 KiB
Newer Older
				if((!append && filepos==0 && fexist(fname))
					|| (startup->options&FTP_OPT_INDEX_FILE 
						&& !stricmp(p,startup->index_file_name))
					|| (startup->options&FTP_OPT_HTML_INDEX_FILE 
						&& !stricmp(p,startup->html_index_file))
					) {
					lprintf("%04d !%s attempted to overwrite existing file: %s"
						,sock,user.alias,fname);
					sockprintf(sock,"553 File already exists.");
					continue;
				}
				if(append || filepos) {	/* RESUME */
#ifdef _WIN32
					GetShortPathName(fname, str, sizeof(str));
#else
					SAFECOPY(str,fname);
#endif
					padfname(getfname(str),f.name);
					strupr(f.name);
					f.dir=dir;
					f.cdt=0;
					f.size=-1;
					if(!getfileixb(&scfg,&f) || !getfiledat(&scfg,&f)) {
						if(filepos) {
							lprintf("%04d !%s file (%s) not in database for %.4s command"
								,sock,user.alias,fname,cmd);
							sockprintf(sock,"550 File not found: %s",p);
							continue;
						}
						append=FALSE;
					}
					/* Verify user is original uploader */
					if((append || filepos) && stricmp(f.uler,user.alias)) {
						lprintf("%04d !%s cannot resume upload of %s, uploaded by %s"
							,sock,user.alias,fname,f.uler);
						sockprintf(sock,"553 Insufficient access (can't resume upload from different user).");
						continue;
					}
				}
				lprintf("%04d %s uploading: %s to %s (%s) in %s mode"
					,sock,user.alias
					,p						/* filename */
					,vpath(lib,dir,str)		/* virtual path */
					,scfg.dir[dir]->path	/* actual path */
					,pasv_sock==INVALID_SOCKET ? "active":"passive");
			}
			sockprintf(sock,"150 Opening BINARY mode data connection for file transfer.");
			filexfer(&data_addr,sock,pasv_sock,&data_sock,fname,filepos
				,&transfer_inprogress,&transfer_aborted,FALSE,FALSE
				,&lastactive
				,&user
				,dir
				,TRUE	/* uploading */
				,TRUE	/* credits */
				,desc
				);
			filepos=0;
			continue;
		}

		if(!stricmp(cmd,"CDUP") || !stricmp(cmd,"XCUP")) {
			if(curdir<0)
				curlib=-1;
			else
				curdir=-1;
			sockprintf(sock,"200 CDUP command successful.");
			continue;
		}

		if(!strnicmp(cmd, "CWD ", 4) || !strnicmp(cmd,"XCWD ",5)) {
			p=cmd+4;
			while(*p && *p<=' ') p++;

			if(!strnicmp(p,BBS_FSYS_DIR,strlen(BBS_FSYS_DIR))) 
				p+=strlen(BBS_FSYS_DIR);	/* already mounted */

			if(*p=='/') {
				curlib=-1;
				curdir=-1;
				p++;
			}
			/* Local File System? */
			if(sysop && !(startup->options&FTP_OPT_NO_LOCAL_FSYS) 
				&& !strnicmp(p,LOCAL_FSYS_DIR,strlen(LOCAL_FSYS_DIR))) {	
				p+=strlen(LOCAL_FSYS_DIR);
				if(!direxist(p)) {
					sockprintf(sock,"550 Directory does not exist.");
					lprintf("%04d !%s attempted to mount invalid directory: %s"
						,sock, user.alias, p);
					continue;
				}
				SAFECOPY(local_dir,p);
				local_fsys=TRUE;
				sockprintf(sock,"250 CWD command successful (local file system mounted).");
				lprintf("%04d %s mounted local file system", sock, user.alias);
				continue;
			}
			success=FALSE;

			/* Directory Alias? */
			if(curlib<0 && ftpalias(p,NULL,&user,&curdir)==TRUE) {
				if(curdir>=0)
					curlib=scfg.dir[curdir]->lib;
				success=TRUE;
			}

			orglib=curlib;
			orgdir=curdir;
			tp=0;
			if(!strncmp(p,"...",3)) {
				curlib=-1;
				curdir=-1;
				p+=3;
			}
			if(!strncmp(p,"./",2))
				p+=2;
			else if(!strncmp(p,"..",2)) {
				if(curdir<0)
					curlib=-1;
				else
					curdir=-1;
				p+=2;
			}
			if(*p==0)
				success=TRUE;
			else if(!strcmp(p,".")) 
				success=TRUE;
			if(!success  && (curlib<0 || *p=='/')) { /* Root dir */
				if(*p=='/') p++;
				tp=strchr(p,'/');
				if(tp) *tp=0;
				for(i=0;i<scfg.total_libs;i++) {
					if(!chk_ar(&scfg,scfg.lib[i]->ar,&user))
						continue;
					if(!stricmp(scfg.lib[i]->sname,p))
						break;
				}
				if(i<scfg.total_libs) {
					curlib=i;
					success=TRUE;
				}
			}
			if((!success && curdir<0) || (success && tp && *(tp+1))) {
				if(tp)
					p=tp+1;
				for(i=0;i<scfg.total_dirs;i++) {
					if(scfg.dir[i]->lib!=curlib)
						continue;
					if(i!=scfg.sysop_dir && i!=scfg.upload_dir
						&& !chk_ar(&scfg,scfg.dir[i]->ar,&user))
						continue;
					if(!stricmp(scfg.dir[i]->code,p))
						break;
				}
				if(i<scfg.total_dirs) {
					curdir=i;
					success=TRUE;
				} else
					success=FALSE;
			}

			if(success)
				sockprintf(sock,"250 CWD command successful.");
			else {
				sockprintf(sock,"550 %s: No such file or directory.",p);
				curlib=orglib;
				curdir=orgdir;
			}
			continue;
		}

		if(!stricmp(cmd, "PWD") || !stricmp(cmd,"XPWD")) {
			if(curlib<0)
				sockprintf(sock,"257 \"/\" is current directory.");
			else if(curdir<0)
				sockprintf(sock,"257 \"/%s\" is current directory."
					,scfg.lib[curlib]->sname);
			else
				sockprintf(sock,"257 \"/%s/%s\" is current directory."
					,scfg.lib[curlib]->sname,scfg.dir[curdir]->code);
			continue;
		}

		if(!strnicmp(cmd, "MKD", 3) || 
			!strnicmp(cmd,"XMKD",4) || 
			!strnicmp(cmd,"SITE EXEC",9)) {
			lprintf("%04d !SUSPECTED HACK ATTEMPT by %s: '%s'"
				,sock,user.alias,cmd);
			hacklog(&scfg, "FTP", user.alias, cmd, host_name, &ftp.client_addr);
			if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
				PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
		sockprintf(sock,"500 Syntax error: '%s'",cmd);
		lprintf("%04d !UNSUPPORTED COMMAND from %s: '%s'"
			,sock,user.alias,cmd);
#if defined(_DEBUG) && defined(SOCKET_DEBUG_TERMINATE)
	socket_debug[sock]|=SOCKET_DEBUG_TERMINATE;
#endif

	if(transfer_inprogress==TRUE) {
		lprintf("%04d Waiting for transfer to complete...",sock);
		while(transfer_inprogress==TRUE) {
			if(server_socket==INVALID_SOCKET) {
				mswait(2000);	/* allow xfer threads to terminate */
				break;
			}
			if(!transfer_aborted) {
				if(gettimeleft(&scfg,&user,logintime)<1) {
					lprintf("%04d Out of time, disconnecting",sock);
					sockprintf(sock,"421 Sorry, you've run out of time.");
					ftp_close_socket(&data_sock,__LINE__);
					transfer_aborted=TRUE;
				}
				if((time(NULL)-lastactive)>startup->max_inactivity) {
rswindell's avatar
rswindell committed
					lprintf("%04d Disconnecting due to to inactivity",sock);
					sockprintf(sock,"421 Disconnecting due to inactivity (%u seconds)."
						,startup->max_inactivity);
					ftp_close_socket(&data_sock,__LINE__);
					transfer_aborted=TRUE;
				}
		}
		lprintf("%04d Done waiting for transfer to complete",sock);
	}

	/* Update User Statistics */
		logoutuserdat(&scfg, &user, time(NULL), logintime);
rswindell's avatar
rswindell committed
		lprintf("%04d %s logged off",sock,user.alias);
rswindell's avatar
rswindell committed
#ifdef _WIN32
	if(startup->hangup_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
		PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME);
rswindell's avatar
rswindell committed
#endif
#ifdef JAVASCRIPT
	if(js_cx!=NULL) {
		lprintf("%04d JavaScript: Destroying context",sock);
		JS_DestroyContext(js_cx);	/* Free Context */
	}
		lprintf("%04d JavaScript: Destroying runtime",sock);
/*	status(STATUS_WFC); server thread should control status display */
rswindell's avatar
rswindell committed

	if(pasv_sock!=INVALID_SOCKET)
		ftp_close_socket(&pasv_sock,__LINE__);
	if(data_sock!=INVALID_SOCKET)
		ftp_close_socket(&data_sock,__LINE__);
rswindell's avatar
rswindell committed

	socket_debug[sock]&=~SOCKET_DEBUG_CTRL;
#if defined(_DEBUG) && defined(SOCKET_DEBUG_TERMINATE)
	socket_debug[sock]&=~SOCKET_DEBUG_TERMINATE;
#endif
rswindell's avatar
rswindell committed

	tmp_sock=sock;
	ftp_close_socket(&tmp_sock,__LINE__);

	if(active_clients>0)
		active_clients--;
	update_clients();

	thread_down();
	lprintf("%04d CTRL thread terminated (%u clients, %u threads remain)"
		,sock, active_clients, thread_count);
rswindell's avatar
rswindell committed
static void cleanup(int code, int line)
rswindell's avatar
rswindell committed
#ifdef _DEBUG
	lprintf("0000 cleanup called from line %d",line);
#endif
	free_cfg(&scfg);

	if(server_socket!=INVALID_SOCKET)
	if(WSAInitialized && WSACleanup()!=0) 
		lprintf("0000 !WSACleanup ERROR %d",ERROR_VALUE);
rswindell's avatar
rswindell committed
	thread_down();
	status("Down");
	if(code)
		lprintf("#### FTP Server thread terminated (%u threads remain)", thread_count);
	if(startup!=NULL && startup->terminated!=NULL)
		startup->terminated(code);
}

const char* DLLCALL ftp_ver(void)
{
	static char ver[256];
	char compiler[32];

	DESCRIBE_COMPILER(compiler);
	sscanf("$Revision$" + 11, "%s", revision);

	sprintf(ver,"%s %s%s  "
		"Compiled %s %s with %s"
		,FTP_SERVER
#ifdef _DEBUG
		," Debug"
#else
		,""
#endif
		,__DATE__, __TIME__, compiler);

	return(ver);
}

	char			compiler[32];
	SOCKADDR_IN		server_addr;
	SOCKADDR_IN		client_addr;
	socklen_t		client_addr_len;
	SOCKET			client_socket;
	int				i;
	int				result;
	time_t			t;
	time_t			start;
	startup=(ftp_startup_t*)arg;

    if(startup==NULL) {
    	fprintf(stderr, "No startup structure passed!\n");
    	return;
    }

	if(startup->size!=sizeof(ftp_startup_t)) {	/* verify size */
		sbbs_beep(100,500);
		sbbs_beep(300,500);
		sbbs_beep(100,500);
		fprintf(stderr, "Invalid startup structure!\n");
		return;
	}

	/* Setup intelligent defaults */
	if(startup->port==0)					startup->port=IPPORT_FTP;
	if(startup->qwk_timeout==0)				startup->qwk_timeout=600;		/* seconds */
	if(startup->max_inactivity==0)			startup->max_inactivity=300;	/* seconds */
	if(startup->index_file_name[0]==0)		SAFECOPY(startup->index_file_name,"00index");
	if(startup->html_index_file[0]==0)		SAFECOPY(startup->html_index_file,"00index.html");
	if(startup->html_index_script[0]==0) {	SAFECOPY(startup->html_index_script,"ftp-html.js");
											startup->options|=FTP_OPT_HTML_INDEX_FILE;
	}
	if(startup->options&FTP_OPT_HTML_INDEX_FILE)
		startup->options&=~FTP_OPT_NO_JAVASCRIPT;
	else
		startup->options|=FTP_OPT_NO_JAVASCRIPT;
	if(startup->js_max_bytes==0)			startup->js_max_bytes=JAVASCRIPT_MAX_BYTES;
	startup->recycle_now=FALSE;
		memset(&scfg, 0, sizeof(scfg));
		lprintf("Synchronet FTP Server Revision %s%s"
		DESCRIBE_COMPILER(compiler);
		lprintf("Compiled %s %s with %s", __DATE__, __TIME__, compiler);
		srand(time(NULL));	/* Seed random number generator */
		sbbs_random(10);	/* Throw away first number */
rswindell's avatar
rswindell committed
			cleanup(1,__LINE__);
		t=time(NULL);
		lprintf("Initializing on %.24s with options: %lx"
			,CTIME_R(&t,str),startup->options);
		/* Initial configuration and load from CNF files */
		SAFECOPY(scfg.ctrl_dir, startup->ctrl_dir);
		lprintf("Loading configuration files from %s", scfg.ctrl_dir);
		scfg.size=sizeof(scfg);
		SAFECOPY(error,UNKNOWN_LOAD_ERROR);
		if(!load_cfg(&scfg, NULL, TRUE, error)) {
			lprintf("!ERROR %s",error);
			lprintf("!Failed to load configuration files");
rswindell's avatar
rswindell committed
			cleanup(1,__LINE__);
			SAFECOPY(startup->host_name,scfg.sys_inetaddr);
		if(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&FTP_OPT_LOCAL_TIMEZONE)) { 
			if(putenv("TZ=UTC0"))
				lprintf("!putenv() FAILED");
			tzset();

			if((t=checktime())!=0) {   /* Check binary time */
				lprintf("!TIME PROBLEM (%ld)",t);
rswindell's avatar
rswindell committed
				cleanup(1,__LINE__);
		/* Use DATA/TEMP for temp dir - should ch'd to be FTP/HOST specific */
		prep_dir(scfg.data_dir, scfg.temp_dir);
		if(!startup->max_clients) {
			startup->max_clients=scfg.sys_nodes;
			if(startup->max_clients<10)
				startup->max_clients=10;
		}
		lprintf("Maximum clients: %d",startup->max_clients);
		lprintf("Maximum inactivity: %d seconds",startup->max_inactivity);
		strlwr(scfg.sys_id); /* Use lower-case unix-looking System ID for group name */
		for(i=0;i<scfg.total_libs;i++) {
			strlwr(scfg.lib[i]->sname);
			dotname(scfg.lib[i]->sname,scfg.lib[i]->sname);
		}
		for(i=0;i<scfg.total_dirs;i++) 
			strlwr(scfg.dir[i]->code);
		/* open a socket and wait for a client */
		if((server_socket=ftp_open_socket(SOCK_STREAM))==INVALID_SOCKET) {
			lprintf("!ERROR %d opening socket", ERROR_VALUE);
rswindell's avatar
rswindell committed
			cleanup(1,__LINE__);
		lprintf("%04d FTP socket opened",server_socket);
		/*****************************/
		/* Listen for incoming calls */
		/*****************************/
		memset(&server_addr, 0, sizeof(server_addr));
		server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
		server_addr.sin_family = AF_INET;
		server_addr.sin_port   = htons(startup->port);
		if(startup->seteuid!=NULL)
			startup->seteuid(FALSE);
		result=bind(server_socket, (struct sockaddr *) &server_addr,sizeof(server_addr));
		if(startup->seteuid!=NULL)
		if(result!=0) {
			lprintf("%04d !ERROR %d (%d) binding socket to port %u"
				,server_socket, result, ERROR_VALUE,startup->port);
			lprintf("%04d %s", server_socket, BIND_FAILURE_HELP);
rswindell's avatar
rswindell committed
			cleanup(1,__LINE__);
		if((result=listen(server_socket, 1))!= 0) {
			lprintf("%04d !ERROR %d (%d) listening on socket"
				,server_socket, result, ERROR_VALUE);
rswindell's avatar
rswindell committed
			cleanup(1,__LINE__);
		/* signal caller that we've started up successfully */
		if(startup->started!=NULL)
    		startup->started();
		lprintf("%04d FTP Server thread started on port %d",server_socket,startup->port);
		status(STATUS_WFC);
		if(initialized==0) {
			sprintf(path,"%sftpsrvr.rec",scfg.ctrl_dir);
			t=fdate(path);
			if(t!=-1 && t>initialized)
				initialized=t;
		}
		while(server_socket!=INVALID_SOCKET) {
			if(!(startup->options&FTP_OPT_NO_RECYCLE)) {
				sprintf(path,"%sftpsrvr.rec",scfg.ctrl_dir);
				t=fdate(path);
				if(!active_clients && t!=-1 && t>initialized) {
					lprintf("0000 Recycle semaphore file (%s) detected", path);
					initialized=t;
					break;
				}
				if(!active_clients && startup->recycle_now==TRUE) {
					lprintf("0000 Recycle semaphore signaled");
					startup->recycle_now=FALSE;
					break;
				}
			FD_ZERO(&socket_set);
			FD_SET(server_socket,&socket_set);
			if((i=select(server_socket+1,&socket_set,NULL,NULL,&tv))<1) {
				if(i==0) {
					mswait(1);
					continue;
				}
				if(ERROR_VALUE==EINTR)
					lprintf("0000 FTP Server listening interrupted");
				else if(ERROR_VALUE == ENOTSOCK)
            		lprintf("0000 FTP Server sockets closed");
				else
					lprintf("0000 !ERROR %d selecting sockets",ERROR_VALUE);
				break;
			if(server_socket==INVALID_SOCKET)	/* terminated */
				break;

			client_addr_len = sizeof(client_addr);
			client_socket = accept(server_socket, (struct sockaddr *)&client_addr
        		,&client_addr_len);
			if(client_socket == INVALID_SOCKET)
			{
				if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR) 
            		lprintf("0000 FTP socket closed while listening");
				else
					lprintf("0000 !ERROR %d accepting connection", ERROR_VALUE);
				break;
			}
			if(startup->socket_open!=NULL)
				startup->socket_open(TRUE);
			sockets++;

			if(active_clients>=startup->max_clients) {
				lprintf("%04d !MAXMIMUM CLIENTS (%d) reached, access denied"
					,client_socket, startup->max_clients);
				sockprintf(client_socket,"421 Maximum active clients reached, please try again later.");
				mswait(3000);
				ftp_close_socket(&client_socket,__LINE__);
				continue;
			}
			if((ftp=malloc(sizeof(ftp_t)))==NULL) {
				lprintf("%04d !ERROR allocating %d bytes of memory for ftp_t"
					,client_socket,sizeof(ftp_t));
				sockprintf(client_socket,"421 System error, please try again later.");
				mswait(3000);
				ftp_close_socket(&client_socket,__LINE__);
				continue;
			}
			ftp->socket=client_socket;
			ftp->client_addr=client_addr;
			_beginthread (ctrl_thread, 0, ftp);
		}
		if(active_clients) {
			lprintf("0000 Waiting for %d active clients to disconnect...", active_clients);
			start=time(NULL);
			while(active_clients) {
				if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
					lprintf("0000 !TIMEOUT waiting for %d active clients",active_clients);
					break;
				}
				mswait(100);
			}
			lprintf("000 Done waiting");
		}

		if(thread_count>1) {
			lprintf("0000 Waiting for %d threads to terminate...", thread_count-1);
			start=time(NULL);
			while(thread_count>1) {
				if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
					lprintf("0000 !TIMEOUT waiting for %d threads",thread_count-1);
rswindell's avatar
rswindell committed
		cleanup(0,__LINE__);
		if(recycle_server) {
			lprintf("Recycling server...");

    lprintf("#### FTP Server thread terminated (%u threads remain)", thread_count);