Skip to content
Snippets Groups Projects
ftpsrvr.c 114 KiB
Newer Older
/* ftpsrvr.c */

/* Synchronet FTP server */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2000 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										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

/* Platform-specific headers */
#ifdef _WIN32
	#include <share.h>		/* SH_DENYNO */
	#include <direct.h>		/* _mkdir/_rmdir() */
	#include <process.h>	/* _beginthread */
	#include <windows.h>	/* required for mmsystem.h */
	#include <mmsystem.h>	/* SND_ASYNC */

#elif defined(__unix__)

	#include <signal.h>		/* signal/SIGPIPE */

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/stat.h>		/* S_IWRITE */

/* Synchronet-specific headers */
rswindell's avatar
rswindell committed
#include "ftpsrvr.h"
#include "telnet.h"

/* Constants */

#define FTP_SERVER				"Synchronet FTP Server"
#ifdef  JAVASCRIPT
#define FTP_VERSION				"1.10"
#else
#define FTP_VERSION				"1.05"
#endif

#define STATUS_WFC				"Listening"

#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		15		/* 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 */

static ftp_startup_t* startup=NULL;
static scfg_t	scfg;
static SOCKET	server_socket=INVALID_SOCKET;
static DWORD	active_clients=0;
static DWORD	sockets=0;
static HANDLE	socket_mutex=NULL;
static BYTE 	socket_debug[20000]={0};

#define	SOCKET_DEBUG_CTRL		(1<<0)	// 0x01
#define SOCKET_DEBUG_CONNECT	(1<<1)	// 0x02
#define SOCKET_DEBUG_READLINE	(1<<2)	// 0x04
#define SOCKET_DEBUG_ACCEPT		(1<<3)	// 0x08
#define SOCKET_DEBUG_DOWNLOAD	(1<<4)	// 0x10
#define SOCKET_DEBUG_SELECT		(1<<5)	// 0x20
#define SOCKET_DEBUG_RECV_CHAR	(1<<6)	// 0x40
#define SOCKET_DEBUG_RECV_BUF	(1<<7)	// 0x80

typedef struct {
	SOCKET			socket;
	SOCKADDR_IN		client_addr;
} ftp_t;


static const char *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);
}

BOOL dir_op(scfg_t* cfg, user_t* user, uint dirnum)
{
	return(user->level>=SYSOP_LEVEL || user->exempt&FLAG('R')
		|| (cfg->dir[dirnum]->op_ar[0] && chk_ar(cfg,cfg->dir[dirnum]->op_ar,user)));
}

static int lprintf(char *fmt, ...)
{
	int		result;
	va_list argptr;
	char sbuf[1024];

    if(startup==NULL || startup->lputs==NULL)
    va_start(argptr,fmt);
    vsprintf(sbuf,fmt,argptr);
    va_end(argptr);
    result=startup->lputs(sbuf);

	return(result);
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
		lprintf("%s %s",WSAData.szDescription, WSAData.szSystemStatus);
		return (TRUE);
	}

    lprintf("!WinSock startup ERROR %d", status);
	return (FALSE);
}

#define winsock_startup()	(TRUE)

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
	    startup->status(str);
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
		startup->clients(active_clients);
}

static void client_on(SOCKET sock, client_t* client)
{
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(TRUE,sock,client);
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(FALSE,sock,NULL);
}

static void thread_up(void)
{
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(TRUE);
}

static void thread_down(void)
{
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(FALSE);
}


	sock=socket(AF_INET, type, IPPROTO_IP);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
		startup->socket_open(TRUE);
	if(sock!=INVALID_SOCKET) {
		sockets++;
#ifdef _DEBUG
		lprintf("%04d Socket opened (%d sockets in use)",sock,sockets);
#endif
	}
	return(sock);
}

#ifdef __BORLANDC__
#pragma argsused
#endif
static int ftp_close_socket(SOCKET* sock, int line)
rswindell's avatar
rswindell committed
#ifndef _WIN32
#define ReleaseMutex(x)
#else
	if((result=WaitForSingleObject(socket_mutex,5000))!=WAIT_OBJECT_0) 
		lprintf("%04d !ERROR %d getting socket mutex from line %d"
			,*sock,ERROR_VALUE,line);

	if(IsBadWritePtr(sock,sizeof(SOCKET))) {
		ReleaseMutex(socket_mutex);
		lprintf("0000 !BAD socket pointer in close_socket from line %d",line);
rswindell's avatar
rswindell committed
	}
#endif
	if((*sock)==INVALID_SOCKET) {
		ReleaseMutex(socket_mutex);
		lprintf("0000 !INVALID_SOCKET in close_socket from line %d",line);
	shutdown(*sock,SHUT_RDWR);	/* required on Unix */

	result=closesocket(*sock);
	if(result==0 && startup!=NULL && startup->socket_open!=NULL) 
		startup->socket_open(FALSE);

	sockets--;

	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
			lprintf("%04d !ERROR %d closing socket from line %d",*sock,ERROR_VALUE,line);
	}
		lprintf("%04d Socket closed (%d sockets in use) from line %d",*sock,sockets,line);
#endif
	*sock=INVALID_SOCKET;
	ReleaseMutex(socket_mutex);

	return(result);
}

static int sockprintf(SOCKET sock, char *fmt, ...)
{
	int		len;
	int		result;
	va_list argptr;
	char	sbuf[1024];

    va_start(argptr,fmt);
    len=vsprintf(sbuf,fmt,argptr);
	if(startup!=NULL && startup->options&FTP_OPT_DEBUG_TX)
		lprintf("%04d TX: %s", sock, sbuf);
	strcat(sbuf,"\r\n");
	len+=2;
    va_end(argptr);
	while((result=send(sock,sbuf,len,0))!=len) {
		if(result==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
			if(ERROR_VALUE==EWOULDBLOCK) {
				mswait(1);
rswindell's avatar
rswindell committed
			if(ERROR_VALUE==ECONNRESET) 
				lprintf("%04d Connection reset by peer on send",sock);
			else if(ERROR_VALUE==ECONNABORTED)
				lprintf("%04d Connection aborted by peer on send",sock);
			else
				lprintf("%04d !ERROR %d sending",sock,ERROR_VALUE);
			return(0);
		}
		lprintf("%04d !ERROR: short send: %d instead of %d",sock,result,len);
	}
	return(len);
}

/************************************************/
/* Truncates white-space chars off end of 'str' */
/************************************************/
{
	uint c;

c=strlen(str);
while(c && (uchar)str[c-1]<=' ') c--;
str[c]=0;
}

/* Returns the directory index of a virtual lib/dir path (e.g. main/games/filename) */
int getdir(char* p, user_t* user)
{
	char*	tp;
	char	path[MAX_PATH+1];
	int		dir;
	int		lib;

	sprintf(path,"%.*s",(int)sizeof(path)-1,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++) {
		if(!chk_ar(&scfg,scfg.lib[lib]->ar,user))
			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 
			&& !chk_ar(&scfg,scfg.dir[dir]->ar,user))
			continue;
		if(!stricmp(scfg.dir[dir]->code,p))
			break;
	}
	if(dir>=scfg.total_dirs) 
		return(-1);

	return(dir);
}

/*********************************/
/* JavaScript Data and Functions */
/*********************************/
#ifdef JAVASCRIPT

JSRuntime* js_runtime=NULL;

