Newer
Older
/* Synchronet FTP server */
/* $Id$ */
// vi: tabstop=4
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* 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. *
****************************************************************************/
#include <stdlib.h> /* ltoa in GNU C lib */
#include <stdarg.h> /* va_list, varargs */
#include <string.h> /* strrchr */
#include <fcntl.h> /* O_WRONLY, O_RDONLY, etc. */
#include <errno.h> /* EACCES */
#include <ctype.h> /* toupper */
#include <sys/types.h>
#include <sys/stat.h>
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbs.h"
#include "text.h" /* TOTAL_TEXT */
#include "js_rtpool.h"
#include "js_request.h"
#include "xpprintf.h" // vasprintf
#include "md5.h"
#define FTP_SERVER "Synchronet FTP Server"
#define STATUS_WFC "Listening"
#define ANONYMOUS "anonymous"
#define BBS_VIRTUAL_PATH "bbs:/""/" /* this is actually bbs:<slash><slash> */
#define LOCAL_FSYS_DIR "local:"
#define BBS_FSYS_DIR "bbs:"
#define BBS_HIDDEN_ALIAS "hidden"
#define TIMEOUT_THREAD_WAIT 60 /* Seconds */
#define TIMEOUT_SOCKET_LISTEN 30 /* Seconds */
#define XFER_REPORT_INTERVAL 60 /* Seconds */
#define INDEX_FNAME_LEN 15
#define NAME_LEN 15 /* User name length for listings */
#define MLSX_TYPE (1<<0)
#define MLSX_PERM (1<<1)
#define MLSX_SIZE (1<<2)
#define MLSX_MODIFY (1<<3)
#define MLSX_OWNER (1<<4)
#define MLSX_UNIQUE (1<<5)
static ftp_startup_t* startup=NULL;
static protected_uint32_t active_clients;
static protected_uint32_t thread_count;
static volatile time_t uptime=0;
static volatile ulong served=0;
static volatile BOOL terminate_server=FALSE;
static char revision[16];
static char *text[TOTAL_TEXT];
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
#ifdef SOCKET_DEBUG
static BYTE socket_debug[0x10000]={0};
#define SOCKET_DEBUG_CTRL (1<<0) /* 0x01 */
#define SOCKET_DEBUG_SEND (1<<1) /* 0x02 */
#define SOCKET_DEBUG_READLINE (1<<2) /* 0x04 */
#define SOCKET_DEBUG_ACCEPT (1<<3) /* 0x08 */
#define SOCKET_DEBUG_SENDTHREAD (1<<4) /* 0x10 */
#define SOCKET_DEBUG_TERMINATE (1<<5) /* 0x20 */
#define SOCKET_DEBUG_RECV_CHAR (1<<6) /* 0x40 */
#define SOCKET_DEBUG_FILEXFER (1<<7) /* 0x80 */
char* genvpath(int lib, int dir, char* str);
typedef struct {
SOCKET socket;
static const char *ftp_mon[]={"Jan","Feb","Mar","Apr","May","Jun"
,"Jul","Aug","Sep","Oct","Nov","Dec"};
BOOL direxist(char *dir)
{
if(access(dir,0)==0)
return(TRUE);
else
return(FALSE);
}
BOOL dir_op(scfg_t* cfg, user_t* user, client_t* client, uint dirnum)
return(user->level>=SYSOP_LEVEL
|| (cfg->dir[dirnum]->op_ar!=NULL && cfg->dir[dirnum]->op_ar[0] && chk_ar(cfg,cfg->dir[dirnum]->op_ar,user,client)));
#if defined(__GNUC__) // Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
static int lprintf(int level, const char *fmt, ...)
{
va_list argptr;
char sbuf[1024];
va_start(argptr,fmt);
vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
sbuf[sizeof(sbuf)-1]=0;
if(level <= LOG_ERR) {
char errmsg[sizeof(sbuf)+16];
SAFEPRINTF(errmsg, "ftp %s", sbuf);
errorlog(&scfg, startup==NULL ? NULL:startup->host_name, errmsg);
if(startup!=NULL && startup->errormsg!=NULL)
startup->errormsg(startup->cbdata,level,errmsg);
}
if(startup==NULL || startup->lputs==NULL || level > startup->log_level)
return(0);
#if defined(_WIN32)
if(IsBadCodePtr((FARPROC)startup->lputs))
return(0);
#endif
return startup->lputs(startup->cbdata,level,sbuf);
}
#ifdef _WINSOCKAPI_
static WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL WSAInitialized=FALSE;
static BOOL winsock_startup(void)
{
int status; /* Status Code */
if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
WSAInitialized=TRUE;
lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
#else /* No WINSOCK */
#define winsock_startup() (TRUE)
#define SOCKLIB_DESC NULL
#endif
static void status(char* str)
{
if(startup!=NULL && startup->status!=NULL)
startup->status(startup->cbdata,str);
}
static void update_clients(void)
{
if(startup!=NULL && startup->clients!=NULL)
startup->clients(startup->cbdata,protected_uint32_value(active_clients));
static void client_on(SOCKET sock, client_t* client, BOOL update)
{
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,TRUE,sock,client,update);
}
static void client_off(SOCKET sock)
{
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
static void thread_up(BOOL setuid)
{
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,TRUE, setuid);
static int32_t thread_down(void)
int32_t count = protected_uint32_adjust(&thread_count,-1);
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,FALSE, FALSE);
return count;
char error[256];
startup->socket_open(startup->cbdata,TRUE);
if(set_socket_options(&scfg, sock, "FTP", error, sizeof(error)))
lprintf(LOG_ERR,"%04d !ERROR %s",sock, error);
}
static void ftp_close_socket_cb(SOCKET sock, void *cbdata)
{
if(startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,FALSE);
}
static SOCKET ftp_open_socket(int domain, int type)
{
SOCKET sock;
sock=socket(domain, type, IPPROTO_IP);
if(sock != INVALID_SOCKET)
ftp_open_socket_cb(sock, NULL);
return(sock);
}
#ifdef __BORLANDC__
#pragma argsused
#endif
static int ftp_close_socket(SOCKET* sock, CRYPT_SESSION *sess, int line)
if (*sess != -1) {
cryptDestroySession(*sess);
*sess = -1;
}
lprintf(LOG_WARNING,"0000 !INVALID_SOCKET in close_socket from line %u",line);
shutdown(*sock,SHUT_RDWR); /* required on Unix */
if(startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,FALSE);

rswindell
committed
if(result!=0) {
if(ERROR_VALUE!=ENOTSOCK)
lprintf(LOG_WARNING,"%04d !ERROR %d closing socket from line %u",*sock,ERROR_VALUE,line);
*sock=INVALID_SOCKET;
return(result);
}
#define GCES(status, sock, session, estr, action) do { \
int GCES_level; \
get_crypt_error_string(status, session, &estr, action, &GCES_level);\
if (estr != NULL) { \
lprintf(GCES_level, "%04d TLS %s", sock, estr); \
free_crypt_attrstr(estr); \
estr=NULL; \
#if defined(__GNUC__) // Catch printf-format errors with sockprintf
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
#endif
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...)
int maxlen;
int result;
va_list argptr;
char sbuf[1024];
fd_set socket_set;
struct timeval tv;
len=vsnprintf(sbuf,maxlen=sizeof(sbuf)-2,fmt,argptr);
va_end(argptr);
if(len<0 || len>maxlen) /* format error or output truncated */
len=maxlen;
if(startup!=NULL && startup->options&FTP_OPT_DEBUG_TX)
lprintf(LOG_DEBUG,"%04d TX%s: %.*s", sock, sess != -1 ? "S" : "", len, sbuf);
memcpy(sbuf+len,"\r\n",2);
if(sock==INVALID_SOCKET) {
lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockprintf");
return(0);
}
/* Check socket for writability (using select) */
tv.tv_usec=0;
FD_ZERO(&socket_set);
FD_SET(sock,&socket_set);
if((result=select(sock+1,NULL,&socket_set,NULL,&tv))<1) {
lprintf(LOG_WARNING,"%04d !TIMEOUT selecting socket for send"
lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for send"
,sock, ERROR_VALUE);
if (sess != -1) {
int tls_sent;
int sent = 0;
while (sent < len) {
result = cryptPushData(sess, sbuf+sent, len-sent, &tls_sent);
if (result == CRYPT_OK)
sent += tls_sent;
else {
if (result != CRYPT_ERROR_TIMEOUT)
return 0;
return 0;
}
}
}
else {
while((result=sendsocket(sock,sbuf,len))!=len) {
if(result==SOCKET_ERROR) {
if(ERROR_VALUE==EWOULDBLOCK) {
YIELD();
continue;
}
if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_WARNING,"%04d Connection reset by peer on send",sock);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_WARNING,"%04d Connection aborted by peer on send",sock);
else
lprintf(LOG_WARNING,"%04d !ERROR %d sending",sock,ERROR_VALUE);
return(0);
}
lprintf(LOG_WARNING,"%04d !ERROR: short send: %u instead of %u",sock,result,len);
}
}
return(len);
}
/* Returns the directory index of a virtual lib/dir path (e.g. main/games/filename) */
{
char* tp;
char path[MAX_PATH+1];
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++) {
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
if(!stricmp(scfg.dir[dir]->code_suffix,p))
break;
}
if(dir>=scfg.total_dirs)
return(-1);
return(dir);
}
/*********************************/
/* JavaScript Data and Functions */
/*********************************/
#ifdef JAVASCRIPT
js_server_props_t js_server_props;
js_write(JSContext *cx, uintN argc, jsval *arglist)
jsval *argv=JS_ARGV(cx, arglist);
JSString* str=NULL;
jsrefcount rc;
size_t len;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
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;
JSSTRING_TO_MSTRING(cx, str, p, &len);
HANDLE_PENDING(cx, p);
rc=JS_SUSPENDREQUEST(cx);
if(p) {
fwrite(p, len, 1, fp);
free(p);
}
JS_RESUMEREQUEST(cx, rc);
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
return(JS_TRUE);
}
static JSBool
js_writeln(JSContext *cx, uintN argc, jsval *arglist)
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((fp=(FILE*)JS_GetContextPrivate(cx))==NULL)
return(JS_FALSE);
rc=JS_SUSPENDREQUEST(cx);
fprintf(fp,"\r\n");
JS_RESUMEREQUEST(cx, rc);
return(JS_TRUE);
}
static JSFunctionSpec js_global_functions[] = {
{"write", js_write, 1}, /* write to HTML file */
{"writeln", js_writeln, 1}, /* write to HTML file */
{"print", js_writeln, 1}, /* alias for writeln */
{0}
};
static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
char line[64];
char file[MAX_PATH+1];
char* warning;
int log_level;
lprintf(LOG_ERR,"!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 %u",report->lineno);
else
line[0]=0;
if(JSREPORT_IS_WARNING(report->flags)) {
if(JSREPORT_IS_STRICT(report->flags))
warning="strict warning";
else
warning="warning";
log_level=LOG_WARNING;
} else {
log_level=LOG_ERR;
lprintf(log_level,"!JavaScript %s%s%s: %s",warning,file,line,message);
if(fp!=NULL)
fprintf(fp,"!JavaScript %s%s%s: %s",warning,file,line,message);
js_initcx(JSRuntime* runtime, SOCKET sock, JSObject** glob, JSObject** ftp, js_callback_t* cb)
BOOL rooted=FALSE;
lprintf(LOG_DEBUG,"%04d JavaScript: Initializing context (stack: %lu bytes)"
,sock,startup->js.cx_stack);
if((js_cx = JS_NewContext(runtime, startup->js.cx_stack))==NULL)
JS_BEGINREQUEST(js_cx);
lprintf(LOG_DEBUG,"%04d JavaScript: Context created",sock);
JS_SetErrorReporter(js_cx, js_ErrorReporter);
memset(cb, 0, sizeof(js_callback_t));
/* ToDo: call js_CreateCommonObjects() instead */
lprintf(LOG_DEBUG,"%04d JavaScript: Initializing Global object",sock);
if(!js_CreateGlobalObject(js_cx, &scfg, NULL, &startup->js, glob))
rooted=TRUE;
if(!JS_DefineFunctions(js_cx, *glob, js_global_functions))
break;
/* Internal JS Object */
if(js_CreateInternalJsObject(js_cx, *glob, cb, &startup->js)==NULL)
lprintf(LOG_DEBUG,"%04d JavaScript: Initializing System object",sock);
if(js_CreateSystemObject(js_cx, *glob, &scfg, uptime, startup->host_name, SOCKLIB_DESC)==NULL)
break;
if((*ftp=JS_DefineObject(js_cx, *glob, "ftp", NULL
,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL)
break;
if(js_CreateServerObject(js_cx,*glob,&js_server_props)==NULL)
break;
success=TRUE;
} while(0);
if(rooted)
JS_RemoveObjectRoot(js_cx, glob);
JS_ENDREQUEST(js_cx);
JS_DestroyContext(js_cx);
return(NULL);
}
return(js_cx);
}
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)

