Skip to content
Snippets Groups Projects
ftpsrvr.c 107 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 ILLEGAL_FILENAME_CHARS	"\\/|<>+[]:=\";,%"

#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)
        return(0);
    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);
}

static SOCKET open_socket(int type)

	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 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)
		lprintf("%04d !ERROR %d closing socket from line %d",*sock,ERROR_VALUE,line);
	else {
#ifdef _DEBUG
		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->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);
JSContext* js_initcx(SOCKET sock, JSObject** glob)
{
	char		ver[256];
	JSContext*	js_cx;
	JSObject*	js_glob;
	JSObject*	sysobj;
	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((sysobj=js_CreateSystemObject(js_cx, js_glob, &scfg))==NULL) 
		sprintf(ver,"%s v%s",FTP_SERVER,FTP_VERSION);
		val = STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, ver));
		if(!JS_SetProperty(js_cx, sysobj, "version", &val))
			break;

		val = STRING_TO_JSVAL(JS_NewStringCopyZ(js_cx, ftp_ver()));
		if(!JS_SetProperty(js_cx, sysobj, "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* js_glob, 
					   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, js_glob, "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, js_glob, "file_list", &val)) {
			lprintf("%04d !JavaScript FAILED to set file property",sock);
			break;
		}
		val=OBJECT_TO_JSVAL(dir_array);
		if(!JS_SetProperty(js_cx, js_glob, "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, js_glob, "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);

		val=OBJECT_TO_JSVAL(dir_obj);
		if(!JS_SetProperty(js_cx, js_glob, "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,"/");
Loading
Loading full blame...