static JSBool
js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    uintN		i;
    JSString *	str;
	FILE*	fp;

	if((fp=(FILE*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

    for (i = 0; i < argc; i++) {
		str = JS_ValueToString(cx, argv[i]);
		if (!str)
		    return JS_FALSE;
		fprintf(fp,"%s",JS_GetStringBytes(str));
	}

	return(JS_TRUE);
}

static JSFunctionSpec js_global_functions[] = {
	{"write",           js_write,           1},		/* write to HTML file */
	{0}
};

static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
	char	line[64];
	char	file[MAX_PATH+1];
	char*	warning;
	FILE*	fp;

	fp=(FILE*)JS_GetContextPrivate(cx);
	
	if(report==NULL) {
		lprintf("!JavaScript: %s", message);
		if(fp!=NULL)
			fprintf(fp,"!JavaScript: %s", message);
		return;
    }

	if(report->filename)
		sprintf(file," %s",report->filename);
	else
		file[0]=0;

	if(report->lineno)
		sprintf(line," line %d",report->lineno);
	else
		line[0]=0;

	if(JSREPORT_IS_WARNING(report->flags)) {
		if(JSREPORT_IS_STRICT(report->flags))
			warning="strict warning";
		else
			warning="warning";
	} else
		warning="";

	lprintf("!JavaScript %s%s%s: %s",warning,file,line,message);
	if(fp!=NULL)
		fprintf(fp,"!JavaScript %s%s%s: %s",warning,file,line,message);
static JSClass js_server_class = {
        "FtpServer",0, 
        JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub, 
        JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub 
}; 

static JSClass js_ftp_class = {
        "Ftp",0, 
        JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub, 
        JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub 
}; 

JSContext* js_initcx(SOCKET sock, JSObject** glob, JSObject** ftp)
{
	char		ver[256];
	JSContext*	js_cx;
	JSObject*	js_glob;
	jsval		val;
	BOOL		success=FALSE;
	lprintf("%04d JavaScript: Initializing context",sock);

    if((js_cx = JS_NewContext(js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
		return(NULL);

rswindell's avatar
rswindell committed
	lprintf("%04d JavaScript: Context created",sock);

	JS_BeginRequest(js_cx);	/* Required for multi-thread support */

    JS_SetErrorReporter(js_cx, js_ErrorReporter);

		lprintf("%04d JavaScript: Initializing Global object",sock);
		if((js_glob=js_CreateGlobalObject(js_cx, &scfg))==NULL) 
		if (!JS_DefineFunctions(js_cx, js_glob, js_global_functions)) 
			break;
		lprintf("%04d JavaScript: Initializing System object",sock);
		if(js_CreateSystemObject(js_cx, js_glob, &scfg)==NULL) 
			break;

		if((*ftp=JS_DefineObject(js_cx, js_glob, "ftp", &js_ftp_class
			,NULL,0))==NULL)
			break;

		if((server=JS_DefineObject(js_cx, js_glob, "server", &js_server_class
			,NULL,0))==NULL)
		sprintf(ver,"%s v%s",FTP_SERVER,FTP_VERSION);
		val = STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, ver));
		if(!JS_SetProperty(js_cx, server, "version", &val))
			break;

		val = STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, ftp_ver()));
		if(!JS_SetProperty(js_cx, server, "version_detail", &val))
			break;

		if(glob!=NULL)
			*glob=js_glob;

		success=TRUE;

	} while(0);
	JS_EndRequest(js_cx);		/* Required for multi-thread support */

	if(!success) {
		JS_DestroyContext(js_cx);
		return(NULL);
	}

	return(js_cx);
}

static JSClass js_file_class = {
        JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub, 
        JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub 
}; 

BOOL js_add_file(JSContext* js_cx, JSObject* array, 
				 char* name, char* desc, char* ext_desc,
				 ulong size, ulong credits, 
				 time_t time, time_t uploaded, time_t last_downloaded, 
				 ulong times_downloaded, ulong misc, 
				 char* uploader, char* link)
{
	JSObject*	file;
	jsval		val;
	jsint		index;

	if((file=JS_NewObject(js_cx, &js_file_class, NULL, NULL))==NULL)
		return(FALSE);

	val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, name));
	if(!JS_SetProperty(js_cx, file, "name", &val))
		return(FALSE);

	val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, desc));
	if(!JS_SetProperty(js_cx, file, "description", &val))
		return(FALSE);

	val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, ext_desc));
	if(!JS_SetProperty(js_cx, file, "extended_description", &val))
		return(FALSE);

	val=INT_TO_JSVAL(size);
	if(!JS_SetProperty(js_cx, file, "size", &val))
		return(FALSE);

	val=INT_TO_JSVAL(credits);
	if(!JS_SetProperty(js_cx, file, "credits", &val))
		return(FALSE);

	val=INT_TO_JSVAL(time);
	if(!JS_SetProperty(js_cx, file, "time", &val))
		return(FALSE);

	val=INT_TO_JSVAL(uploaded);
	if(!JS_SetProperty(js_cx, file, "uploaded", &val))
		return(FALSE);

	val=INT_TO_JSVAL(last_downloaded);
	if(!JS_SetProperty(js_cx, file, "last_downloaded", &val))
		return(FALSE);

	val=INT_TO_JSVAL(times_downloaded);
	if(!JS_SetProperty(js_cx, file, "times_downloaded", &val))
		return(FALSE);

	val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, uploader));
	if(!JS_SetProperty(js_cx, file, "uploader", &val))
		return(FALSE);

	if(!JS_SetProperty(js_cx, file, "settings", &val))
	val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, link));
	if(!JS_SetProperty(js_cx, file, "link", &val))
		return(FALSE);

	if(!JS_GetArrayLength(js_cx, array, &index))
		return(FALSE);

	val=OBJECT_TO_JSVAL(file);
	return(JS_SetElement(js_cx, array, index, &val));
}