rswindell
committed
jsuint index;
if(uploaded == 0)
uploaded = time;
if((file=JS_NewObject(js_cx, NULL, NULL, NULL))==NULL)
if((js_str=JS_NewStringCopyZ(js_cx, name))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, file, "name", &val))
return(FALSE);
if((js_str=JS_NewStringCopyZ(js_cx, desc))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, file, "description", &val))
return(FALSE);
if((js_str=JS_NewStringCopyZ(js_cx, ext_desc))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
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=DOUBLE_TO_JSVAL((double)time);
if(!JS_SetProperty(js_cx, file, "time", &val))
return(FALSE);
val=INT_TO_JSVAL((int32)uploaded);
if(!JS_SetProperty(js_cx, file, "uploaded", &val))
return(FALSE);
val=INT_TO_JSVAL((int32)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);
if((js_str=JS_NewStringCopyZ(js_cx, uploader))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, file, "uploader", &val))
return(FALSE);
val=INT_TO_JSVAL(misc);
if(!JS_SetProperty(js_cx, file, "settings", &val))
return(FALSE);
if((js_str=JS_NewStringCopyZ(js_cx, link))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
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, client_t* client)
{
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];
BOOL alias_dir;
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;
JSObject* js_script=NULL;
long double start=xp_timer();
jsrefcount rc;
lprintf(LOG_DEBUG,"%04d <%s> JavaScript: Generating HTML Index for %s"
,sock, user->alias, genvpath(lib,dir,str));
JS_SetContextPrivate(js_cx, fp);
do { /* pseudo try/catch */
if((file_array=JS_NewArrayObject(js_cx, 0, NULL))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to create file_array",sock, user->alias);
/* file[] */
val=OBJECT_TO_JSVAL(file_array);
if(!JS_SetProperty(js_cx, parent, "file_list", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set file property",sock, user->alias);
break;
}
if((dir_array=JS_NewArrayObject(js_cx, 0, NULL))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to create dir_array",sock, user->alias);
/* dir[] */
val=OBJECT_TO_JSVAL(dir_array);
if(!JS_SetProperty(js_cx, parent, "dir_list", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set dir property",sock, user->alias);
break;
}
rc=JS_SUSPENDREQUEST(js_cx);
if(strcspn(startup->html_index_script,"/\\")==strlen(startup->html_index_script)) {
sprintf(spath,"%s%s",scfg.mods_dir,startup->html_index_script);
if(scfg.mods_dir[0]==0 || !fexist(spath))
sprintf(spath,"%s%s",scfg.exec_dir,startup->html_index_script);
} else
sprintf(spath,"%.*s",(int)sizeof(spath)-4,startup->html_index_script);
/* Add extension if not specified */
if(!strchr(spath,'.'))
strcat(spath,".js");
if(!fexist(spath)) {
JS_RESUMEREQUEST(js_cx, rc);
lprintf(LOG_ERR,"%04d <%s> !HTML JavaScript (%s) doesn't exist",sock,user->alias, spath);
JS_RESUMEREQUEST(js_cx, rc);
if((js_str=JS_NewStringCopyZ(js_cx, startup->html_index_file))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, parent, "html_index_file", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set html_index_file property",sock, user->alias);
break;
}
/* curlib */
if((lib_obj=JS_NewObject(js_cx, NULL, 0, NULL))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to create lib_obj",sock, user->alias);
break;
}
val=OBJECT_TO_JSVAL(lib_obj);
if(!JS_SetProperty(js_cx, parent, "curlib", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curlib property",sock, user->alias);
/* curdir */
if((dir_obj=JS_NewObject(js_cx, NULL, 0, NULL))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to create dir_obj",sock, user->alias);
val=OBJECT_TO_JSVAL(dir_obj);
if(!JS_SetProperty(js_cx, parent, "curdir", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curdir property",sock, user->alias);
if(lib>=0) { /* root */
strcat(vpath,scfg.lib[lib]->sname);
if((js_str=JS_NewStringCopyZ(js_cx, scfg.lib[lib]->sname))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, lib_obj, "name", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curlib.name property",sock, user->alias);
break;
}
if((js_str=JS_NewStringCopyZ(js_cx, scfg.lib[lib]->lname))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, lib_obj, "description", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curlib.desc property",sock, user->alias);
break;
}
if(dir>=0) { /* 1st level */
strcat(vpath,scfg.dir[dir]->code_suffix);
strcat(vpath,"/");
if((js_str=JS_NewStringCopyZ(js_cx, scfg.dir[dir]->code))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, dir_obj, "code", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curdir.code property",sock, user->alias);
break;
}
if((js_str=JS_NewStringCopyZ(js_cx, scfg.dir[dir]->sname))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, dir_obj, "name", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curdir.name property",sock, user->alias);
break;
}
if((js_str=JS_NewStringCopyZ(js_cx, scfg.dir[dir]->lname))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, dir_obj, "description", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curdir.desc property",sock, user->alias);
break;
}
val=INT_TO_JSVAL(scfg.dir[dir]->misc);
if(!JS_SetProperty(js_cx, dir_obj, "settings", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curdir.misc property",sock, user->alias);
break;
}
if((js_str=JS_NewStringCopyZ(js_cx, vpath))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, parent, "path", &val)) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to set curdir property",sock, user->alias);
break;
}
if(lib<0) { /* root dir */
rc=JS_SUSPENDREQUEST(js_cx);
/* 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),alias_fp))
break;
p=aliasline; /* alias pointer */
SKIP_WHITESPACE(p);
if(*p==';') /* comment */
continue;
tp=p; /* terminator pointer */
FIND_WHITESPACE(tp);
if(*tp) *tp=0;
np=tp+1; /* filename pointer */
SKIP_WHITESPACE(np);
tp=np; /* terminator pointer */
FIND_WHITESPACE(tp);
if(*tp) *tp=0;
dp=tp+1; /* description pointer */
SKIP_WHITESPACE(dp);
truncsp(dp);
if(stricmp(dp,BBS_HIDDEN_ALIAS)==0)
continue;
alias_dir=FALSE;
/* Virtual Path? */
if(!strnicmp(np,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
if((dir=getdir(np+strlen(BBS_VIRTUAL_PATH),user,client))<0)
continue; /* No access or invalid virtual path */
tp=strrchr(np,'/');
if(tp==NULL)
continue;
tp++;
if(*tp) {
SAFEPRINTF2(aliasfile,"%s%s",scfg.dir[dir]->path,tp);
np=aliasfile;
}
else
alias_dir=TRUE;
}
if(!alias_dir && !fexist(np))
continue;
SAFEPRINTF2(vpath,"/%s/%s",p,startup->html_index_file);
JS_RESUMEREQUEST(js_cx, rc);
,alias_dir ? dir_array : file_array
,dp /* description */
,NULL /* extdesc */
,flength(np) /* size */
,fdate(np) /* time */
,0 /* last downloaded */
,0 /* times downloaded */
,0 /* misc */
,vpath /* link */
rc=JS_SUSPENDREQUEST(js_cx);
JS_RESUMEREQUEST(js_cx, rc);
/* QWK Packet */
if(startup->options&FTP_OPT_ALLOW_QWK /* && fexist(qwkfile) */) {
sprintf(str,"%s.qwk",scfg.sys_id);
SAFEPRINTF(vpath,"/%s",str);
js_add_file(js_cx
,file_array
,str /* filename */
,"QWK Message Packet" /* description */
,NULL /* extdesc */
,time(NULL) /* time */
,0 /* uploaded */
,0 /* last downloaded */
,0 /* times downloaded */
,0 /* misc */
,str /* link */
);
}
/* Library Folders */
for(i=0;i<scfg.total_libs;i++) {
SAFEPRINTF2(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 */
,vpath /* link */
for(i=0;i<scfg.total_dirs;i++) {
if(scfg.dir[i]->lib!=lib)
continue;
if(/* i!=scfg.sysop_dir && i!=scfg.upload_dir && */
SAFEPRINTF3(vpath,"/%s/%s/%s"
,scfg.dir[i]->code_suffix
,startup->html_index_file);
js_add_file(js_cx
,dir_array
,scfg.dir[i]->sname /* filename */
,NULL /* extdesc */
,getfiles(&scfg,i) /* size */
,0,0,0,0,0 /* unused */
,scfg.dir[i]->misc /* misc */
,vpath /* link */
SAFEPRINTF(path,"%s*",scfg.dir[dir]->path);
rc=JS_SUSPENDREQUEST(js_cx);
time_t start = time(NULL);
glob(path, GLOB_MARK, NULL, &g);
if(*lastchar(g.gl_pathv[i]) == '/') /* is directory */
continue;
#ifdef _WIN32
GetShortPathName(g.gl_pathv[i], str, sizeof(str));
#else
SAFECOPY(str,g.gl_pathv[i]);
memset(&f, 0, sizeof(f));
padfname(getfname(str),f.name);
f.dir=dir;
BOOL filedat;
if((filedat=getfileixb(&scfg,&f))==FALSE
&& !(startup->options&FTP_OPT_DIR_FILES)
&& !(scfg.dir[dir]->misc&DIR_FILES))
continue;
if(filedat) {
if(!getfiledat(&scfg,&f))
continue;
} else {
f.size = f.cdt;
f.date = f.dateuled;
if(!filedat || (scfg.dir[dir]->misc&DIR_FCHK)) {
struct stat st;
if(stat(g.gl_pathv[i], &st) != 0)
continue;
f.cdt = st.st_size;
f.size = st.st_size;
f.date = (time32_t)st.st_mtime;
}
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
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,extdesc);
}
SAFEPRINTF3(vpath,"/%s/%s/%s"
,scfg.lib[scfg.dir[dir]->lib]->sname
,scfg.dir[dir]->code_suffix
,getfname(g.gl_pathv[i]));
JS_RESUMEREQUEST(js_cx, rc);
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.misc /* misc */
,f.uler /* uploader */
,getfname(g.gl_pathv[i]) /* link */
);
rc=JS_SUSPENDREQUEST(js_cx);
lprintf(LOG_INFO, "%04d <%s> JavaScript array of /%s/%s (%lu files) created in %ld seconds"
,sock, user->alias, scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix
,(ulong)g.gl_pathc,(long)time(NULL) - start);
JS_RESUMEREQUEST(js_cx, rc);
JS_ClearPendingException(js_cx);
if((js_script=JS_CompileFile(js_cx, parent, spath))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to compile script (%s)",sock, user->alias, spath);
js_PrepareToExecute(js_cx, parent, spath, /* startup_dir: */NULL, parent);
if((success=JS_ExecuteScript(js_cx, parent, js_script, &rval))!=TRUE) {
lprintf(LOG_ERR,"%04d <%s> !JavaScript FAILED to execute script (%s)",sock, user->alias, spath);
lprintf(LOG_DEBUG,"%04d <%s> JavaScript: Done executing script: %s (%.2Lf seconds)"
,sock, user->alias, spath, xp_timer()-start);
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");
#endif /* ifdef JAVASCRIPT */
BOOL upload_stats(ulong bytes)
{
char str[MAX_PATH+1];

rswindell
committed
sprintf(str,"%sdsts.dab",scfg.ctrl_dir);
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)
{
char str[MAX_PATH+1];

rswindell
committed
sprintf(str,"%sdsts.dab",scfg.ctrl_dir);
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(LOG_NOTICE,"%04d Socket closed by peer on receive (line %u)"
lprintf(LOG_NOTICE,"%04d Connection reset by peer on receive (line %u)"
lprintf(LOG_NOTICE,"%04d Connection aborted by peer on receive (line %u)"
lprintf(LOG_NOTICE,"%04d !ERROR %d receiving on socket (line %u)"
lprintf(LOG_WARNING,"%04d !ERROR: recv on socket returned unexpected value: %d (line %u)"
static int sock_recvbyte(SOCKET sock, CRYPT_SESSION sess, char *buf, time_t *lastactive)

rswindell
committed
fd_set socket_set;
if(ftp_set==NULL || terminate_server) {
sockprintf(sock,sess,"421 Server downed, aborting.");
lprintf(LOG_WARNING,"%04d Server downed, aborting",sock);
return(0);
}
if (sess > -1) {
/* Try a read with no timeout first. */
if ((ret = cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, 0)) != CRYPT_OK)
GCES(ret, sock, sess, estr, "setting read timeout");
while (1) {
ret = cryptPopData(sess, buf, 1, &len);
/* Successive reads will be with the full timeout after a select() */
cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity);
switch(ret) {
case CRYPT_OK:
break;
case CRYPT_ERROR_TIMEOUT:
if (!first) {
GCES(ret, sock, sess, estr, "popping data");
return -1;
}
break;
if (len)
return len;
if((time(NULL)-(*lastactive))>startup->max_inactivity) {
lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock);
sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)."
,startup->max_inactivity);
return(0);
}

rswindell
committed

rswindell
committed

rswindell
committed

rswindell
committed
if(i<1) {
if(i==0) {
if((time(NULL)-(*lastactive))>startup->max_inactivity) {
lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock);
sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)."
,startup->max_inactivity);
return(0);
}
continue;
}
recverror(sock,i,__LINE__);
return(i);
}
}
else {
while (1) {
tv.tv_sec=startup->max_inactivity;
tv.tv_usec=0;
FD_ZERO(&socket_set);
FD_SET(sock,&socket_set);
i=select(sock+1,&socket_set,NULL,NULL,&tv);
if(i<1) {
if(i==0) {
if((time(NULL)-(*lastactive))>startup->max_inactivity) {
lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock);
sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)."
,startup->max_inactivity);
return(0);
}
continue;
#ifdef SOCKET_DEBUG_RECV_CHAR
socket_debug[sock]|=SOCKET_DEBUG_RECV_CHAR;
#endif
i=recv(sock, buf, 1, 0);
#ifdef SOCKET_DEBUG_RECV_CHAR
socket_debug[sock]&=~SOCKET_DEBUG_RECV_CHAR;
#endif
return i;
}
}
int sockreadline(SOCKET socket, CRYPT_SESSION sess, char* buf, int len, time_t* lastactive)
{
char ch;
int i,rd=0;
buf[0]=0;
if(socket==INVALID_SOCKET) {
lprintf(LOG_WARNING,"INVALID SOCKET in call to sockreadline");
return(0);
}
while(rd<len-1) {
i = sock_recvbyte(socket, sess, &ch, lastactive);
if(i<1) {
if (sess != -1)
recverror(socket,i,__LINE__);

rswindell
committed
return(i);
}
if(ch=='\n' /* && rd>=1 */) { /* Mar-9-2003: terminate on sole LF */
break;
}
buf[rd++]=ch;
}
if(rd>0 && buf[rd-1]=='\r')
buf[rd-1]=0;
else
buf[rd]=0;
void DLLCALL ftp_terminate(void)
terminate_server=TRUE;
int ftp_remove(SOCKET sock, int line, const char* fname, const char* username)
{
int ret=0;
if(fexist(fname) && (ret=remove(fname))!=0) {
if(fexist(fname)) // In case there was a race condition (other host deleted file first)
lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) (line %d) removing file: %s", sock, username, errno, STRERROR(errno), line, fname);
return ret;
}
BOOL* inprogress;
BOOL* aborted;
BOOL delfile;
BOOL tmpfile;
BOOL credits;
BOOL append;
long filepos;
char filename[MAX_PATH+1];
time_t* lastactive;
user_t* user;
client_t* client;
int dir;
char* desc;
} xfer_t;
static void send_thread(void* arg)
{
char buf[8192];
char fname[MAX_PATH+1];
char str[128];
char username[128];
long mod;
ulong l;
ulong last_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;
socklen_t addr_len;
fd_set socket_set;
struct timeval tv;
free(arg);
SetThreadName("sbbs/ftpSend");
thread_up(TRUE /* setuid */);
length=flength(xfer.filename);
if((fp=fnopen(NULL,xfer.filename,O_RDONLY|O_BINARY))==NULL /* non-shareable open failed */
&& (fp=fopen(xfer.filename,"rb"))==NULL) { /* shareable open failed */
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
,xfer.ctrl_sock, xfer.user->alias, errno, strerror(errno), __LINE__, xfer.filename);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 ERROR %d (%s) opening %s", errno, strerror(errno), xfer.filename);
if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
thread_down();
#ifdef SOCKET_DEBUG_SENDTHREAD
socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SENDTHREAD;
#endif
if(startup->options&FTP_OPT_DEBUG_DATA || xfer.filepos)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d sending %s from offset %lu"
,xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos);
fseek(fp,xfer.filepos,SEEK_SET);
last_report=start=time(NULL);
while((xfer.filepos+total)<length) {
/* Periodic progress report */
if(total && now>=last_report+XFER_REPORT_INTERVAL) {
if(xfer.filepos)
sprintf(str," from offset %lu",xfer.filepos);
else
str[0]=0;
lprintf(LOG_INFO,"%04d <%s> DATA Sent %lu bytes (%lu total) of %s (%lu cps)%s"
,xfer.ctrl_sock, xfer.user->alias, total,length,xfer.filename
,(ulong)((total-last_total)/(now-last_report))
,str);
last_total=total;
last_report=now;
}
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer aborted.");
error=TRUE;
break;
}
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer locally aborted",xfer.ctrl_sock, xfer.user->alias);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer locally aborted.");
error=TRUE;
break;
}
/* Check socket for writability (using select) */
tv.tv_sec=1;
tv.tv_usec=0;
FD_ZERO(&socket_set);
FD_SET(*xfer.data_sock,&socket_set);
i=select((*xfer.data_sock)+1,NULL,&socket_set,NULL,&tv);
if(i==SOCKET_ERROR) {
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d selecting socket %d for send"
,xfer.ctrl_sock, xfer.user->alias, ERROR_VALUE, *xfer.data_sock);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer error.");
error=TRUE;
break;
}
fseek(fp,xfer.filepos+total,SEEK_SET);
rd=fread(buf,sizeof(char),sizeof(buf),fp);
socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SEND;
#endif
if (*xfer.data_sess != -1) {
int status = cryptPushData(*xfer.data_sess, buf, rd, &wr);
if (status != CRYPT_OK) {
GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "pushing data");
wr = -1;
}
else {
status = cryptFlushData(*xfer.data_sess);
if (status != CRYPT_OK) {
GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "flushing data");
wr = -1;
}
}
}
else
wr=sendsocket(*xfer.data_sock,buf,rd);
socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SEND;
#endif
if(ERROR_VALUE==EWOULDBLOCK) {
/*lprintf(LOG_WARNING,"%04d DATA send would block, retrying",xfer.ctrl_sock);*/
YIELD();
continue;
}
else if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_WARNING,"%04d <%s> DATA Connection reset by peer, sending on socket %d"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
lprintf(LOG_WARNING,"%04d <%s> DATA Connection aborted by peer, sending on socket %d"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d sending on data socket %d"
,xfer.ctrl_sock, xfer.user->alias,ERROR_VALUE,*xfer.data_sock);
/* Send NAK */
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Error %d sending on DATA channel"
,ERROR_VALUE);
error=TRUE;
break;
}
if(wr==0) {
lprintf(LOG_WARNING,"%04d <%s> !DATA socket %d disconnected",xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 DATA channel disconnected");
error=TRUE;
break;
}
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) sending on socket %d"
,xfer.ctrl_sock, xfer.user->alias, wr, ERROR_VALUE, *xfer.data_sock);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"451 DATA send error");
error=TRUE;
break;
}
total+=wr;
*xfer.lastactive=time(NULL);
//YIELD();
if((i=ferror(fp))!=0)
lprintf(LOG_ERR,"%04d <%s> !DATA FILE ERROR %d (%d, %s)"
,xfer.ctrl_sock, xfer.user->alias, i, errno, strerror(errno));
ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__); /* Signal end of file */
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket closed",xfer.ctrl_sock, xfer.user->alias);
dur=(long)(time(NULL)-start);
cps=dur ? total/dur : total*2;
lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %lu bytes sent in %lu seconds (%lu cps)"
,xfer.user->alias
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"226 Download complete (%lu cps).",cps);
if(xfer.dir>=0) {
memset(&f,0,sizeof(f));
GetShortPathName(xfer.filename,fname,sizeof(fname));
SAFECOPY(fname,xfer.filename);
#endif
padfname(getfname(fname),f.name);
f.dir=xfer.dir;
f.size=total;
if(getfileixb(&scfg,&f)==TRUE && getfiledat(&scfg,&f)==TRUE) {
f.timesdled++;
putfiledat(&scfg,&f);
f.datedled=time32(NULL);
putfileixb(&scfg,&f);
lprintf(LOG_INFO,"%04d <%s> DATA downloaded: %s (%u times total)"
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
,xfer.ctrl_sock
,xfer.user->alias
,xfer.filename
,f.timesdled);
/**************************/
/* Update Uploader's Info */
/**************************/
uploader.number=matchuser(&scfg,f.uler,TRUE /*sysop_alias*/);
if(uploader.number
&& uploader.number!=xfer.user->number
&& getuserdat(&scfg,&uploader)==0
&& uploader.firston<f.dateuled) {
l=f.cdt;
if(!(scfg.dir[f.dir]->misc&DIR_CDTDL)) /* Don't give credits on d/l */
l=0;
if(scfg.dir[f.dir]->misc&DIR_CDTMIN && cps) { /* Give min instead of cdt */
mod=((ulong)(l*(scfg.dir[f.dir]->dn_pct/100.0))/cps)/60;
adjustuserrec(&scfg,uploader.number,U_MIN,10,mod);
sprintf(tmp,"%lu minute",mod);
} else {
mod=(ulong)(l*(scfg.dir[f.dir]->dn_pct/100.0));
adjustuserrec(&scfg,uploader.number,U_CDT,10,mod);
ultoac(mod,tmp);
}
if(!(scfg.dir[f.dir]->misc&DIR_QUIET)) {
addr_len = sizeof(addr);
if(uploader.level>=SYSOP_LEVEL
&& getpeername(xfer.ctrl_sock,&addr.addr,&addr_len)==0
&& inet_addrtop(&addr, host_ip, sizeof(host_ip))!=NULL)
SAFEPRINTF2(username,"%s [%s]",xfer.user->alias,host_ip);
else
SAFECOPY(username,xfer.user->alias);
/* Inform uploader of downloaded file */
safe_snprintf(str,sizeof(str),text[DownloadUserMsg]
,getfname(xfer.filename)
,xfer.filepos ? "partially FTP-" : "FTP-"
,username,tmp);
putsmsg(&scfg,uploader.number,str);
}
if(!xfer.tmpfile && !xfer.delfile && !(scfg.dir[f.dir]->misc&DIR_NOSTAT))
download_stats(total);
}
if(xfer.credits) {
user_downloaded(&scfg, xfer.user, 1, total);
if(xfer.dir>=0 && !is_download_free(&scfg,xfer.dir,xfer.user,xfer.client))
subtract_cdt(&scfg, xfer.user, xfer.credits);
}
}
fclose(fp);
if(xfer.tmpfile) {
if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
#if defined(SOCKET_DEBUG_SENDTHREAD)
socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SENDTHREAD;
#endif
thread_down();
}
static void receive_thread(void* arg)
{
char* p;
char str[128];
char ext[F_EXBSIZE+1];
char desc[F_EXBSIZE+1];
char cmd[MAX_PATH*2];
char tmp[MAX_PATH+1];
char fname[MAX_PATH+1];
int rd;
int file;
ulong total=0;
ulong last_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;
fd_set socket_set;
struct timeval tv;
free(arg);
SetThreadName("sbbs/ftpReceive");
thread_up(TRUE /* setuid */);
if((fp=fopen(xfer.filename,xfer.append ? "ab" : "wb"))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
,xfer.ctrl_sock, xfer.user->alias, errno, strerror(errno), __LINE__, xfer.filename);
sockprintf(xfer.ctrl_sock,sess,"450 ERROR %d (%s) opening %s", errno, strerror(errno), xfer.filename);
thread_down();
if(xfer.append)
xfer.filepos=filelength(fileno(fp));

rswindell
committed
if(xfer.filepos || startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d receiving %s from offset %lu"
,xfer.ctrl_sock,xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos);
fseek(fp,xfer.filepos,SEEK_SET);
last_report=start=time(NULL);
while(1) {
now=time(NULL);
/* Periodic progress report */
if(total && now>=last_report+XFER_REPORT_INTERVAL) {
if(xfer.filepos)
sprintf(str," from offset %lu",xfer.filepos);
else
str[0]=0;
lprintf(LOG_INFO,"%04d <%s> DATA Received %lu bytes of %s (%lu cps)%s"
,xfer.ctrl_sock
,xfer.user->alias
,total,xfer.filename
,(ulong)((total-last_total)/(now-last_report))
,str);
last_total=total;
if(startup->max_fsize && (xfer.filepos+total) > startup->max_fsize) {
lprintf(LOG_WARNING,"%04d <%s> !DATA received %lu bytes of %s exceeds maximum allowed (%"PRIu64" bytes)"
,xfer.ctrl_sock, xfer.user->alias, xfer.filepos+total, xfer.filename, startup->max_fsize);
sockprintf(xfer.ctrl_sock,sess,"552 File size exceeds maximum allowed (%"PRIu64" bytes)", startup->max_fsize);
error=TRUE;
break;
}
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);

rswindell
committed
/* Send NAK */
error=TRUE;
break;
}
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer locally aborted",xfer.ctrl_sock, xfer.user->alias);

rswindell
committed
/* Send NAK */
sockprintf(xfer.ctrl_sock,sess,"426 Transfer locally aborted.");
error=TRUE;
break;
}
/* Check socket for readability (using select) */
tv.tv_sec=1;
tv.tv_usec=0;
FD_ZERO(&socket_set);
FD_SET(*xfer.data_sock,&socket_set);
i=select((*xfer.data_sock)+1,&socket_set,NULL,NULL,&tv);
if(i==SOCKET_ERROR) {
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d selecting socket %d for receive"
,xfer.ctrl_sock, xfer.user->alias, ERROR_VALUE, *xfer.data_sock);
error=TRUE;
break;
}
#if defined(SOCKET_DEBUG_RECV_BUF)
socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_RECV_BUF;
if (*xfer.data_sess != -1) {
int status = cryptPopData(*xfer.data_sess, buf, sizeof(buf), &rd);
if (status != CRYPT_OK) {
GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "popping data");
}
}
else {
rd=recv(*xfer.data_sock,buf,sizeof(buf),0);
}
#if defined(SOCKET_DEBUG_RECV_BUF)
socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_RECV_BUF;
if(rd<1) {
if(rd==0) { /* Socket closed */
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d closed by client"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
break;
}
if(rd==SOCKET_ERROR) {
if(ERROR_VALUE==EWOULDBLOCK) {
/*lprintf(LOG_WARNING,"%04d DATA recv would block, retrying",xfer.ctrl_sock);*/
YIELD();
continue;
}
else if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_WARNING,"%04d <%s> DATA Connection reset by peer, receiving on socket %d"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
lprintf(LOG_WARNING,"%04d <%s> DATA Connection aborted by peer, receiving on socket %d"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d receiving on data socket %d"
,xfer.ctrl_sock, xfer.user->alias,ERROR_VALUE,*xfer.data_sock);

rswindell
committed
/* Send NAK */
sockprintf(xfer.ctrl_sock,sess,"426 Error %d receiving on DATA channel"
,ERROR_VALUE);
error=TRUE;
break;
}
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR recv returned %d on socket %d"
,xfer.ctrl_sock, xfer.user->alias,rd,*xfer.data_sock);

rswindell
committed
/* Send NAK */
sockprintf(xfer.ctrl_sock,sess,"451 Unexpected socket error: %d",rd);
error=TRUE;
break;
}
fwrite(buf,1,rd,fp);
total+=rd;
*xfer.lastactive=time(NULL);
YIELD();

rswindell
committed
fclose(fp);
if(error && startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d closed",xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
if(xfer.filepos+total < startup->min_fsize) {
lprintf(LOG_WARNING,"%04d <%s> DATA received %lu bytes for %s, less than minimum required (%"PRIu64" bytes)"
,xfer.ctrl_sock, xfer.user->alias, xfer.filepos+total, xfer.filename, startup->min_fsize);
sockprintf(xfer.ctrl_sock,sess,"550 File size less than minimum required (%"PRIu64" bytes)"
,startup->min_fsize);
error=TRUE;
}
if(error) {
if(!xfer.append)
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
} else {
dur=(long)(time(NULL)-start);
cps=dur ? total/dur : total*2;
lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %lu bytes received in %lu seconds (%lu cps)"
,xfer.user->alias
,total,dur,cps);
if(xfer.dir>=0) {
memset(&f,0,sizeof(f));
GetShortPathName(xfer.filename,fname,sizeof(fname));
SAFECOPY(fname,xfer.filename);
#endif
padfname(getfname(fname),f.name);
filedat=getfileixb(&scfg,&f);
if(scfg.dir[f.dir]->misc&DIR_AONLY) /* Forced anonymous */
f.misc|=FM_ANON;
f.cdt=flength(xfer.filename);
f.dateuled=time32(NULL);
/* Description specified with DESC command? */
if(xfer.desc!=NULL && *xfer.desc!=0)
/* Necessary for DIR and LIB ARS keyword support in subsequent chk_ar()'s */
SAFECOPY(xfer.user->curdir, scfg.dir[f.dir]->code);
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,xfer.client))
break;
if(i<scfg.total_fextrs) {
sprintf(tmp,"%sFILE_ID.DIZ",scfg.temp_dir);
if(fexistcase(tmp))
ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias);
cmdstr(&scfg,xfer.user,scfg.fextr[i]->cmd,fname,"FILE_ID.DIZ",cmd);
lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ: %s",xfer.ctrl_sock, xfer.user->alias,cmd);
if(!fexistcase(tmp)) {
sprintf(tmp,"%sDESC.SDI",scfg.temp_dir);
if(fexistcase(tmp))
ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias);
cmdstr(&scfg,xfer.user,scfg.fextr[i]->cmd,fname,"DESC.SDI",cmd);
lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ: %s",xfer.ctrl_sock, xfer.user->alias,cmd);
fexistcase(tmp); /* fixes filename case */
}
if((file=nopen(tmp,O_RDONLY))!=-1) {
lprintf(LOG_DEBUG,"%04d <%s> DATA Parsing DIZ: %s",xfer.ctrl_sock, xfer.user->alias,tmp);
memset(ext,0,sizeof(ext));
read(file,ext,sizeof(ext)-1);
for(i=sizeof(ext)-1;i;i--) /* trim trailing spaces */
if(!f.desc[0]) { /* use for normal description */
strip_exascii(desc, desc); /* strip extended ASCII chars */
prep_file_desc(desc, desc); /* strip control chars and dupe chars */
for(i=0;desc[i];i++) /* find approprate first char */
if(isalnum(desc[i]))
break;
ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias);
lprintf(LOG_DEBUG,"%04d <%s> DATA DIZ Does not exist: %s",xfer.ctrl_sock, xfer.user->alias,tmp);
}
} /* FILE_ID.DIZ support */
if(f.desc[0]==0) /* no description given, use (long) filename */
SAFECOPY(f.uler,xfer.user->alias); /* exception here, Aug-27-2002 */
if(filedat) {
if(!putfiledat(&scfg,&f))
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR updating file (%s) in database"
,xfer.ctrl_sock, xfer.user->alias,f.name);
/* need to update the index here */
} else {
if(!addfiledat(&scfg,&f))
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR adding file (%s) to database"
,xfer.ctrl_sock, xfer.user->alias,f.name);
if(f.misc&FM_EXTDESC)
putextdesc(&scfg,f.dir,f.datoffset,ext);
if(scfg.dir[f.dir]->upload_sem[0])
ftouch(scfg.dir[f.dir]->upload_sem);
/**************************/
/* Update Uploader's Info */
/**************************/
user_uploaded(&scfg, xfer.user, (!xfer.append && xfer.filepos==0) ? 1:0, total);
if(scfg.dir[f.dir]->up_pct && scfg.dir[f.dir]->misc&DIR_CDTUL) { /* credit for upload */
if(scfg.dir[f.dir]->misc&DIR_CDTMIN && cps) /* Give min instead of cdt */
xfer.user->min=adjustuserrec(&scfg,xfer.user->number,U_MIN,10
,((ulong)(total*(scfg.dir[f.dir]->up_pct/100.0))/cps)/60);
else
xfer.user->cdt=adjustuserrec(&scfg,xfer.user->number,U_CDT,10
,(ulong)(f.cdt*(scfg.dir[f.dir]->up_pct/100.0)));
}
if(!(scfg.dir[f.dir]->misc&DIR_NOSTAT))
upload_stats(total);

rswindell
committed
/* Send ACK */
sockprintf(xfer.ctrl_sock,sess,"226 Upload complete (%lu cps).",cps);
*xfer.inprogress=FALSE;
static BOOL start_tls(SOCKET *sock, CRYPT_SESSION *sess, BOOL resp)
{
BOOL nodelay;
ulong nb;
int status;
char *estr = NULL;
if (get_ssl_cert(&scfg, &estr, &level) == -1) {
if (estr) {
lprintf(level, "%04d TLS %s", *sock, estr);
free_crypt_attrstr(estr);
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
if ((status = cryptCreateSession(sess, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
GCES(status, *sock, CRYPT_UNUSED, estr, "creating session");
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "disabling certificate verification");
if(resp)
sockprintf(*sock, *sess, "431 TLS not available");
lock_ssl_cert();
if ((status=cryptSetAttribute(*sess, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
unlock_ssl_cert();
GCES(status, *sock, *sess, estr, "setting private key");
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return FALSE;
}
nodelay = TRUE;
setsockopt(*sock,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
nb=0;
ioctlsocket(*sock,FIONBIO,&nb);
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_NETWORKSOCKET, *sock)) != CRYPT_OK) {
unlock_ssl_cert();
GCES(status, *sock, *sess, estr, "setting network socket");
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return TRUE;
}
if (resp)
sockprintf(*sock, -1, "234 Ready to start TLS");
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
unlock_ssl_cert();
GCES(status, *sock, *sess, estr, "setting session active");
unlock_ssl_cert();
if ((status = cryptSetAttribute(*sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting read timeout");
static void filexfer(union xp_sockaddr* addr, SOCKET ctrl_sock, CRYPT_SESSION ctrl_sess, SOCKET pasv_sock, CRYPT_SESSION pasv_sess, SOCKET* data_sock
,CRYPT_SESSION *data_sess, char* filename, long filepos, BOOL* inprogress, BOOL* aborted
,BOOL delfile, BOOL tmpfile
,time_t* lastactive
,user_t* user
,int dir
,BOOL receiving
,BOOL credits
,BOOL append
ulong l;
xfer_t* xfer;
struct timeval tv;
fd_set socket_set;
if((*inprogress)==TRUE) {
lprintf(LOG_WARNING,"%04d <%s> !DATA TRANSFER already in progress",ctrl_sock, user->alias);
sockprintf(ctrl_sock,ctrl_sess,"425 Transfer already in progress.");
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
return;
}
*inprogress=TRUE;
if(pasv_sock==INVALID_SOCKET) { /* !PASV */
if((*data_sock=socket(addr->addr.sa_family, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d opening socket", ctrl_sock, user->alias, ERROR_VALUE);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d opening socket",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
return;
}
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d opened",ctrl_sock, user->alias,*data_sock);
/* Use port-1 for all data connections */
reuseaddr=TRUE;
setsockopt(*data_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr));
addr_len = sizeof(server_addr);
if((result=getsockname(ctrl_sock, &server_addr.addr,&addr_len))!=0) {
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) getting address/port of command socket (%u)"
,ctrl_sock, user->alias,result,ERROR_VALUE,pasv_sock);
inet_setaddrport(&server_addr, inet_addrport(&server_addr)-1); /* 20? */
if(result!=0) {
inet_setaddrport(&server_addr, 0); /* any user port */
result=bind(*data_sock, &server_addr.addr,addr_len);
}
lprintf(LOG_ERR,"%04d <%s> DATA ERROR %d (%d) binding socket %d"
,ctrl_sock, user->alias, result, ERROR_VALUE, *data_sock);
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, user->alias);
if(result!=0) {
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d (%d) connecting to client %s port %u on socket %d"
,ctrl_sock, user->alias,result,ERROR_VALUE
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, user->alias);
return;
}
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d connected to %s port %u"
,ctrl_sock, user->alias,*data_sock,host_ip,inet_addrport(addr));
if (protected) {
if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
lprintf(LOG_DEBUG,"%04d <%s> !DATA ERROR activating TLS"
,ctrl_sock, user->alias);
sockprintf(ctrl_sock,ctrl_sess,"425 Error activating TLS");
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
ftp_close_socket(data_sock,data_sess,__LINE__);
return;
}
}
if(startup->options&FTP_OPT_DEBUG_DATA) {
lprintf(LOG_ERR,"%04d <%s> PASV !DATA ERROR %d (%d) getting address/port of passive socket (%u)"
,ctrl_sock, user->alias,result,ERROR_VALUE,pasv_sock);
else
lprintf(LOG_DEBUG,"%04d <%s> PASV DATA socket %d listening on %s port %u"
,ctrl_sock, user->alias,pasv_sock,host_ip,inet_addrport(addr));
/* 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;
if(result<1) {
lprintf(LOG_WARNING,"%04d <%s> PASV !DATA select returned %d (error: %d)"
,ctrl_sock, user->alias,result,ERROR_VALUE);
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, user->alias);
*inprogress=FALSE;
return;
}
socket_debug[ctrl_sock]|=SOCKET_DEBUG_ACCEPT;
socket_debug[ctrl_sock]&=~SOCKET_DEBUG_ACCEPT;
if(*data_sock==INVALID_SOCKET) {
lprintf(LOG_WARNING,"%04d <%s> PASV !DATA ERROR %d accepting connection on socket %d"
,ctrl_sock, user->alias,ERROR_VALUE,pasv_sock);
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, user->alias);
Loading
Loading full blame...