BOOL js_generate_index(JSContext* js_cx, JSObject* parent, 
					   SOCKET sock, FILE* fp, int lib, int dir, user_t* user)
{
	char		str[256];
	char		path[MAX_PATH+1];
	char		spath[MAX_PATH+1];	/* script path */
	char		vpath[MAX_PATH+1];	/* virtual path */
	char		aliasfile[MAX_PATH+1];
	char		extdesc[513];
	char*		p;
	char*		tp;
	char*		np;
	char		aliasline[512];
	BOOL		success=FALSE;
	FILE*		alias_fp;
	uint		i;
	file_t		f;
	glob_t		g;
	jsval		val;
	jsval		rval;
	JSObject*	lib_obj=NULL;
	JSObject*	dir_obj=NULL;
	JSObject*	file_array=NULL;
	JSObject*	dir_array=NULL;
	JSScript*	js_script=NULL;

	JS_SetContextPrivate(js_cx, fp);

	do {	/* pseudo try/catch */

		if((file_array=JS_NewArrayObject(js_cx, 0, NULL))==NULL) {
			lprintf("%04d !JavaScript FAILED to create file_array",sock);
			break;
		}

		if((dir_array=JS_NewArrayObject(js_cx, 0, NULL))==NULL) {
			lprintf("%04d !JavaScript FAILED to create dir_array",sock);
			break;
		}

		/* Add extension if not specified */
		if(!strchr(startup->html_index_script,BACKSLASH))
			sprintf(spath,"%s%s",scfg.exec_dir,startup->html_index_script);
		else
			sprintf(spath,"%.*s",sizeof(spath)-4,startup->html_index_script);
		if(!strchr(spath,'.'))
			strcat(spath,".js");

		if(!fexist(spath)) {
			lprintf("%04d !HTML JavaScript (%s) doesn't exist",sock,spath);
			break;
		}

		val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, startup->html_index_file));
		if(!JS_SetProperty(js_cx, parent, "html_index_file", &val)) {
			lprintf("%04d !JavaScript FAILED to set html_index_file property",sock);
			break;
		}

		/* file[] */
		val=OBJECT_TO_JSVAL(file_array);
		if(!JS_SetProperty(js_cx, parent, "file_list", &val)) {
			lprintf("%04d !JavaScript FAILED to set file property",sock);
			break;
		}
		val=OBJECT_TO_JSVAL(dir_array);
		if(!JS_SetProperty(js_cx, parent, "dir_list", &val)) {
			lprintf("%04d !JavaScript FAILED to set dir property",sock);
			break;
		}

		if((lib_obj=JS_NewObject(js_cx, &js_file_class, 0, NULL))==NULL) {
			lprintf("%04d !JavaScript FAILED to create lib_obj",sock);
			break;
		}
		val=OBJECT_TO_JSVAL(lib_obj);
		if(!JS_SetProperty(js_cx, parent, "curlib", &val)) {
			lprintf("%04d !JavaScript FAILED to set curlib property",sock);
			break;
		if((dir_obj=JS_NewObject(js_cx, &js_file_class, 0, NULL))==NULL) {
			lprintf("%04d !JavaScript FAILED to create dir_obj",sock);
		if(!JS_SetProperty(js_cx, parent, "curdir", &val)) {
			lprintf("%04d !JavaScript FAILED to set curdir property",sock);
		strcpy(vpath,"/");

		if(lib>=0) { /* root */

			strcat(vpath,scfg.lib[lib]->sname);
			strcat(vpath,"/");

			val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, scfg.lib[lib]->sname));
			if(!JS_SetProperty(js_cx, lib_obj, "name", &val)) {
				lprintf("%04d !JavaScript FAILED to set curlib.name property",sock);
				break;
			}

			val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, scfg.lib[lib]->lname));
			if(!JS_SetProperty(js_cx, lib_obj, "description", &val)) {
				lprintf("%04d !JavaScript FAILED to set curlib.desc property",sock);
				break;
			}

		if(dir>=0) { /* 1st level */
			strcat(vpath,scfg.dir[dir]->code);
			strcat(vpath,"/");

			val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, scfg.dir[dir]->code));
			if(!JS_SetProperty(js_cx, dir_obj, "code", &val)) {
				lprintf("%04d !JavaScript FAILED to set curdir.code property",sock);
				break;
			}

			val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, scfg.dir[dir]->sname));
			if(!JS_SetProperty(js_cx, dir_obj, "name", &val)) {
				lprintf("%04d !JavaScript FAILED to set curdir.name property",sock);
				break;
			}

			val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, scfg.dir[dir]->lname));
			if(!JS_SetProperty(js_cx, dir_obj, "description", &val)) {
				lprintf("%04d !JavaScript FAILED to set curdir.desc property",sock);
				break;
			}

			val=INT_TO_JSVAL(scfg.dir[dir]->misc);
			if(!JS_SetProperty(js_cx, dir_obj, "settings", &val)) {
				lprintf("%04d !JavaScript FAILED to set curdir.misc property",sock);
				break;
			}
		}

		val=STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, vpath));
		if(!JS_SetProperty(js_cx, parent, "path", &val)) {
			lprintf("%04d !JavaScript FAILED to set curdir property",sock);
			break;
		}

		if(lib<0) {	/* root dir */

			/* File Aliases */
			sprintf(path,"%sftpalias.cfg",scfg.ctrl_dir);
			if((alias_fp=fopen(path,"r"))!=NULL) {

				while(!feof(alias_fp)) {
					if(!fgets(aliasline,sizeof(aliasline)-1,alias_fp))
						break;

					p=aliasline;	/* alias pointer */
					while(*p && *p<=' ') p++;

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

					tp=p;		/* terminator pointer */
					while(*tp && *tp>' ') tp++;
					if(*tp) *tp=0;

					np=tp+1;	/* filename pointer */
					while(*np && *np<=' ') np++;

					tp=np;		/* terminator pointer */
					while(*tp && *tp>' ') tp++;
					if(*tp) *tp=0;
					dp=tp+1;	/* description pointer */
					while(*dp && *dp<=' ') dp++;
					truncsp(dp);

					alias_dir=FALSE;

					/* Virtual Path? */
					if(!strnicmp(np,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
						if((dir=getdir(np+strlen(BBS_VIRTUAL_PATH),user))<0)
							continue; /* No access or invalid virtual path */
						tp=strrchr(np,'/');
						if(tp==NULL) 
							continue;
						tp++;
						if(*tp) {
							sprintf(aliasfile,"%s%s",scfg.dir[dir]->path,tp);
							np=aliasfile;
						}
						else 
							alias_dir=TRUE;
					}

					if(!alias_dir && !fexist(np))
						continue;
					if(alias_dir) {
						if(!chk_ar(&scfg,scfg.dir[dir]->ar,user))
							continue;
						sprintf(vpath,"/%s/%s",p,startup->html_index_file);
					js_add_file(js_cx
						,alias_dir ? dir_array : file_array
						,p				/* filename */
						,dp				/* description */
						,NULL			/* extdesc */
						,flength(np)	/* size */
						,0				/* credits */
						,fdate(np)		/* time */
						,fdate(np)		/* uploaded */
						,0				/* last downloaded */
						,0				/* times downloaded */
						,scfg.sys_id	/* uploader */
						);
				}

				fclose(alias_fp);
			}

			/* QWK Packet */
			if(startup->options&FTP_OPT_ALLOW_QWK /* && fexist(qwkfile) */) {
				sprintf(str,"%s.qwk",scfg.sys_id);
				sprintf(vpath,"/%s",str);
				js_add_file(js_cx
					,file_array 
					,str						/* filename */
					,"QWK Message Packet"		/* description */
					,10240						/* size */
					,0							/* credits */
					,time(NULL)					/* time */
					,0							/* uploaded */
					,0							/* last downloaded */
					,0							/* times downloaded */
					,scfg.sys_id				/* uploader */
					);
			}

			/* Library Folders */
			for(i=0;i<scfg.total_libs;i++) {
				if(!chk_ar(&scfg,scfg.lib[i]->ar,user))
					continue;
				sprintf(vpath,"/%s/%s",scfg.lib[i]->sname,startup->html_index_file);
				js_add_file(js_cx
					,dir_array 
					,scfg.lib[i]->sname			/* filename */
					,scfg.lib[i]->lname			/* description */
					,NULL						/* extdesc */
					,0,0,0,0,0,0,0				/* unused */
					,scfg.sys_id				/* uploader */
					);
			}
		} else if(dir<0) {
			for(i=0;i<scfg.total_dirs;i++) {
				if(scfg.dir[i]->lib!=lib)
					continue;
				if(/* i!=scfg.sysop_dir && i!=scfg.upload_dir && */
					!chk_ar(&scfg,scfg.dir[i]->ar,user))
					continue;
				sprintf(vpath,"/%s/%s/%s"
					,scfg.lib[scfg.dir[i]->lib]->sname
					,scfg.dir[i]->code
					,startup->html_index_file);
				js_add_file(js_cx
					,dir_array 
					,scfg.dir[i]->sname			/* filename */
					,scfg.dir[i]->lname			/* description */
					,getfiles(&scfg,i)			/* size */
					,0,0,0,0,0					/* unused */
					,scfg.dir[i]->misc			/* misc */
					,scfg.sys_id				/* uploader */
					);

			}
		} else if(chk_ar(&scfg,scfg.dir[dir]->ar,user)){
			sprintf(path,"%s*",scfg.dir[dir]->path);
			glob(path,0,NULL,&g);
			for(i=0;i<(int)g.gl_pathc;i++) {
				if(isdir(g.gl_pathv[i]))
					continue;
	#ifdef _WIN32
				GetShortPathName(g.gl_pathv[i], str, sizeof(str));
	#else
				strcpy(str,g.gl_pathv[i]);
	#endif
				padfname(getfname(str),f.name);
				strupr(f.name);
				f.dir=dir;
				if(getfileixb(&scfg,&f)) {
					f.size=0; /* flength(g.gl_pathv[i]); */
					getfiledat(&scfg,&f);
					if(f.misc&FM_EXTDESC) {
						extdesc[0]=0;
						getextdesc(&scfg, dir, f.datoffset, extdesc);
						/* Remove Ctrl-A Codes and Ex-ASCII code */
						remove_ctrl_a(extdesc,NULL);
					}
					sprintf(vpath,"/%s/%s/%s"
						,scfg.lib[scfg.dir[dir]->lib]->sname
						,scfg.dir[dir]->code
						,getfname(g.gl_pathv[i]));
					js_add_file(js_cx
						,file_array 
						,getfname(g.gl_pathv[i])	/* filename */
						,f.desc						/* description */
						,f.misc&FM_EXTDESC ? extdesc : NULL
						,f.size						/* size */
						,f.cdt						/* credits */
						,f.date						/* time */
						,f.dateuled					/* uploaded */
						,f.datedled					/* last downloaded */
						,f.timesdled				/* times downloaded */
						,f.uler						/* uploader */
						,getfname(g.gl_pathv[i])	/* link */
						);
				}
			}
			globfree(&g);
		}

		/* RUN SCRIPT */
		if((js_script=JS_CompileFile(js_cx, parent, spath))==NULL) {
			lprintf("%04d !JavaScript FAILED to compile script (%s)",sock,spath);
			break;
		}

		if((success=JS_ExecuteScript(js_cx, parent, js_script, &rval))!=TRUE) {
			lprintf("%04d !JavaScript FAILED to execute script (%s)",sock,spath);
			break;
		}

	} while(0);


	if(js_script!=NULL) 
		JS_DestroyScript(js_cx, js_script);

	JS_DeleteProperty(js_cx, parent, "path");
	JS_DeleteProperty(js_cx, parent, "sort");
	JS_DeleteProperty(js_cx, parent, "reverse");
	JS_DeleteProperty(js_cx, parent, "file_list");
	JS_DeleteProperty(js_cx, parent, "dir_list");
	JS_DeleteProperty(js_cx, parent, "curlib");
	JS_DeleteProperty(js_cx, parent, "curdir");
	JS_DeleteProperty(js_cx, parent, "html_index_file");
static int nopen(char *str, int access)
{
	int file,share,count=0;

    if(access&O_DENYNONE) {
        share=SH_DENYNO;
        access&=~O_DENYNONE; }
    else if(access==O_RDONLY) share=SH_DENYWR;
    else share=SH_DENYRW;
    while(((file=sopen(str,O_BINARY|access,share))==-1)
        && errno==EACCES && count++<LOOP_NOPEN)
    return(file);
}

time_t gettimeleft(scfg_t* cfg, user_t* user, time_t starttime)
{
	time_t	now;
    long    tleft;
	time_t	timeleft;

	now=time(NULL);

	if(user->exempt&FLAG('T')) {   /* Time online exemption */
		timeleft=cfg->level_timepercall[user->level]*60;
		if(timeleft<10)             /* never get below 10 for exempt users */
			timeleft=10; }
	else {
		tleft=(((long)cfg->level_timeperday[user->level]-user->ttoday)
			+user->textra)*60L;
		if(tleft<0) tleft=0;
		if(tleft>cfg->level_timepercall[user->level]*60)
			tleft=cfg->level_timepercall[user->level]*60;
		tleft+=user->min*60L;
		tleft-=now-starttime;
		if(tleft>0x7fffL)
			timeleft=0x7fff;
		else
			timeleft=tleft; }

	return(timeleft);
}

static time_t checktime(void)
{
	struct tm tm;

    memset(&tm,0,sizeof(tm));
    tm.tm_year=94;
    tm.tm_mday=1;
}

BOOL upload_stats(ulong bytes)
{
	if((file=nopen(str,O_RDWR))==-1) 
		return(FALSE);

	lseek(file,20L,SEEK_SET);   /* Skip timestamp, logons and logons today */
	read(file,&val,4);        /* Uploads today         */
	val++;
	lseek(file,-4L,SEEK_CUR);
	write(file,&val,4);
	read(file,&val,4);        /* Upload bytes today    */
	val+=bytes;
	lseek(file,-4L,SEEK_CUR);
	write(file,&val,4);
	close(file);
	return(TRUE);
}

BOOL download_stats(ulong bytes)
{
	if((file=nopen(str,O_RDWR))==-1) 
		return(FALSE);

	lseek(file,28L,SEEK_SET);   /* Skip timestamp, logons and logons today */
	read(file,&val,4);        /* Downloads today         */
	val++;
	lseek(file,-4L,SEEK_CUR);
	write(file,&val,4);
	read(file,&val,4);        /* Download bytes today    */
	val+=bytes;
	lseek(file,-4L,SEEK_CUR);
	write(file,&val,4);
	close(file);
	return(TRUE);
}

void recverror(SOCKET socket, int rd, int line)
		lprintf("%04d Socket closed by peer on receive (line %d)"
			,socket, line);
	else if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
		if(ERROR_VALUE==ECONNRESET) 
			lprintf("%04d Connection reset by peer on receive (line %d)"
				,socket, line);
		else if(ERROR_VALUE==ECONNABORTED) 
			lprintf("%04d Connection aborted by peer on receive (line %d)"
				,socket, line);
			lprintf("%04d !ERROR %d receiving on socket (line %d)"
				,socket, ERROR_VALUE, line);
		lprintf("%04d !ERROR: recv on socket returned unexpected value: %d (line %d)"
			,socket, rd, line);
}

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

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

		i=select(socket+1,&socket_set,NULL,NULL,&tv);

		if(server_socket==INVALID_SOCKET) {
			sockprintf(socket,"421 Server downed, aborting.");
			lprintf("%04d Server downed, aborting.",socket);
			return(0);
		}
		if(i<1) {
				if((time(NULL)-(*lastactive))>startup->max_inactivity) {
					lprintf("%04d Disconnecting due to to inactivity.",socket);
					sockprintf(socket,"421 Disconnecting due to inactivity (%u seconds)."
						,startup->max_inactivity);
					return(0);
				}
				mswait(1);
			recverror(socket,i,__LINE__);
		socket_debug[socket]|=SOCKET_DEBUG_RECV_CHAR;
		socket_debug[socket]&=~SOCKET_DEBUG_RECV_CHAR;
			recverror(socket,i,__LINE__);
		if(ch=='\n' && rd>=1) {
			break;
		}	
		buf[rd++]=ch;
	}
	buf[rd-1]=0;
	
	return(rd);
}

/*****************************************************************************/
/* Returns command line generated from instr with %c replacments             */
/*****************************************************************************/
char * cmdstr(user_t* user, char *instr, char *fpath, char *fspec, char *cmd)
{
	char	str[256];
    int		i,j,len;
#ifdef _WIN32
	char sfpath[MAX_PATH];
#endif

    len=strlen(instr);
    for(i=j=0;i<len;i++) {
        if(instr[i]=='%') {
            i++;
            cmd[j]=0;
            switch(toupper(instr[i])) {
                case 'A':   /* User alias */
                    strcat(cmd,user->alias);
                    break;
                case 'B':   /* Baud (DTE) Rate */
                case 'C':   /* Connect Description */
                case 'D':   /* Connect (DCE) Rate */
                case 'E':   /* Estimated Rate */
                case 'H':   /* Port Handle or Hardware Flow Control */
                case 'P':   /* COM Port */
                case 'R':   /* Rows */
                case 'T':   /* Time left in seconds */
                case '&':   /* Address of msr */
                case 'Y':	/* COMSPEC */
					/* UNSUPPORTED */
					break;
                case 'F':   /* File path */
                    strcat(cmd,fpath);
                    break;
                case 'G':   /* Temp directory */
                    strcat(cmd,scfg.temp_dir);
                    break;
                case 'I':   /* UART IRQ Line */
                    strcat(cmd,ultoa(scfg.com_irq,str,10));
                    break;
                case 'J':
                    strcat(cmd,scfg.data_dir);
                    break;
                case 'K':
                    strcat(cmd,scfg.ctrl_dir);
                    break;
                case 'L':   /* Lines per message */
                    strcat(cmd,ultoa(scfg.level_linespermsg[user->level],str,10));
                    break;
                case 'M':   /* Minutes (credits) for user */
                    strcat(cmd,ultoa(user->min,str,10));
                    break;
                case 'N':   /* Node Directory (same as SBBSNODE environment var) */
                    strcat(cmd,scfg.node_dir);
                    break;
                case 'O':   /* SysOp */
                    strcat(cmd,scfg.sys_op);
                    break;
                case 'Q':   /* QWK ID */
                    strcat(cmd,scfg.sys_id);
                    break;
                case 'S':   /* File Spec */
                    strcat(cmd,fspec);
                    break;
                case 'U':   /* UART I/O Address (in hex) */
                    strcat(cmd,ultoa(scfg.com_base,str,16));
                    break;
                case 'V':   /* Synchronet Version */
                    sprintf(str,"%s%c",VERSION,REVISION);
                    break;
                case 'W':   /* Time-slice API type (mswtype) */
#if 0 //ndef __FLAT__
                    strcat(cmd,ultoa(mswtyp,str,10));
#endif
                    break;
                case 'X':
                    strcat(cmd,scfg.shell[user->shell]->code);
                    break;
                case 'Z':
                    strcat(cmd,scfg.text_dir);
                    break;
				case '~':	/* DOS-compatible (8.3) filename */
#ifdef _WIN32
					strcpy(sfpath,fpath);
					GetShortPathName(fpath,sfpath,sizeof(sfpath));
					strcat(cmd,sfpath);
#else
                    strcat(cmd,fpath);
#endif			
					break;
                case '!':   /* EXEC Directory */
                    strcat(cmd,scfg.exec_dir);
                    break;
                case '#':   /* Node number (same as SBBSNNUM environment var) */
                    sprintf(str,"%d",scfg.node_num);
                    strcat(cmd,str);
                    break;
                case '*':
                    sprintf(str,"%03d",scfg.node_num);
                    strcat(cmd,str);
                    break;
                case '$':   /* Credits */
                    strcat(cmd,ultoa(user->cdt+user->freecdt,str,10));
                    break;
                case '%':   /* %% for percent sign */
                    strcat(cmd,"%");
                    break;
				case '?':	/* Platform */
#ifdef __OS2__
					strcpy(str,"OS2");
#else
					strcpy(str,PLATFORM_DESC);
#endif
					strlwr(str);
					strcat(cmd,str);
					break;
                default:    /* unknown specification */
                    if(isdigit(instr[i])) {
                        sprintf(str,"%0*d",instr[i]&0xf,user->number);
                        strcat(cmd,str); }
                    break; }
            j=strlen(cmd); }
        else
            cmd[j++]=instr[i]; }
    cmd[j]=0;

    return(cmd);
}


{
	if(server_socket!=INVALID_SOCKET) {
    	lprintf("%04d FTP Terminate: closing socket",server_socket);
	    server_socket=INVALID_SOCKET;
    }
}


typedef struct {
	SOCKET	ctrl_sock;
	SOCKET*	data_sock;
	BOOL*	inprogress;
	BOOL*	aborted;
	BOOL	delfile;
	BOOL	tmpfile;
	BOOL	credits;
	BOOL	append;
	long	filepos;
	time_t*	lastactive;
	user_t*	user;
	int		dir;
	char*	desc;
} xfer_t;

static void send_thread(void* arg)
{
	char		buf[8192];
	int			rd;
	int			wr;
	ulong		total=0;
	ulong		dur;
	ulong		cps;
	ulong		length;
	BOOL		error=FALSE;
	FILE*		fp;
	file_t		f;
	xfer_t		xfer;
	time_t		now;
	time_t		start;
	time_t		last_report;

	xfer=*(xfer_t*)arg;

	length=flength(xfer.filename);

	if((fp=fopen(xfer.filename,"rb"))==NULL) {
		lprintf("%04d !DATA ERROR %d opening %s",xfer.ctrl_sock,errno,xfer.filename);
		sockprintf(xfer.ctrl_sock,"450 ERROR %d opening %s.",errno,xfer.filename);
		if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
			remove(xfer.filename);
		*xfer.inprogress=FALSE;
		return;
	}
	thread_up();

	*xfer.inprogress=TRUE;
	*xfer.aborted=FALSE;
	if(startup->options&FTP_OPT_DEBUG_DATA || xfer.filepos)
		lprintf("%04d DATA socket %d sending %s from offset %ld"
			,xfer.ctrl_sock,*xfer.data_sock,xfer.filename,xfer.filepos);

	fseek(fp,xfer.filepos,SEEK_SET);
	last_report=start=time(NULL);
	while(!feof(fp)) {
		now=time(NULL);
		if(total && now>=last_report+XFER_REPORT_INTERVAL) {
			lprintf("%04d Sent %ld bytes (%ld total) of %s (%lu cps)"
				,xfer.ctrl_sock,xfer.filepos+total,length,xfer.filename
				,now-start ? total/(now-start) : total*2);
			last_report=now;
		}

		if(*xfer.aborted==TRUE) {
			lprintf("%04d !DATA Transfer aborted",xfer.ctrl_sock);
			sockprintf(xfer.ctrl_sock,"426 Transfer aborted.");
			error=TRUE;
			break;
		}
		if(server_socket==INVALID_SOCKET) {
			lprintf("%04d !DATA Transfer locally aborted",xfer.ctrl_sock);
			sockprintf(xfer.ctrl_sock,"426 Transfer locally aborted.");
			error=TRUE;
			break;
		}
		rd=fread(buf,sizeof(char),sizeof(buf),fp);
		if(rd<1) /* EOF or READ error */
		wr=send(*xfer.data_sock,buf,rd,0);
		if(wr!=rd) {
			if(wr==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
				if(ERROR_VALUE==ECONNRESET) 
					lprintf("%04d DATA Connection reset by peer, sending on socket %d"
						,xfer.ctrl_sock,*xfer.data_sock);
				else if(ERROR_VALUE==ECONNABORTED) 
					lprintf("%04d DATA Connection aborted by peer, sending on socket %d"
						,xfer.ctrl_sock,*xfer.data_sock);
				else
					lprintf("%04d !DATA ERROR %d sending on data socket %d"
						,xfer.ctrl_sock,ERROR_VALUE,*xfer.data_sock);
				sockprintf(xfer.ctrl_sock,"426 Error %d sending on DATA channel",ERROR_VALUE);
				error=TRUE;
				break;
			}
			if(wr==0) {
				lprintf("%04d !DATA socket %d disconnected",xfer.ctrl_sock, *xfer.data_sock);
				sockprintf(xfer.ctrl_sock,"426 DATA channel disconnected");
				error=TRUE;
				break;
			}
			lprintf("%04d !DATA ERROR sent %d instead of %d on socket %d"
				,xfer.ctrl_sock,wr,rd,*xfer.data_sock);
			sockprintf(xfer.ctrl_sock,"451 Short DATA transfer");
			error=TRUE;
			break;
		}
		total+=wr;
		*xfer.lastactive=time(NULL);	/* exception here */
		mswait(1);
	if((i=ferror(fp))!=0) 
		lprintf("%04d !FILE ERROR %d (%d)",xfer.ctrl_sock,i,errno);

	ftp_close_socket(xfer.data_sock,__LINE__);	/* Signal end of file */
	if(startup->options&FTP_OPT_DEBUG_DATA)
		lprintf("%04d DATA socket closed",xfer.ctrl_sock);
	
	if(!error) {
		dur=time(NULL)-start;
		cps=dur ? total/dur : total*2;
		lprintf("%04d Transfer successful: %lu bytes sent in %lu seconds (%lu cps)"
			,xfer.ctrl_sock
			,total,dur,cps);
		sockprintf(xfer.ctrl_sock,"226 Download complete (%lu cps).",cps);

		if(xfer.dir>=0) {
			memset(&f,0,sizeof(f));
			GetShortPathName(xfer.filename,fname,sizeof(fname));
			strcpy(fname,xfer.filename);
#endif
			padfname(getfname(fname),f.name);
			strupr(f.name);
			f.dir=xfer.dir;
			f.size=total;
			if(getfileixb(&scfg,&f)==TRUE && getfiledat(&scfg,&f)==TRUE) {
				f.timesdled++;
				putfiledat(&scfg,&f);
			lprintf("%04d %s downloaded: %s (%lu times total)"
				,xfer.ctrl_sock
				,xfer.user->alias
				,xfer.filename
				,f.timesdled);
			}
			/* Need to update datedled in index */
		}	

		if(xfer.credits) {
			xfer.user->dls=(ushort)adjustuserrec(&scfg, xfer.user->number,U_DLS,5,1);
			xfer.user->dlb=adjustuserrec(&scfg, xfer.user->number,U_DLB,10,total);
			if(xfer.dir>=0 && !(scfg.dir[xfer.dir]->misc&DIR_FREE) 
				/* && !chk_ar(&scfg, scfg.dir[xfer.dir]->ex_ar, xfer.user) */
				&& !(xfer.user->exempt&FLAG('D')))
				subtract_cdt(&scfg, xfer.user, xfer.credits);
		}
		if(!xfer.tmpfile && !xfer.delfile)
			download_stats(total);
	}

	fclose(fp);
	*xfer.inprogress=FALSE;
	if(xfer.tmpfile) {
		if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES))
			remove(xfer.filename);
	} 
	else if(xfer.delfile && !error)
		remove(xfer.filename);

	thread_down();
}

static void receive_thread(void* arg)
{
	char		buf[8192];
	char		ext[F_EXBSIZE+1];
	char		desc[F_EXBSIZE+1];
	char		cmd[MAX_PATH*2];
	char		tmp[MAX_PATH+1];
	int			rd;
	int			file;
	ulong		total=0;
	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;

	xfer=*(xfer_t*)arg;

	if((fp=fopen(xfer.filename,xfer.append ? "ab" : "wb"))==NULL) {
		lprintf("%04d !DATA ERROR %d opening %s",xfer.ctrl_sock,errno,xfer.filename);
		sockprintf(xfer.ctrl_sock,"450 ERROR %d opening %s.",errno,xfer.filename);
		return;
	}

	thread_up();

	*xfer.inprogress=TRUE;
	*xfer.aborted=FALSE;
	if(xfer.filepos || startup->options&FTP_OPT_DEBUG_DATA)
		lprintf("%04d DATA socket %d receiving from offset %ld"
			,xfer.ctrl_sock,*xfer.data_sock,xfer.filepos);

	fseek(fp,xfer.filepos,SEEK_SET);
	last_report=start=time(NULL);
	while(1) {

		now=time(NULL);
		if(total && now>=last_report+XFER_REPORT_INTERVAL) {
			lprintf("%04d Received %ld bytes of %s (%lu cps)"
				,xfer.ctrl_sock,total,xfer.filename
				,now-start? total/(now-start) : total*2);
			last_report=now;
		}
		if(*xfer.aborted==TRUE) {
			lprintf("%04d !DATA Transfer aborted",xfer.ctrl_sock);
			sockprintf(xfer.ctrl_sock,"426 Transfer aborted.");
			error=TRUE;
			break;
		}
		if(server_socket==INVALID_SOCKET) {
			lprintf("%04d !DATA Transfer locally aborted",xfer.ctrl_sock);
			sockprintf(xfer.ctrl_sock,"426 Transfer locally aborted.");
			error=TRUE;
			break;
		}
		socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_RECV_BUF;
		rd=recv(*xfer.data_sock,buf,sizeof(buf),0);
		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("%04d DATA socket %d closed by client"
						,xfer.ctrl_sock,*xfer.data_sock);
				break;
			}
			if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
				if(ERROR_VALUE==ECONNRESET) 
					lprintf("%04d Connection reset by peer, receiving on socket %d"
						,xfer.ctrl_sock,*xfer.data_sock);
				else if(ERROR_VALUE==ECONNABORTED) 
					lprintf("%04d Connection aborted by peer, receiving on socket %d"
						,xfer.ctrl_sock,*xfer.data_sock);
				else
					lprintf("%04d !DATA ERROR %d receiving on data socket %d"
						,xfer.ctrl_sock,ERROR_VALUE,*xfer.data_sock);
				sockprintf(xfer.ctrl_sock,"426 Error %d receiving on DATA channel"
					,ERROR_VALUE);
				error=TRUE;
				break;
			}
			lprintf("%04d !DATA ERROR recv returned %d on socket %d"
				,xfer.ctrl_sock,rd,*xfer.data_sock);
			sockprintf(xfer.ctrl_sock,"451 Unexpected socket error: %d",rd);
			error=TRUE;
			break;
		}
		fwrite(buf,1,rd,fp);
		total+=rd;
		*xfer.lastactive=time(NULL);
		mswait(1);
	if(error && startup->options&FTP_OPT_DEBUG_DATA)
		lprintf("%04d DATA socket %d closed",xfer.ctrl_sock,*xfer.data_sock);
	
	if(!error) {
		dur=time(NULL)-start;
		cps=dur ? total/dur : total*2;
		lprintf("%04d Transfer successful: %lu bytes received in %lu seconds (%lu cps)"
			,xfer.ctrl_sock
			,total,dur,cps);

		if(xfer.dir>=0) {
			memset(&f,0,sizeof(f));
			GetShortPathName(xfer.filename,fname,sizeof(fname));
#else
			strcpy(fname,xfer.filename);
#endif
			padfname(getfname(fname),f.name);
			strupr(f.name);
			f.dir=xfer.dir;
			if(scfg.dir[f.dir]->misc&DIR_AONLY)  /* Forced anonymous */
				f.misc|=FM_ANON;
			f.cdt=total;
			f.dateuled=time(NULL);
			f.timesdled=0;
			f.datedled=0L;
			f.opencount=0;

			/* Desciption specified with DESC command? */
			if(xfer.desc!=NULL && *xfer.desc!=0)	
				sprintf(f.desc,"%.*s",(int)sizeof(f.desc)-1,xfer.desc);

			/* FILE_ID.DIZ support */
			p=strrchr(f.name,'.');
			if(p!=NULL && scfg.dir[f.dir]->misc&DIR_DIZ) {
				for(i=0;i<scfg.total_fextrs;i++)
					if(!stricmp(scfg.fextr[i]->ext,p+1) 
						&& chk_ar(&scfg,scfg.fextr[i]->ar,xfer.user))
						break;
				if(i<scfg.total_fextrs) {
					sprintf(tmp,"%sFILE_ID.DIZ",scfg.temp_dir);
					remove(tmp);
					system(cmdstr(xfer.user,scfg.fextr[i]->cmd,fname,"FILE_ID.DIZ",cmd));
					if(!fexist(tmp)) {
						sprintf(tmp,"%sDESC.SDI",scfg.temp_dir);
						remove(tmp);
						system(cmdstr(xfer.user,scfg.fextr[i]->cmd,fname,"DESC.SDI",cmd)); 
					}
					if((file=nopen(tmp,O_RDONLY))!=-1) {
						memset(ext,0,sizeof(ext));
						read(file,ext,sizeof(ext)-1);
						for(i=sizeof(ext)-1;i;i--)	/* trim trailing spaces */
							if(ext[i-1]>SP)
								break;
						ext[i]=0;
						if(!f.desc[0]) {			/* use for normal description */
							strcpy(desc,ext);
							strip_exascii(desc);	/* strip extended ASCII chars */
							strip_ctrl(desc);		/* strip control chars */
							for(i=0;desc[i];i++)	/* find approprate first char */
								if(isalnum(desc[i]))
									break;
							sprintf(f.desc,"%.*s",LEN_FDESC,desc+i); 
						}
						close(file);
						remove(tmp);
						f.misc|=FM_EXTDESC; 
					} 
				} 
			} /* FILE_ID.DIZ support */

			if(f.desc[0]==0) 	/* no description given, use (long) filename */
				sprintf(f.desc,"%.*s",(int)sizeof(f.desc)-1,getfname(xfer.filename));

			strcpy(f.uler,xfer.user->alias);
			if(!addfiledat(&scfg,&f))
				lprintf("%04d !ERROR adding file (%s) to database",xfer.ctrl_sock,f.name);

			if(f.misc&FM_EXTDESC)
				putextdesc(&scfg,f.dir,f.datoffset,ext);

			if(scfg.dir[f.dir]->upload_sem[0])
				if((file=sopen(scfg.dir[f.dir]->upload_sem,O_WRONLY|O_CREAT|O_TRUNC,SH_DENYNO))!=-1)
					close(file);
			/**************************/
			/* Update Uploader's Info */
			/**************************/
			xfer.user->uls=(short)adjustuserrec(&scfg, xfer.user->number,U_ULS,5,1);
			xfer.user->ulb=adjustuserrec(&scfg, xfer.user->number,U_ULB,10,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)(f.cdt*(scfg.dir[f.dir]->up_pct/100.0))); 
			}
			upload_stats(total);
		}
		/* Send ACK */
		sockprintf(xfer.ctrl_sock,"226 Upload complete (%lu cps).",cps);
	}

	thread_down();
}



static void filexfer(SOCKADDR_IN* addr, SOCKET ctrl_sock, SOCKET pasv_sock, SOCKET* data_sock
					,char* filename, long filepos, BOOL* inprogress, BOOL* aborted
					,BOOL delfile, BOOL tmpfile
					,time_t* lastactive
					,user_t* user
					,int dir
					,BOOL receiving
					,BOOL credits
					,BOOL append
					,char* desc)
{
	int			result;
	int			addr_len;
	SOCKADDR_IN	server_addr;
	static xfer_t xfer;
	struct timeval	tv;
	fd_set			socket_set;

	if((*inprogress)==TRUE) {
		lprintf("%04d !TRANSFER already in progress",ctrl_sock);
		sockprintf(ctrl_sock,"425 Transfer already in progress.");
		return;
	}
	*inprogress=TRUE;

rswindell's avatar
rswindell committed
	if(*data_sock!=INVALID_SOCKET)
rswindell's avatar
rswindell committed

	if(pasv_sock==INVALID_SOCKET) {	/* !PASV */

		if((*data_sock=socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
			lprintf("%04d !DATA ERROR %d opening socket", ctrl_sock, ERROR_VALUE);
			sockprintf(ctrl_sock,"425 Error %d opening socket",ERROR_VALUE);
			if(tmpfile)
				remove(filename);
			*inprogress=FALSE;
			return;
		}
		if(startup->socket_open!=NULL)
			startup->socket_open(TRUE);
		sockets++;
		if(startup->options&FTP_OPT_DEBUG_DATA)
			lprintf("%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));

		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((WORD)(startup->port-1));	/* 20? */

		if((result=bind(*data_sock, (struct sockaddr *) &server_addr
			,sizeof(server_addr)))!=0) {
			lprintf ("%04d !DATA ERROR %d (%d) binding socket %d"
				,ctrl_sock, result, ERROR_VALUE, *data_sock);
			sockprintf(ctrl_sock,"425 Error %d binding socket",ERROR_VALUE);
			if(tmpfile)
				remove(filename);
			*inprogress=FALSE;
		socket_debug[ctrl_sock]|=SOCKET_DEBUG_CONNECT;
		result=connect(*data_sock, (struct sockaddr *)addr,sizeof(struct sockaddr));
		socket_debug[ctrl_sock]&=~SOCKET_DEBUG_CONNECT;

		if(result!=0) {
			lprintf("%04d !DATA ERROR %d (%d) connecting to client %s port %d on socket %d"
					,ctrl_sock,result,ERROR_VALUE
					,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port),*data_sock);
			sockprintf(ctrl_sock,"425 Error %d connecting to socket",ERROR_VALUE);
			if(tmpfile)
				remove(filename);
			*inprogress=FALSE;
			return;
		}
		if(startup->options&FTP_OPT_DEBUG_DATA)
			lprintf("%04d DATA socket %d connected to %s port %d"
				,ctrl_sock,*data_sock,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port));

	} else {	/* PASV */

		if(startup->options&FTP_OPT_DEBUG_DATA)
			lprintf("%04d PASV DATA socket %d listening on %s port %d"
					,ctrl_sock,pasv_sock,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port));

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

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

		socket_debug[ctrl_sock]|=SOCKET_DEBUG_SELECT;
		result=select(pasv_sock+1,&socket_set,NULL,NULL,&tv);
		socket_debug[ctrl_sock]&=~SOCKET_DEBUG_SELECT;
		if(result<1) {
			lprintf("%04d !PASV select returned %d (error: %d)",ctrl_sock,result,ERROR_VALUE);
			sockprintf(ctrl_sock,"425 Error %d selecting socket for connection",ERROR_VALUE);
			if(tmpfile)
				remove(filename);
			*inprogress=FALSE;
			return;
		}
			
		addr_len=sizeof(SOCKADDR_IN);
		socket_debug[ctrl_sock]|=SOCKET_DEBUG_ACCEPT;
		*data_sock=accept(pasv_sock,(struct sockaddr*)addr,&addr_len);
		socket_debug[ctrl_sock]&=~SOCKET_DEBUG_ACCEPT;

		if(*data_sock==INVALID_SOCKET) {
			lprintf("%04d !PASV DATA ERROR %d accepting connection on socket %d"
				,ctrl_sock,ERROR_VALUE,pasv_sock);
			sockprintf(ctrl_sock,"425 Error %d accepting connection",ERROR_VALUE);
			if(tmpfile)
				remove(filename);
			*inprogress=FALSE;
			return;
		}
		if(startup->socket_open!=NULL)
			startup->socket_open(TRUE);
		sockets++;
		if(startup->options&FTP_OPT_DEBUG_DATA)
			lprintf("%04d PASV DATA socket %d connected to %s port %d"
				,ctrl_sock,*data_sock,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port));
	}


	/* Get Mutex here? */
	memset(&xfer,0,sizeof(xfer));
	xfer.ctrl_sock=ctrl_sock;
	xfer.data_sock=data_sock;
	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;
	xfer.dir=dir;
	xfer.desc=desc;
	sprintf(xfer.filename,"%.*s",(int)sizeof(xfer.filename)-1,filename);
	if(receiving)
		_beginthread(receive_thread,0,(void*)&xfer);
	else
		_beginthread(send_thread,0,(void*)&xfer);
}

/* 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);
}

void parsepath(char** pp, user_t* user, int* curlib, int* curdir)
{
	char*	p;
	char*	tp;
	int		dir=*curdir;
	int		lib=*curlib;

	sprintf(path,"%.*s",(int)sizeof(path)-1,*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,'/');
		if(tp) *tp=0;
		for(lib=0;lib<scfg.total_libs;lib++) {
			if(!chk_ar(&scfg,scfg.lib[lib]->ar,user))
				continue;
			if(!stricmp(scfg.lib[lib]->sname,p))
				break;
		}
		if(lib>=scfg.total_libs) { /* not found */
			*curlib=-1;
			return;
		}
		*curlib=lib;

		if(tp==NULL) {
			*curdir=-1;
			return;
		}

		p=tp+1;
	}

	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 
			&& !chk_ar(&scfg,scfg.dir[dir]->ar,user))
			continue;
		if(!stricmp(scfg.dir[dir]->code,p))
			break;
	}

	if(dir>=scfg.total_dirs)  /* not found */
		return;

	*curdir=dir;

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

BOOL alias(char* fullalias, char* filename, user_t* user, 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) 
		return(result);

	sprintf(alias,"%.*s",(int)sizeof(alias)-1,fullalias);
	p=strrchr(alias+1,'/');
	if(p) {
		*p=0;
		fname=p+1;
	}

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

		p=line;	/* alias */
		while(*p && *p<=' ') p++;
		if(*p==';')	/* comment */
			continue;

		tp=p;		/* terminator */
		while(*tp && *tp>' ') tp++;
		if(*tp) *tp=0;

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

		p=tp+1;		/* filename */
		while(*p && *p<=' ') p++;

		tp=p;		/* terminator */
		while(*tp && *tp>' ') tp++;
		if(*tp) *tp=0;

		if(!strnicmp(p,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
			if((dir=getdir(p+strlen(BBS_VIRTUAL_PATH),user))<0)	{
				lprintf("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];
	sprintf(root,"%.*s",(int)sizeof(root)-1,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* vpath(int lib, int dir, char* str)
{
	strcpy(str,"/");
	if(lib<0)
		return(str);
	strcat(str,scfg.lib[lib]->sname);
	if(dir<0)
		return(str);
	strcat(str,scfg.dir[dir]->code);
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];
	char*		host_name;
	char		host_ip[64];
	char		path[MAX_PATH+1];
	char		local_dir[MAX_PATH+1];
	char		ren_from[MAX_PATH+1]="";
	char		html_index_ext[MAX_PATH+1];
	WORD		port;
	ulong		ip_addr;
	int			addr_len;
	DWORD		h1,h2,h3,h4;
	u_short		p1,p2;	/* For PORT command */
	int			i;
	int			rd;
	int			file;
	int			result;
	int			lib;
	int			dir;
	int			curlib=-1;
	int			curdir=-1;
	int			orglib;
	int			orgdir;
	long		filepos=0L;
	long		timeleft;
	ulong		l;
	ulong		avail;
	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;
	SOCKET		data_sock=INVALID_SOCKET;
	HOSTENT*	host;
	SOCKADDR_IN	addr;
	SOCKADDR_IN	data_addr;
	SOCKADDR_IN	pasv_addr;
	ftp_t		ftp=*(ftp_t*)arg;
	user_t		user;
	time_t		t;
	time_t		now;
	time_t		logintime;
	time_t		lastactive;
	file_t		f;
	node_t		node;
	client_t	client;
	struct tm	tm;
	struct tm *	tm_p;
	struct tm 	cur_tm;
#ifdef JAVASCRIPT
	JSContext*	js_cx;
	JSObject*	js_glob;

	thread_up();

	lastactive=time(NULL);

	sock=ftp.socket;
	data_addr=ftp.client_addr;
	/* Default data port is ctrl port-1 */
	data_addr.sin_port=ntohs(data_addr.sin_port)-1;
	data_addr.sin_port=htons(data_addr.sin_port);
	
	lprintf("%04d CTRL thread started", sock);

	free(arg);

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
#ifdef JAVASCRIPT
	if(((js_cx=js_initcx(sock,&js_glob,&js_ftp))==NULL)) {
		lprintf("%04d !ERROR initializing JavaScript context",sock);
		sockprintf(sock,"425 Error initializing JavaScript context");
		thread_down();
		return;
	}
#endif

	l=1;

	if((i=ioctlsocket(sock, FIONBIO, &l))!=0) {
		lprintf("%04d !ERROR %d (%d) disabling socket blocking"
			,sock, i, ERROR_VALUE);
		sockprintf(sock,"425 Error %d disabling socket blocking"
			,ERROR_VALUE);
		thread_down();
		return;
	}

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

	strcpy(host_ip,inet_ntoa(ftp.client_addr.sin_addr));

	lprintf ("%04d CTRL connection accepted from: %s port %u"
		,sock, host_ip, ntohs(ftp.client_addr.sin_port));

	if(startup->options&FTP_OPT_NO_HOST_LOOKUP)
		host=NULL;
	else
		host=gethostbyaddr ((char *)&ftp.client_addr.sin_addr
			,sizeof(ftp.client_addr.sin_addr),AF_INET);

	if(host!=NULL && host->h_name!=NULL)
		host_name=host->h_name;
	else
		host_name="<no name>";

	lprintf("%04d Host name: %s", sock, host_name);

		lprintf("%04d !Client blocked in ip.can: %s", sock, host_ip);
		sockprintf(sock,"550 Access denied.");
	if(trashcan(&scfg,host_name,"host")) {
		lprintf("%04d !Client blocked in host.can: %s", sock, host_name);
Loading
Loading full blame...