--- sbbs/sbbs3/ftpsrvr.c 2018/04/24 16:39:34 1.1 +++ sbbs/sbbs3/ftpsrvr.c 2018/04/24 16:40:38 1.1.1.2 @@ -2,13 +2,13 @@ /* Synchronet FTP server */ -/* $Id: ftpsrvr.c,v 1.1 2018/04/24 16:39:34 root Exp $ */ +/* $Id: ftpsrvr.c,v 1.1.1.2 2018/04/24 16:40:38 root Exp $ */ /**************************************************************************** * @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 * + * Copyright 2004 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 * @@ -39,15 +39,11 @@ #ifdef _WIN32 #include /* SH_DENYNO */ - #include /* _mkdir/_rmdir() */ + #include /* _mkdir/rmdir() */ #include /* _beginthread */ #include /* required for mmsystem.h */ #include /* SND_ASYNC */ -#elif defined(__unix__) - - #include /* signal/SIGPIPE */ - #endif @@ -62,17 +58,18 @@ #include /* S_IWRITE */ /* Synchronet-specific headers */ +#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */ #include "sbbs.h" +#include "text.h" /* TOTAL_TEXT */ #include "ftpsrvr.h" #include "telnet.h" /* Constants */ -#define FTP_VERSION "1.02" - -#define ILLEGAL_FILENAME_CHARS "\\/|<>+[]:=\";,%" +#define FTP_SERVER "Synchronet FTP Server" #define STATUS_WFC "Listening" +#define ANONYMOUS "anonymous" #define BBS_VIRTUAL_PATH "bbs:/""/" /* this is actually bbs: */ #define LOCAL_FSYS_DIR "local:" @@ -80,12 +77,48 @@ #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 */ +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 DWORD thread_count=0; +static time_t uptime=0; +static DWORD served=0; +static BOOL terminate_server=FALSE; +static char revision[16]; +static char *text[TOTAL_TEXT]; +static link_list_t recycle_semfiles; +static link_list_t shutdown_semfiles; + +#ifdef _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 +#endif + + +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"}; @@ -103,30 +136,20 @@ BOOL dir_op(scfg_t* cfg, user_t* user, u || (cfg->dir[dirnum]->op_ar[0] && chk_ar(cfg,cfg->dir[dirnum]->op_ar,user))); } -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; - -typedef struct { - SOCKET socket; - SOCKADDR_IN client_addr; -} ftp_t; - -static int lprintf(char *fmt, ...) +static int lprintf(int level, char *fmt, ...) { int result; va_list argptr; char sbuf[1024]; - if(startup!=NULL && startup->lputs==NULL) + if(startup==NULL || startup->lputs==NULL) return(0); + va_start(argptr,fmt); - vsprintf(sbuf,fmt,argptr); + vsnprintf(sbuf,sizeof(sbuf),fmt,argptr); + sbuf[sizeof(sbuf)-1]=0; va_end(argptr); - result=startup->lputs(sbuf); + result=startup->lputs(startup->cbdata,level,sbuf); return(result); } @@ -134,6 +157,8 @@ static int lprintf(char *fmt, ...) #ifdef _WINSOCKAPI_ static WSADATA WSAData; +#define SOCKLIB_DESC WSAData.szDescription + static BOOL WSAInitialized=FALSE; static BOOL winsock_startup(void) @@ -141,68 +166,75 @@ static BOOL winsock_startup(void) int status; /* Status Code */ if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) { - lprintf("%s %s",WSAData.szDescription, WSAData.szSystemStatus); + lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus); WSAInitialized=TRUE; return (TRUE); } - lprintf("!WinSock startup ERROR %d", status); + lprintf(LOG_ERR,"!WinSock startup ERROR %d", status); return (FALSE); } #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(str); + startup->status(startup->cbdata,str); } static void update_clients(void) { if(startup!=NULL && startup->clients!=NULL) - startup->clients(active_clients); + startup->clients(startup->cbdata,active_clients); } -static void client_on(SOCKET sock, client_t* client) +static void client_on(SOCKET sock, client_t* client, BOOL update) { if(startup!=NULL && startup->client_on!=NULL) - startup->client_on(TRUE,sock,client); + 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(FALSE,sock,NULL); + startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE); } -static void thread_up(void) +static void thread_up(BOOL setuid) { + thread_count++; if(startup!=NULL && startup->thread_up!=NULL) - startup->thread_up(TRUE); + startup->thread_up(startup->cbdata,TRUE, setuid); } static void thread_down(void) { + if(thread_count>0) + thread_count--; if(startup!=NULL && startup->thread_up!=NULL) - startup->thread_up(FALSE); + startup->thread_up(startup->cbdata,FALSE, FALSE); } -static SOCKET open_socket(int type) +static SOCKET ftp_open_socket(int type) { - SOCKET sock; + SOCKET sock; + char error[256]; sock=socket(AF_INET, type, IPPROTO_IP); if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) - startup->socket_open(TRUE); + startup->socket_open(startup->cbdata,TRUE); if(sock!=INVALID_SOCKET) { + if(set_socket_options(&scfg, sock, error)) + lprintf(LOG_ERR,"%04d !ERROR %s",sock, error); sockets++; #ifdef _DEBUG - lprintf("%04d Socket opened (%d sockets in use)",sock,sockets); + lprintf(LOG_DEBUG,"%04d Socket opened (%u sockets in use)",sock,sockets); #endif } return(sock); @@ -211,46 +243,33 @@ static SOCKET open_socket(int type) #ifdef __BORLANDC__ #pragma argsused #endif -static int close_socket(SOCKET* sock, int line) +static int ftp_close_socket(SOCKET* sock, int line) { int result; -#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); - return(-1); - } -#endif if((*sock)==INVALID_SOCKET) { - ReleaseMutex(socket_mutex); - lprintf("0000 !INVALID_SOCKET in close_socket from line %d",line); + lprintf(LOG_WARNING,"0000 !INVALID_SOCKET in close_socket from line %u",line); return(-1); } shutdown(*sock,SHUT_RDWR); /* required on Unix */ result=closesocket(*sock); - if(/* result==0 && */ startup!=NULL && startup->socket_open!=NULL) - startup->socket_open(FALSE); + if(startup!=NULL && startup->socket_open!=NULL) + startup->socket_open(startup->cbdata,FALSE); sockets--; - if(result!=0) - lprintf("%04d !ERROR %d closing socket from line %d",*sock,ERROR_VALUE,line); - else { + if(result!=0) { + if(ERROR_VALUE!=ENOTSOCK) + lprintf(LOG_WARNING,"%04d !ERROR %d closing socket from line %u",*sock,ERROR_VALUE,line); + } else if(sock==&server_socket || *sock==server_socket) + lprintf(LOG_DEBUG,"%04d Server socket closed (%u sockets in use) from line %u",*sock,sockets,line); #ifdef _DEBUG - lprintf("%04d Socket closed (%d sockets in use) from line %d",*sock,sockets,line); + else + lprintf(LOG_DEBUG,"%04d Socket closed (%u sockets in use) from line %u",*sock,sockets,line); #endif - } *sock=INVALID_SOCKET; - ReleaseMutex(socket_mutex); return(result); } @@ -258,65 +277,727 @@ static int close_socket(SOCKET* sock, in static int sockprintf(SOCKET sock, char *fmt, ...) { int len; + int maxlen; int result; va_list argptr; char sbuf[1024]; + fd_set socket_set; + struct timeval tv; 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; + len=vsnprintf(sbuf,maxlen=sizeof(sbuf)-2,fmt,argptr); va_end(argptr); - while((result=send(sock,sbuf,len,0))!=len) { + + 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", sock, len, sbuf); + memcpy(sbuf+len,"\r\n",2); + len+=2; + + if(sock==INVALID_SOCKET) { + lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockprintf"); + return(0); + } + + /* Check socket for writability (using select) */ + tv.tv_sec=300; + tv.tv_usec=0; + + FD_ZERO(&socket_set); + FD_SET(sock,&socket_set); + + if((result=select(sock+1,NULL,&socket_set,NULL,&tv))<1) { + if(result==0) + lprintf(LOG_WARNING,"%04d !TIMEOUT selecting socket for send" + ,sock); + else + lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for send" + ,sock, ERROR_VALUE); + return(0); + } + while((result=sendsocket(sock,sbuf,len))!=len) { if(result==SOCKET_ERROR) { if(ERROR_VALUE==EWOULDBLOCK) { - mswait(1); + YIELD(); continue; } if(ERROR_VALUE==ECONNRESET) - lprintf("%04d Connection reset by peer on send",sock); + lprintf(LOG_WARNING,"%04d Connection reset by peer on send",sock); else if(ERROR_VALUE==ECONNABORTED) - lprintf("%04d Connection aborted by peer on send",sock); + lprintf(LOG_WARNING,"%04d Connection aborted by peer on send",sock); else - lprintf("%04d !ERROR %d sending",sock,ERROR_VALUE); + lprintf(LOG_WARNING,"%04d !ERROR %d sending",sock,ERROR_VALUE); return(0); } - lprintf("%04d !ERROR: short send: %d instead of %d",sock,result,len); + lprintf(LOG_WARNING,"%04d !ERROR: short send: %u instead of %u",sock,result,len); } return(len); } -/************************************************/ -/* Truncates white-space chars off end of 'str' */ -/************************************************/ -static void truncsp(char *str) + +/* 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; + + SAFECOPY(path,p); + p=path; + + if(*p=='/') + p++; + else if(!strncmp(p,"./",2)) + p+=2; + + tp=strchr(p,'/'); + if(tp) *tp=0; + for(lib=0;libar,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;dirlib!=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_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; + +static JSBool +js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + JSString* str=NULL; + 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)); + } + + if(str==NULL) + *rval = JSVAL_VOID; + else + *rval = STRING_TO_JSVAL(str); + return(JS_TRUE); +} + +static JSBool +js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - uint c; + FILE* fp; + + if((fp=(FILE*)JS_GetContextPrivate(cx))==NULL) + return(JS_FALSE); -c=strlen(str); -while(c && (uchar)str[c-1]<=' ') c--; -str[c]=0; + js_write(cx,obj,argc,argv,rval); + fprintf(fp,"\r\n"); + 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 int nopen(char *str, int access) +static void +js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report) { - int file,share,count=0; + char line[64]; + char file[MAX_PATH+1]; + char* warning; + FILE* fp; + + fp=(FILE*)JS_GetContextPrivate(cx); + + if(report==NULL) { + 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"; + } else + warning=""; + + lprintf(LOG_ERR,"!JavaScript %s%s%s: %s",warning,file,line,message); + if(fp!=NULL) + fprintf(fp,"!JavaScript %s%s%s: %s",warning,file,line,message); +} + +static JSContext* +js_initcx(JSRuntime* runtime, SOCKET sock, JSObject** glob, JSObject** ftp) +{ + JSContext* js_cx; + JSObject* js_glob; + BOOL success=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) + return(NULL); + + lprintf(LOG_DEBUG,"%04d JavaScript: Context created",sock); + + JS_SetErrorReporter(js_cx, js_ErrorReporter); + + do { - 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++10) - mswait(55); - return(file); + lprintf(LOG_DEBUG,"%04d JavaScript: Initializing Global object",sock); + if((js_glob=js_CreateGlobalObject(js_cx, &scfg, NULL))==NULL) + break; + + if (!JS_DefineFunctions(js_cx, js_glob, js_global_functions)) + break; + + lprintf(LOG_DEBUG,"%04d JavaScript: Initializing System object",sock); + if(js_CreateSystemObject(js_cx, js_glob, &scfg, uptime, startup->host_name, SOCKLIB_DESC)==NULL) + break; + + if((*ftp=JS_DefineObject(js_cx, js_glob, "ftp", NULL + ,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL) + break; + + if(js_CreateServerObject(js_cx,js_glob,&js_server_props)==NULL) + break; + + if(glob!=NULL) + *glob=js_glob; + + success=TRUE; + + } while(0); + + if(!success) { + 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) +{ + JSObject* file; + JSString* js_str; + jsval val; + jsuint index; + + if((file=JS_NewObject(js_cx, NULL, NULL, NULL))==NULL) + return(FALSE); + + 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); + + JS_NewNumberValue(js_cx,time,&val); + 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); + + 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) +{ + 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* dp; + char aliasline[512]; + 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; + JSScript* js_script=NULL; + JSString* js_str; + + JS_SetContextPrivate(js_cx, fp); + + do { /* pseudo try/catch */ + + if((file_array=JS_NewArrayObject(js_cx, 0, NULL))==NULL) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to create file_array",sock); + break; + } + + /* file[] */ + val=OBJECT_TO_JSVAL(file_array); + if(!JS_SetProperty(js_cx, parent, "file_list", &val)) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to set file property",sock); + break; + } + + if((dir_array=JS_NewArrayObject(js_cx, 0, NULL))==NULL) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to create dir_array",sock); + break; + } + + /* dir[] */ + val=OBJECT_TO_JSVAL(dir_array); + if(!JS_SetProperty(js_cx, parent, "dir_list", &val)) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to set dir property",sock); + break; + } + + 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)) { + lprintf(LOG_ERR,"%04d !HTML JavaScript (%s) doesn't exist",sock,spath); + break; + } + + 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 !JavaScript FAILED to set html_index_file property",sock); + break; + } + + /* curlib */ + if((lib_obj=JS_NewObject(js_cx, NULL, 0, NULL))==NULL) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to create lib_obj",sock); + break; + } + + val=OBJECT_TO_JSVAL(lib_obj); + if(!JS_SetProperty(js_cx, parent, "curlib", &val)) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to set curlib property",sock); + break; + } + + /* curdir */ + if((dir_obj=JS_NewObject(js_cx, NULL, 0, NULL))==NULL) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to create dir_obj",sock); + break; + } + + val=OBJECT_TO_JSVAL(dir_obj); + if(!JS_SetProperty(js_cx, parent, "curdir", &val)) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to set curdir property",sock); + break; + } + + SAFECOPY(vpath,"/"); + + if(lib>=0) { /* root */ + + strcat(vpath,scfg.lib[lib]->sname); + strcat(vpath,"/"); + + 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 !JavaScript FAILED to set curlib.name property",sock); + 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 !JavaScript FAILED to set curlib.desc property",sock); + 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 !JavaScript FAILED to set curdir.code property",sock); + 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 !JavaScript FAILED to set curdir.name property",sock); + 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 !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(LOG_ERR,"%04d !JavaScript FAILED to set curdir.misc property",sock); + 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 !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),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) { + SAFEPRINTF2(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; + SAFEPRINTF2(vpath,"/%s/%s",p,startup->html_index_file); + } else + SAFECOPY(vpath,p); + 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 */ + ,0 /* misc */ + ,scfg.sys_id /* uploader */ + ,vpath /* link */ + ); + } + + fclose(alias_fp); + } + + /* 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 */ + ,10240 /* size */ + ,0 /* credits */ + ,time(NULL) /* time */ + ,0 /* uploaded */ + ,0 /* last downloaded */ + ,0 /* times downloaded */ + ,0 /* misc */ + ,scfg.sys_id /* uploader */ + ,str /* link */ + ); + } + + /* Library Folders */ + for(i=0;iar,user)) + continue; + 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 */ + ,scfg.sys_id /* uploader */ + ,vpath /* link */ + ); + } + } else if(dir<0) { + /* Directories */ + for(i=0;ilib!=lib) + continue; + if(/* i!=scfg.sysop_dir && i!=scfg.upload_dir && */ + !chk_ar(&scfg,scfg.dir[i]->ar,user)) + continue; + SAFEPRINTF3(vpath,"/%s/%s/%s" + ,scfg.lib[scfg.dir[i]->lib]->sname + ,scfg.dir[i]->code_suffix + ,startup->html_index_file); + js_add_file(js_cx + ,dir_array + ,scfg.dir[i]->sname /* filename */ + ,scfg.dir[i]->lname /* description */ + ,NULL /* extdesc */ + ,getfiles(&scfg,i) /* size */ + ,0,0,0,0,0 /* unused */ + ,scfg.dir[i]->misc /* misc */ + ,scfg.sys_id /* uploader */ + ,vpath /* link */ + ); + + } + } else if(chk_ar(&scfg,scfg.dir[dir]->ar,user)){ + SAFEPRINTF(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 + SAFECOPY(str,g.gl_pathv[i]); + #endif + padfname(getfname(str),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); + } + SAFEPRINTF3(vpath,"/%s/%s/%s" + ,scfg.lib[scfg.dir[dir]->lib]->sname + ,scfg.dir[dir]->code_suffix + ,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.misc /* misc */ + ,f.uler /* uploader */ + ,getfname(g.gl_pathv[i]) /* link */ + ); + } + } + globfree(&g); + } + + + /* RUN SCRIPT */ + JS_ClearPendingException(js_cx); + + if((js_script=JS_CompileFile(js_cx, parent, spath))==NULL) { + lprintf(LOG_ERR,"%04d !JavaScript FAILED to compile script (%s)",sock,spath); + break; + } + + if((success=JS_ExecuteScript(js_cx, parent, js_script, &rval))!=TRUE) { + lprintf(LOG_ERR,"%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"); + + return(success); +} + + +#endif /* ifdef JAVASCRIPT */ + + time_t gettimeleft(scfg_t* cfg, user_t* user, time_t starttime) { time_t now; @@ -357,7 +1038,7 @@ static time_t checktime(void) BOOL upload_stats(ulong bytes) { - char str[MAX_PATH]; + char str[MAX_PATH+1]; int file; ulong val; @@ -380,7 +1061,7 @@ BOOL upload_stats(ulong bytes) BOOL download_stats(ulong bytes) { - char str[MAX_PATH]; + char str[MAX_PATH+1]; int file; ulong val; @@ -401,19 +1082,24 @@ BOOL download_stats(ulong bytes) return(TRUE); } -void recverror(SOCKET socket, int rd) +void recverror(SOCKET socket, int rd, int line) { if(rd==0) - lprintf("%04d Socket closed by peer on receive",socket); + lprintf(LOG_NOTICE,"%04d Socket closed by peer on receive (line %u)" + ,socket, line); else if(rd==SOCKET_ERROR) { if(ERROR_VALUE==ECONNRESET) - lprintf("%04d Connection reset by peer on receive",socket); + lprintf(LOG_NOTICE,"%04d Connection reset by peer on receive (line %u)" + ,socket, line); else if(ERROR_VALUE==ECONNABORTED) - lprintf("%04d Connection aborted by peer on receive",socket); + lprintf(LOG_NOTICE,"%04d Connection aborted by peer on receive (line %u)" + ,socket, line); else - lprintf("%04d !ERROR %d receiving on socket", socket, ERROR_VALUE); + lprintf(LOG_NOTICE,"%04d !ERROR %d receiving on socket (line %u)" + ,socket, ERROR_VALUE, line); } else - lprintf("%04d !ERROR: recv on socket returned unexpected value: %d",socket,rd); + lprintf(LOG_WARNING,"%04d !ERROR: recv on socket returned unexpected value: %d (line %u)" + ,socket, rd, line); } int sockreadline(SOCKET socket, char* buf, int len, time_t* lastactive) @@ -422,10 +1108,17 @@ int sockreadline(SOCKET socket, char* bu int i,rd=0; fd_set socket_set; struct timeval tv; + + buf[0]=0; + + if(socket==INVALID_SOCKET) { + lprintf(LOG_WARNING,"INVALID SOCKET in call to sockreadline"); + return(0); + } while(rdmax_inactivity; tv.tv_usec=0; FD_ZERO(&socket_set); @@ -433,53 +1126,181 @@ int sockreadline(SOCKET socket, char* bu i=select(socket+1,&socket_set,NULL,NULL,&tv); - if(server_socket==INVALID_SOCKET) { + if(server_socket==INVALID_SOCKET || terminate_server) { sockprintf(socket,"421 Server downed, aborting."); - lprintf("%04d Server downed, aborting.",socket); + lprintf(LOG_WARNING,"%04d Server downed, aborting",socket); return(0); } if(i<1) { if(i==0) { if((time(NULL)-(*lastactive))>startup->max_inactivity) { - lprintf("%04d Disconnecting due to to inactivity.",socket); + lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",socket); sockprintf(socket,"421 Disconnecting due to inactivity (%u seconds)." ,startup->max_inactivity); return(0); } - mswait(1); continue; } - recverror(socket,i); + recverror(socket,i,__LINE__); return(i); } +#ifdef SOCKET_DEBUG_RECV_CHAR + socket_debug[socket]|=SOCKET_DEBUG_RECV_CHAR; +#endif i=recv(socket, &ch, 1, 0); - if(i<1) { -#if 0 - if(ERROR_VALUE==EWOULDBLOCK) { - mswait(1); - continue; - } +#ifdef SOCKET_DEBUG_RECV_CHAR + socket_debug[socket]&=~SOCKET_DEBUG_RECV_CHAR; #endif - recverror(socket,i); + if(i<1) { + recverror(socket,i,__LINE__); return(i); } - if(ch=='\n' && rd>=1) { + if(ch=='\n' /* && rd>=1 */) { /* Mar-9-2003: terminate on sole LF */ break; } buf[rd++]=ch; } - buf[rd-1]=0; + if(rd>0 && buf[rd-1]=='\r') + buf[rd-1]=0; + else + buf[rd]=0; return(rd); } +#if 0 /* now exported from in xtrn.cpp */ +/*****************************************************************************/ +/* Returns command line generated from instr with %c replacments */ +/*****************************************************************************/ +static 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+1]; +#endif + + len=strlen(instr); + for(i=j=0;ialias); + 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) */ + 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 + SAFECOPY(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,"%u",scfg.node_num); + strcat(cmd,str); + break; + case '*': + sprintf(str,"%03u",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__ + SAFECOPY(str,"OS2"); +#else + SAFECOPY(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); +} +#endif + void DLLCALL ftp_terminate(void) { - if(server_socket!=INVALID_SOCKET) { - lprintf("%04d FTP Terminate: closing socket",server_socket); - close_socket(&server_socket,__LINE__); - server_socket=INVALID_SOCKET; - } + lprintf(LOG_DEBUG,"%04d FTP Server terminate",server_socket); + terminate_server=TRUE; } @@ -493,7 +1314,7 @@ typedef struct { BOOL credits; BOOL append; long filepos; - char filename[MAX_PATH]; + char filename[MAX_PATH+1]; time_t* lastactive; user_t* user; int dir; @@ -503,11 +1324,17 @@ typedef struct { static void send_thread(void* arg) { char buf[8192]; - char fname[MAX_PATH]; + char fname[MAX_PATH+1]; + char str[128]; + char tmp[128]; + char username[128]; int i; int rd; int wr; + long mod; + ulong l; ulong total=0; + ulong last_total=0; ulong dur; ulong cps; ulong length; @@ -518,101 +1345,153 @@ static void send_thread(void* arg) time_t now; time_t start; time_t last_report; + user_t uploader; + SOCKADDR_IN addr; + socklen_t addr_len; + fd_set socket_set; + struct timeval tv; xfer=*(xfer_t*)arg; + free(arg); + + thread_up(TRUE /* setuid */); length=flength(xfer.filename); - if((fp=fopen(xfer.filename,"rb"))==NULL) { - lprintf("%04d !DATA ERROR %d opening %s",xfer.ctrl_sock,errno,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 !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); - close_socket(xfer.data_sock,__LINE__); + ftp_close_socket(xfer.data_sock,__LINE__); *xfer.inprogress=FALSE; + thread_down(); return; } - thread_up(); - *xfer.inprogress=TRUE; +#ifdef SOCKET_DEBUG_SENDTHREAD + socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SENDTHREAD; +#endif + *xfer.aborted=FALSE; if(startup->options&FTP_OPT_DEBUG_DATA || xfer.filepos) - lprintf("%04d DATA socket %d sending %s from offset %ld" + lprintf(LOG_DEBUG,"%04d DATA socket %d sending %s from offset %lu" ,xfer.ctrl_sock,*xfer.data_sock,xfer.filename,xfer.filepos); fseek(fp,xfer.filepos,SEEK_SET); last_report=start=time(NULL); - while(!feof(fp)) { + while((xfer.filepos+total)=last_report+XFER_REPORT_INTERVAL) { - lprintf("%04d Sent %ld bytes (%ld total) of %s (%lu cps)" + if(xfer.filepos) + sprintf(str," from offset %lu",xfer.filepos); + else + str[0]=0; + lprintf(LOG_INFO,"%04d Sent %lu bytes (%lu total) of %s (%lu cps)%s" ,xfer.ctrl_sock,total,length,xfer.filename - ,now-start ? total/(now-start) : total*2); + ,(total-last_total)/(now-last_report) + ,str); + last_total=total; last_report=now; } if(*xfer.aborted==TRUE) { - lprintf("%04d !DATA Transfer aborted",xfer.ctrl_sock); + lprintf(LOG_WARNING,"%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); + if(server_socket==INVALID_SOCKET || terminate_server) { + lprintf(LOG_WARNING,"%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) { - lprintf("%04d !READ ERROR (%d) on %s",xfer.ctrl_sock,errno,xfer.filename); + + /* 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 !DATA ERROR %d selecting socket %d for send" + ,xfer.ctrl_sock, ERROR_VALUE, *xfer.data_sock); + sockprintf(xfer.ctrl_sock,"426 Transfer error."); error=TRUE; break; } - wr=send(*xfer.data_sock,buf,rd,0); - if(wr!=rd) { + if(i<1) + continue; + + fseek(fp,xfer.filepos+total,SEEK_SET); + rd=fread(buf,sizeof(char),sizeof(buf),fp); + if(rd<1) /* EOF or READ error */ + break; + +#ifdef SOCKET_DEBUG_SEND + socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SEND; +#endif + wr=sendsocket(*xfer.data_sock,buf,rd); +#ifdef SOCKET_DEBUG_SEND + socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SEND; +#endif + if(wr<1) { if(wr==SOCKET_ERROR) { - if(ERROR_VALUE==ECONNRESET) - lprintf("%04d DATA Connection reset by peer, sending on socket %d" + 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 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" + lprintf(LOG_WARNING,"%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" + lprintf(LOG_WARNING,"%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); + /* Send NAK */ + 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); + lprintf(LOG_WARNING,"%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"); + lprintf(LOG_ERR,"%04d !DATA SEND ERROR %d (%d) on socket %d" + ,xfer.ctrl_sock, wr, ERROR_VALUE, *xfer.data_sock); + sockprintf(xfer.ctrl_sock,"451 DATA send error"); error=TRUE; break; } total+=wr; *xfer.lastactive=time(NULL); - mswait(1); + YIELD(); } if((i=ferror(fp))!=0) - lprintf("%04d !FILE ERROR %d (%d)",xfer.ctrl_sock,i,errno); + lprintf(LOG_ERR,"%04d !FILE ERROR %d (%d)",xfer.ctrl_sock,i,errno); - close_socket(xfer.data_sock,__LINE__); /* Signal end of file */ + 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); + lprintf(LOG_DEBUG,"%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)" + lprintf(LOG_INFO,"%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); @@ -622,37 +1501,72 @@ static void send_thread(void* arg) #ifdef _WIN32 GetShortPathName(xfer.filename,fname,sizeof(fname)); #else - strcpy(xfer.filename,fname); + SAFECOPY(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); + lprintf(LOG_INFO,"%04d %s downloaded: %s (%lu times total)" + ,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.firstonmisc&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,(struct sockaddr *)&addr,&addr_len)==0) + SAFEPRINTF2(username,"%s [%s]",xfer.user->alias,inet_ntoa(addr.sin_addr)); + 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); + } + } } /* Need to update datedled in index */ + + if(!xfer.tmpfile && !xfer.delfile && !(scfg.dir[f.dir]->misc&DIR_NOSTAT)) + download_stats(total); } 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) - && !(xfer.user->exempt&FLAG('D'))) + if(xfer.dir>=0 && !is_download_free(&scfg,xfer.dir,xfer.user)) subtract_cdt(&scfg, xfer.user, xfer.credits); } - if(!xfer.tmpfile && !xfer.delfile) - download_stats(total); } fclose(fp); - *xfer.inprogress=FALSE; + if(server_socket!=INVALID_SOCKET && !terminate_server) + *xfer.inprogress=FALSE; if(xfer.tmpfile) { if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES)) remove(xfer.filename); @@ -660,84 +1574,143 @@ static void send_thread(void* arg) else if(xfer.delfile && !error) remove(xfer.filename); +#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 buf[8192]; - char fname[MAX_PATH]; + 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 i; int rd; int file; ulong total=0; + ulong last_total=0; ulong dur; ulong cps; BOOL error=FALSE; + BOOL filedat; 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; xfer=*(xfer_t*)arg; + free(arg); + + thread_up(TRUE /* setuid */); if((fp=fopen(xfer.filename,xfer.append ? "ab" : "wb"))==NULL) { - lprintf("%04d !DATA ERROR %d opening %s",xfer.ctrl_sock,xfer.filename,errno); - close_socket(xfer.data_sock,__LINE__); + lprintf(LOG_ERR,"%04d !DATA ERROR %d opening %s",xfer.ctrl_sock,errno,xfer.filename); + sockprintf(xfer.ctrl_sock,"450 ERROR %d opening %s.",errno,xfer.filename); + ftp_close_socket(xfer.data_sock,__LINE__); + *xfer.inprogress=FALSE; + thread_down(); return; } - thread_up(); + if(xfer.append) + xfer.filepos=filelength(fileno(fp)); - *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); + lprintf(LOG_DEBUG,"%04d DATA socket %d receiving %s from offset %lu" + ,xfer.ctrl_sock,*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) { - lprintf("%04d Received %ld bytes of %s (%lu cps)" + if(xfer.filepos) + sprintf(str," from offset %lu",xfer.filepos); + else + str[0]=0; + lprintf(LOG_INFO,"%04d Received %lu bytes of %s (%lu cps)%s" ,xfer.ctrl_sock,total,xfer.filename - ,now-start? total/(now-start) : total*2); + ,(total-last_total)/(now-last_report) + ,str); + last_total=total; last_report=now; } if(*xfer.aborted==TRUE) { - lprintf("%04d !DATA Transfer aborted",xfer.ctrl_sock); + lprintf(LOG_WARNING,"%04d !DATA Transfer aborted",xfer.ctrl_sock); /* Send NAK */ sockprintf(xfer.ctrl_sock,"426 Transfer aborted."); error=TRUE; break; } - if(server_socket==INVALID_SOCKET) { - lprintf("%04d !DATA Transfer locally aborted",xfer.ctrl_sock); + if(server_socket==INVALID_SOCKET || terminate_server) { + lprintf(LOG_WARNING,"%04d !DATA Transfer locally aborted",xfer.ctrl_sock); /* Send NAK */ sockprintf(xfer.ctrl_sock,"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 !DATA ERROR %d selecting socket %d for receive" + ,xfer.ctrl_sock, ERROR_VALUE, *xfer.data_sock); + sockprintf(xfer.ctrl_sock,"426 Transfer error."); + error=TRUE; + break; + } + if(i<1) + continue; + +#if defined(SOCKET_DEBUG_RECV_BUF) + socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_RECV_BUF; +#endif rd=recv(*xfer.data_sock,buf,sizeof(buf),0); +#if defined(SOCKET_DEBUG_RECV_BUF) + socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_RECV_BUF; +#endif if(rd<1) { if(rd==0) { /* Socket closed */ if(startup->options&FTP_OPT_DEBUG_DATA) - lprintf("%04d DATA socket %d closed by client" + lprintf(LOG_DEBUG,"%04d DATA socket %d closed by client" ,xfer.ctrl_sock,*xfer.data_sock); break; } if(rd==SOCKET_ERROR) { - if(ERROR_VALUE==ECONNRESET) - lprintf("%04d Connection reset by peer, receiving on socket %d" + 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 DATA 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" + lprintf(LOG_WARNING,"%04d DATA 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" + lprintf(LOG_WARNING,"%04d !DATA ERROR %d receiving on data socket %d" ,xfer.ctrl_sock,ERROR_VALUE,*xfer.data_sock); /* Send NAK */ sockprintf(xfer.ctrl_sock,"426 Error %d receiving on DATA channel" @@ -745,7 +1718,7 @@ static void receive_thread(void* arg) error=TRUE; break; } - lprintf("%04d !DATA ERROR recv returned %d on socket %d" + lprintf(LOG_ERR,"%04d !DATA ERROR recv returned %d on socket %d" ,xfer.ctrl_sock,rd,*xfer.data_sock); /* Send NAK */ sockprintf(xfer.ctrl_sock,"451 Unexpected socket error: %d",rd); @@ -755,20 +1728,21 @@ static void receive_thread(void* arg) fwrite(buf,1,rd,fp); total+=rd; *xfer.lastactive=time(NULL); - mswait(1); + YIELD(); } - *xfer.inprogress=FALSE; + if(server_socket!=INVALID_SOCKET && !terminate_server) + *xfer.inprogress=FALSE; fclose(fp); - close_socket(xfer.data_sock,__LINE__); + ftp_close_socket(xfer.data_sock,__LINE__); if(error && startup->options&FTP_OPT_DEBUG_DATA) - lprintf("%04d DATA socket %d closed",xfer.ctrl_sock,*xfer.data_sock); + lprintf(LOG_DEBUG,"%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)" + lprintf(LOG_INFO,"%04d Transfer successful: %lu bytes received in %lu seconds (%lu cps)" ,xfer.ctrl_sock ,total,dur,cps); @@ -777,33 +1751,85 @@ static void receive_thread(void* arg) #ifdef _WIN32 GetShortPathName(xfer.filename,fname,sizeof(fname)); #else - strcpy(fname,xfer.filename); + SAFECOPY(fname,xfer.filename); #endif padfname(getfname(fname),f.name); - strupr(f.name); f.dir=xfer.dir; + filedat=getfileixb(&scfg,&f); if(scfg.dir[f.dir]->misc&DIR_AONLY) /* Forced anonymous */ f.misc|=FM_ANON; - f.cdt=total; + f.cdt=flength(xfer.filename); f.dateuled=time(NULL); - f.timesdled=0; - f.datedled=0L; - f.opencount=0; - if(xfer.desc==NULL || *xfer.desc==0) { - sprintf(f.desc,"%.*s",(int)sizeof(f.desc)-1,getfname(xfer.filename)); -/* old way strcpy(f.desc,"Received via FTP: No description given"); */ - } else - sprintf(f.desc,"%.*s",(int)sizeof(f.desc)-1,xfer.desc); - strcpy(f.uler,xfer.user->alias); - if(!addfiledat(&scfg,&f)) - lprintf("%04d !ERROR adding file (%s) to database",xfer.ctrl_sock,f.name); + + /* Desciption specified with DESC command? */ + if(xfer.desc!=NULL && *xfer.desc!=0) + SAFECOPY(f.desc,xfer.desc); + + /* FILE_ID.DIZ support */ + p=strrchr(f.name,'.'); + if(p!=NULL && scfg.dir[f.dir]->misc&DIR_DIZ) { + for(i=0;iext,p+1) + && chk_ar(&scfg,scfg.fextr[i]->ar,xfer.user)) + break; + if(icmd,fname,"FILE_ID.DIZ",cmd)); + if(!fexistcase(tmp)) { + sprintf(tmp,"%sDESC.SDI",scfg.temp_dir); + if(fexistcase(tmp)) + remove(tmp); + system(cmdstr(&scfg,xfer.user,scfg.fextr[i]->cmd,fname,"DESC.SDI",cmd)); + fexistcase(tmp); /* fixes filename case */ + } + 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]>' ') + break; + ext[i]=0; + if(!f.desc[0]) { /* use for normal description */ + SAFECOPY(desc,ext); + strip_exascii(desc); /* strip extended ASCII chars */ + prep_file_desc(desc); /* strip control chars and dupe chars */ + for(i=0;desc[i];i++) /* find approprate first char */ + if(isalnum(desc[i])) + break; + SAFECOPY(f.desc,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 */ + SAFECOPY(f.desc,getfname(xfer.filename)); + + SAFECOPY(f.uler,xfer.user->alias); /* exception here, Aug-27-2002 */ + if(filedat) { + if(!putfiledat(&scfg,&f)) + lprintf(LOG_ERR,"%04d !ERROR updating file (%s) in database",xfer.ctrl_sock,f.name); + /* need to update the index here */ + } else { + if(!addfiledat(&scfg,&f)) + lprintf(LOG_ERR,"%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); + ftouch(scfg.dir[f.dir]->upload_sem); /**************************/ /* Update Uploader's Info */ /**************************/ - xfer.user->uls=(short)adjustuserrec(&scfg, xfer.user->number,U_ULS,5,1); + if(!xfer.append && xfer.filepos==0) + 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 */ @@ -813,7 +1839,8 @@ static void receive_thread(void* arg) 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); + if(!(scfg.dir[f.dir]->misc&DIR_NOSTAT)) + upload_stats(total); } /* Send ACK */ sockprintf(xfer.ctrl_sock,"226 Upload complete (%lu cps).",cps); @@ -836,24 +1863,28 @@ static void filexfer(SOCKADDR_IN* addr, ,char* desc) { int result; - int addr_len; + ulong l; + socklen_t addr_len; SOCKADDR_IN server_addr; - static xfer_t xfer; + BOOL reuseaddr; + xfer_t* xfer; + struct timeval tv; + fd_set socket_set; if((*inprogress)==TRUE) { - lprintf("%04d !TRANSFER already in progress",ctrl_sock); + lprintf(LOG_WARNING,"%04d !TRANSFER already in progress",ctrl_sock); sockprintf(ctrl_sock,"425 Transfer already in progress."); return; } *inprogress=TRUE; if(*data_sock!=INVALID_SOCKET) - close_socket(data_sock,__LINE__); + ftp_close_socket(data_sock,__LINE__); 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); + lprintf(LOG_ERR,"%04d !DATA ERROR %d opening socket", ctrl_sock, ERROR_VALUE); sockprintf(ctrl_sock,"425 Error %d opening socket",ERROR_VALUE); if(tmpfile) remove(filename); @@ -861,49 +1892,92 @@ static void filexfer(SOCKADDR_IN* addr, return; } if(startup->socket_open!=NULL) - startup->socket_open(TRUE); + startup->socket_open(startup->cbdata,TRUE); sockets++; if(startup->options&FTP_OPT_DEBUG_DATA) - lprintf("%04d DATA socket %d opened",ctrl_sock,*data_sock); + lprintf(LOG_DEBUG,"%04d DATA socket %d opened",ctrl_sock,*data_sock); + + /* Use port-1 for all data connections */ + reuseaddr=TRUE; + setsockopt(*data_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr)); 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(0); + 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" + result=bind(*data_sock, (struct sockaddr *) &server_addr,sizeof(server_addr)); + if(result!=0) { + server_addr.sin_port = 0; /* any user port */ + result=bind(*data_sock, (struct sockaddr *) &server_addr,sizeof(server_addr)); + } + if(result!=0) { + lprintf(LOG_ERR,"%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; - close_socket(data_sock,__LINE__); + ftp_close_socket(data_sock,__LINE__); return; } - if((result=connect(*data_sock, (struct sockaddr *)addr - ,sizeof(struct sockaddr)))!=0) { - lprintf("%04d !DATA ERROR %d (%d) connecting to client %s port %d on socket %d" + result=connect(*data_sock, (struct sockaddr *)addr,sizeof(struct sockaddr)); + if(result!=0) { + lprintf(LOG_WARNING,"%04d !DATA ERROR %d (%d) connecting to client %s port %u 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; - close_socket(data_sock,__LINE__); + ftp_close_socket(data_sock,__LINE__); return; } if(startup->options&FTP_OPT_DEBUG_DATA) - lprintf("%04d DATA socket %d connected to %s port %d" + lprintf(LOG_DEBUG,"%04d DATA socket %d connected to %s port %u" ,ctrl_sock,*data_sock,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port)); } else { /* PASV */ + + if(startup->options&FTP_OPT_DEBUG_DATA) + lprintf(LOG_DEBUG,"%04d PASV DATA socket %d listening on %s port %u" + ,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); + +#if defined(SOCKET_DEBUG_SELECT) + socket_debug[ctrl_sock]|=SOCKET_DEBUG_SELECT; +#endif + result=select(pasv_sock+1,&socket_set,NULL,NULL,&tv); +#if defined(SOCKET_DEBUG_SELECT) + socket_debug[ctrl_sock]&=~SOCKET_DEBUG_SELECT; +#endif + if(result<1) { + lprintf(LOG_WARNING,"%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); - if((*data_sock=accept(pasv_sock,(struct sockaddr*)addr,&addr_len))==INVALID_SOCKET) { - lprintf("%04d !PASV DATA ERROR %d accepting connection on socket %d" +#ifdef SOCKET_DEBUG_ACCEPT + socket_debug[ctrl_sock]|=SOCKET_DEBUG_ACCEPT; +#endif + *data_sock=accept(pasv_sock,(struct sockaddr*)addr,&addr_len); +#ifdef SOCKET_DEBUG_ACCEPT + socket_debug[ctrl_sock]&=~SOCKET_DEBUG_ACCEPT; +#endif + if(*data_sock==INVALID_SOCKET) { + lprintf(LOG_WARNING,"%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) @@ -912,34 +1986,59 @@ static void filexfer(SOCKADDR_IN* addr, return; } if(startup->socket_open!=NULL) - startup->socket_open(TRUE); + startup->socket_open(startup->cbdata,TRUE); sockets++; if(startup->options&FTP_OPT_DEBUG_DATA) - lprintf("%04d PASV DATA socket %d connected to %s port %d" + lprintf(LOG_DEBUG,"%04d PASV DATA socket %d connected to %s port %u" ,ctrl_sock,*data_sock,inet_ntoa(addr->sin_addr),ntohs(addr->sin_port)); } + do { - /* 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); + l=1; + + if(ioctlsocket(*data_sock, FIONBIO, &l)!=0) { + lprintf(LOG_ERR,"%04d !DATA ERROR %d disabling socket blocking" + ,ctrl_sock, ERROR_VALUE); + sockprintf(ctrl_sock,"425 Error %d disabling socket blocking" + ,ERROR_VALUE); + break; + } + + if((xfer=malloc(sizeof(xfer_t)))==NULL) { + lprintf(LOG_CRIT,"%04d !MALLOC FAILURE LINE %d",ctrl_sock,__LINE__); + sockprintf(ctrl_sock,"425 MALLOC FAILURE"); + break; + } + memset(xfer,0,sizeof(xfer_t)); + 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; + SAFECOPY(xfer->filename,filename); + if(receiving) + result=_beginthread(receive_thread,0,(void*)xfer); + else + result=_beginthread(send_thread,0,(void*)xfer); + + if(result!=-1) + return; /* success */ + + } while(0); + + /* failure */ + if(tmpfile) + remove(filename); + *inprogress=FALSE; } /* convert "user name" to "user.name" or "mr. user" to "mr._user" */ @@ -961,62 +2060,15 @@ char* dotname(char* in, char* out) return(out); } -/* 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]; - 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;libar,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;dirlib!=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); -} - void parsepath(char** pp, user_t* user, int* curlib, int* curdir) { char* p; char* tp; - char path[MAX_PATH]; + char path[MAX_PATH+1]; int dir=*curdir; int lib=*curlib; - sprintf(path,"%.*s",(int)sizeof(path)-1,*pp); + SAFECOPY(path,*pp); p=path; if(*p=='/') { @@ -1042,7 +2094,7 @@ void parsepath(char** pp, user_t* user, return; } - if(lib<0) { + if(lib<0) { /* root */ tp=strchr(p,'/'); if(tp) *tp=0; for(lib=0;liblib!=lib) continue; - if(!chk_ar(&scfg,scfg.dir[dir]->ar,user)) + 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)) + if(!stricmp(scfg.dir[dir]->code_suffix,p)) break; } @@ -1089,31 +2142,36 @@ void parsepath(char** pp, user_t* user, *pp+=tp-path; /* skip "lib/dir/" */ } -BOOL alias(char* fullalias, char* filename, user_t* user, int* curdir) +static BOOL ftpalias(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]; + 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); + return(FALSE); - sprintf(alias,"%.*s",(int)sizeof(alias)-1,fullalias); + SAFECOPY(alias,fullalias); p=strrchr(alias+1,'/'); if(p) { *p=0; fname=p+1; } + if(filename==NULL /* directory */ && *fname /* filename specified */) { + fclose(fp); + return(FALSE); + } + while(!feof(fp)) { - if(!fgets(line,sizeof(line)-1,fp)) + if(!fgets(line,sizeof(line),fp)) break; p=line; /* alias */ @@ -1137,7 +2195,7 @@ BOOL alias(char* fullalias, char* filena 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); + lprintf(LOG_WARNING,"0000 !Invalid virtual path (%s) for %s",p,user->alias); /* invalid or no access */ continue; } @@ -1164,9 +2222,9 @@ BOOL alias(char* fullalias, char* filena char* root_dir(char* path) { char* p; - static char root[MAX_PATH]; + static char root[MAX_PATH+1]; - sprintf(root,"%.*s",(int)sizeof(root)-1,path); + SAFECOPY(root,path); if(!strncmp(root,"\\\\",2)) { /* network path */ p=strchr(root+2,'\\'); @@ -1181,6 +2239,37 @@ char* root_dir(char* path) return(root); } +char* vpath(int lib, int dir, char* str) +{ + strcpy(str,"/"); + if(lib<0) + return(str); + strcat(str,scfg.lib[lib]->sname); + strcat(str,"/"); + if(dir<0) + return(str); + strcat(str,scfg.dir[dir]->code_suffix); + strcat(str,"/"); + return(str); +} + +static BOOL badlogin(SOCKET sock, ulong* login_attempts) +{ + mswait(5000); /* As recommended by RFC2577 */ + if(++(*login_attempts)>=3) { + sockprintf(sock,"421 Too many failed login attempts."); + return(TRUE); + } + sockprintf(sock,"530 Invalid login."); + return(FALSE); +} + +static char* ftp_tmpfname(char* str, SOCKET sock) +{ + sprintf(str,"%sftp%u%u.tx",scfg.data_dir,getpid(),sock); + return(str); +} + static void ctrl_thread(void* arg) { char buf[512]; @@ -1190,25 +2279,25 @@ static void ctrl_thread(void* arg) char* np; char* tp; char password[64]; - char fname[MAX_PATH]; - char qwkfile[MAX_PATH]; - char aliasfile[MAX_PATH]; + 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]; - char local_dir[MAX_PATH]; - char ren_from[MAX_PATH]=""; + 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; + socklen_t 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; @@ -1219,7 +2308,9 @@ static void ctrl_thread(void* arg) long filepos=0L; long timeleft; ulong l; - ulong avail; + ulong login_attempts=0; + ulong avail; /* disk space */ + ulong count; BOOL detail; BOOL success; BOOL getdate; @@ -1228,15 +2319,17 @@ static void ctrl_thread(void* arg) BOOL delfile; BOOL tmpfile; BOOL credits; - BOOL filedat; + BOOL filedat=FALSE; BOOL transfer_inprogress; BOOL transfer_aborted; BOOL sysop=FALSE; BOOL local_fsys=FALSE; BOOL alias_dir; + BOOL append; FILE* fp; FILE* alias_fp; SOCKET sock; + SOCKET tmp_sock; SOCKET pasv_sock=INVALID_SOCKET; SOCKET data_sock=INVALID_SOCKET; HOSTENT* host; @@ -1247,17 +2340,24 @@ static void ctrl_thread(void* arg) user_t user; time_t t; time_t now; - time_t logintime; + time_t logintime=0; time_t lastactive; file_t f; glob_t g; node_t node; client_t client; struct tm tm; - struct tm * tm_p; struct tm cur_tm; +#ifdef JAVASCRIPT + jsval js_val; + JSRuntime* js_runtime=NULL; + JSContext* js_cx=NULL; + JSObject* js_glob; + JSObject* js_ftp; + JSString* js_str; +#endif - thread_up(); + thread_up(TRUE /* setuid */); lastactive=time(NULL); @@ -1267,9 +2367,9 @@ static void ctrl_thread(void* arg) 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); + lprintf(LOG_DEBUG,"%04d CTRL thread started", sock); - free(arg); + free(arg); /* unexplicable assertion here on July 26, 2001 */ #ifdef _WIN32 if(startup->answer_sound[0] && !(startup->options&FTP_OPT_MUTE)) @@ -1282,20 +2382,21 @@ static void ctrl_thread(void* arg) l=1; if((i=ioctlsocket(sock, FIONBIO, &l))!=0) { - lprintf("%04d !ERROR %d (%d) disabling socket blocking" + lprintf(LOG_ERR,"%04d !ERROR %d (%d) disabling socket blocking" ,sock, i, ERROR_VALUE); sockprintf(sock,"425 Error %d disabling socket blocking" ,ERROR_VALUE); - close_socket(&sock,__LINE__); + ftp_close_socket(&sock,__LINE__); thread_down(); return; } memset(&user,0,sizeof(user)); - strcpy(host_ip,inet_ntoa(ftp.client_addr.sin_addr)); + SAFECOPY(host_ip,inet_ntoa(ftp.client_addr.sin_addr)); - lprintf ("%04d CTRL connection accepted from: %s", sock, host_ip); + lprintf(LOG_DEBUG,"%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; @@ -1308,20 +2409,24 @@ static void ctrl_thread(void* arg) else host_name=""; - lprintf("%04d Host name: %s", sock, host_name); + if(!(startup->options&FTP_OPT_NO_HOST_LOOKUP)) { + lprintf(LOG_INFO,"%04d Hostname: %s", sock, host_name); + for(i=0;host!=NULL && host->h_aliases!=NULL && host->h_aliases[i]!=NULL;i++) + lprintf(LOG_INFO,"%04d HostAlias: %s", sock, host->h_aliases[i]); + } if(trashcan(&scfg,host_ip,"ip")) { - lprintf("%04d !Client blocked in ip.can: %s", sock, host_ip); + lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", sock, host_ip); sockprintf(sock,"550 Access denied."); - close_socket(&sock,__LINE__); + ftp_close_socket(&sock,__LINE__); thread_down(); return; } if(trashcan(&scfg,host_name,"host")) { - lprintf("%04d !Client blocked in host.can: %s", sock, host_name); + lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", sock, host_name); sockprintf(sock,"550 Access denied."); - close_socket(&sock,__LINE__); + ftp_close_socket(&sock,__LINE__); thread_down(); return; } @@ -1329,29 +2434,28 @@ static void ctrl_thread(void* arg) /* For PASV mode */ addr_len=sizeof(pasv_addr); if((result=getsockname(sock, (struct sockaddr *)&pasv_addr,&addr_len))!=0) { - lprintf("%04d !ERROR %d (%d) getting address/port", sock, result, ERROR_VALUE); + lprintf(LOG_ERR,"%04d !ERROR %d (%d) getting address/port", sock, result, ERROR_VALUE); sockprintf(sock,"425 Error %d getting address/port",ERROR_VALUE); - close_socket(&sock,__LINE__); + ftp_close_socket(&sock,__LINE__); thread_down(); return; } - active_clients++; - update_clients(); + active_clients++, update_clients(); /* Initialize client display */ client.size=sizeof(client); client.time=time(NULL); - sprintf(client.addr,"%.*s",(int)sizeof(client.addr)-1,host_ip); - sprintf(client.host,"%.*s",(int)sizeof(client.host)-1,host_name); + SAFECOPY(client.addr,host_ip); + SAFECOPY(client.host,host_name); client.port=ntohs(ftp.client_addr.sin_port); client.protocol="FTP"; client.user=""; - client_on(sock,&client); + client_on(sock,&client,FALSE /* update */); - sockprintf(sock,"220-%s (%s)",scfg.sys_name, scfg.sys_inetaddr); - sockprintf(sock," Synchronet FTP Server for %s v%s Ready" - ,PLATFORM_DESC,FTP_VERSION); + sockprintf(sock,"220-%s (%s)",scfg.sys_name, startup->host_name); + sockprintf(sock," Synchronet FTP Server %s-%s Ready" + ,revision,PLATFORM_DESC); sprintf(str,"%sftplogin.txt",scfg.text_dir); if((fp=fopen(str,"rb"))!=NULL) { while(!feof(fp)) { @@ -1364,36 +2468,54 @@ static void ctrl_thread(void* arg) } sockprintf(sock,"220 Please enter your user name."); +#ifdef SOCKET_DEBUG_CTRL + socket_debug[sock]|=SOCKET_DEBUG_CTRL; +#endif while(1) { +#ifdef SOCKET_DEBUG_READLINE + socket_debug[sock]|=SOCKET_DEBUG_READLINE; +#endif rd = sockreadline(sock, buf, sizeof(buf), &lastactive); +#ifdef SOCKET_DEBUG_READLINE + socket_debug[sock]&=~SOCKET_DEBUG_READLINE; +#endif if(rd<1) { - if(transfer_inprogress==TRUE) + if(transfer_inprogress==TRUE) { + lprintf(LOG_WARNING,"%04d Aborting transfer due to receive error",sock); transfer_aborted=TRUE; + } break; } + truncsp(buf); lastactive=time(NULL); cmd=buf; while(((BYTE)*cmd)==TELNET_IAC) { cmd++; - lprintf("%04d RX: Telnet cmd %d",sock,(BYTE)*cmd); + lprintf(LOG_DEBUG,"%04d RX: Telnet cmd: %s",sock,telnet_cmd_desc(*cmd)); cmd++; } while(*cmd && *cmd<' ') { - lprintf("%04d RX: %d (0x%02X)",sock,(BYTE)*cmd,(BYTE)*cmd); + lprintf(LOG_DEBUG,"%04d RX: %d (0x%02X)",sock,(BYTE)*cmd,(BYTE)*cmd); cmd++; } if(!(*cmd)) continue; if(startup->options&FTP_OPT_DEBUG_RX) - lprintf("%04d RX: %s", sock, cmd); + lprintf(LOG_DEBUG,"%04d RX: %s", sock, cmd); if(!stricmp(cmd, "NOOP")) { sockprintf(sock,"200 NOOP command successful."); continue; } if(!stricmp(cmd, "HELP SITE") || !stricmp(cmd, "SITE HELP")) { sockprintf(sock,"214-The following SITE commands are recognized (* => unimplemented):"); - sockprintf(sock," HELP WHO"); + sockprintf(sock," HELP VER WHO UPTIME"); + if(user.level>=SYSOP_LEVEL) + sockprintf(sock, + " RECYCLE [ALL]"); + if(sysop) + sockprintf(sock, + " EXEC "); sockprintf(sock,"214 Direct comments to sysop@%s.",scfg.sys_inetaddr); continue; } @@ -1445,14 +2567,15 @@ static void ctrl_thread(void* arg) p=cmd+5; while(*p && *p<=' ') p++; truncsp(p); - sprintf(user.alias,"%.*s",(int)sizeof(user.alias)-1,p); - if(!stricmp(user.alias,"anonymous")) - strcpy(user.alias,"guest"); - user.number=matchuser(&scfg,user.alias); + SAFECOPY(user.alias,p); + user.number=matchuser(&scfg,user.alias,FALSE /*sysop_alias*/); + if(!user.number && !stricmp(user.alias,"anonymous")) + user.number=matchuser(&scfg,"guest",FALSE); if(user.number && getuserdat(&scfg, &user)==0 && user.pass[0]==0) sockprintf(sock,"331 User name okay, give your full e-mail address as password."); else sockprintf(sock,"331 User name okay, need password."); + user.number=0; continue; } if(!strnicmp(cmd, "PASS ",5) && user.alias[0]) { @@ -1460,73 +2583,96 @@ static void ctrl_thread(void* arg) p=cmd+5; while(*p && *p<=' ') p++; - sprintf(password,"%.*s",(int)sizeof(password)-1,p); - user.number=matchuser(&scfg,user.alias); + SAFECOPY(password,p); + user.number=matchuser(&scfg,user.alias,FALSE /*sysop_alias*/); if(!user.number) { - lprintf("%04d !FTP: UNKNOWN USER: %s",sock,user.alias); - sockprintf(sock,"530 Password not accepted."); + if(scfg.sys_misc&SM_ECHO_PW) + lprintf(LOG_WARNING,"%04d !UNKNOWN USER: %s, Password: %s",sock,user.alias,p); + else + lprintf(LOG_WARNING,"%04d !UNKNOWN USER: %s",sock,user.alias); + if(badlogin(sock,&login_attempts)) + break; continue; } if((i=getuserdat(&scfg, &user))!=0) { - lprintf("%04d !FTP: ERROR %d getting data on user #%d (%s)" + lprintf(LOG_ERR,"%04d !ERROR %d getting data for user #%d (%s)" ,sock,i,user.number,user.alias); sockprintf(sock,"530 Database error %d",i); user.number=0; continue; } if(user.misc&(DELETED|INACTIVE)) { - lprintf("%04d !FTP: DELETED or INACTIVE user #%d (%s)" + lprintf(LOG_WARNING,"%04d !DELETED or INACTIVE user #%d (%s)" ,sock,user.number,user.alias); - sockprintf(sock,"530 Password not accepted."); user.number=0; + if(badlogin(sock,&login_attempts)) + break; continue; } if(user.rest&FLAG('T')) { - lprintf("%04d !FTP: 'T' RESTRICTED user #%d (%s)" + lprintf(LOG_WARNING,"%04d !T RESTRICTED user #%d (%s)" ,sock,user.number,user.alias); - sockprintf(sock,"530 Password not accepted."); user.number=0; + if(badlogin(sock,&login_attempts)) + break; continue; } if(user.ltoday>scfg.level_callsperday[user.level] && !(user.exempt&FLAG('L'))) { - lprintf("%04d !FTP: Max logons (%d) reached" - ,sock,scfg.level_callsperday[user.level]); + lprintf(LOG_WARNING,"%04d !MAXIMUM LOGONS (%d) reached for %s" + ,sock,scfg.level_callsperday[user.level],user.alias); sockprintf(sock,"530 Maximum logons reached."); user.number=0; continue; } if(user.rest&FLAG('L') && user.ltoday>1) { - lprintf("%04d !FTP: L Restricted user already on today" - ,sock); + lprintf(LOG_WARNING,"%04d !L RESTRICTED user #%d (%s) already on today" + ,sock,user.number,user.alias); sockprintf(sock,"530 Maximum logons reached."); user.number=0; continue; } - sprintf(sys_pass,"%s:%s",user.pass,scfg.sys_pass); + SAFEPRINTF2(sys_pass,"%s:%s",user.pass,scfg.sys_pass); if(!user.pass[0]) { /* Guest/Anonymous */ - lprintf("%04d Guest: %s",sock,password); + if(trashcan(&scfg,password,"email")) { + lprintf(LOG_NOTICE,"%04d !BLOCKED e-mail address: %s",sock,password); + user.number=0; + if(badlogin(sock,&login_attempts)) + break; + continue; + } + lprintf(LOG_INFO,"%04d %s: <%s>",sock,user.alias,password); putuserrec(&scfg,user.number,U_NETMAIL,LEN_NETMAIL,password); } else if(user.level>=SYSOP_LEVEL && !stricmp(password,sys_pass)) { - lprintf("%04d Sysop access granted to %s", sock, user.alias); + lprintf(LOG_INFO,"%04d Sysop access granted to %s", sock, user.alias); sysop=TRUE; } else if(stricmp(password,user.pass)) { - lprintf("%04d !FTP: WRONG PASSWORD for user %s: '%s' expected '%s'" - ,sock,user.alias,password,user.pass); - sockprintf(sock,"530 Password not accepted."); + if(scfg.sys_misc&SM_ECHO_PW) + lprintf(LOG_WARNING,"%04d !FAILED Password attempt for user %s: '%s' expected '%s'" + ,sock, user.alias, password, user.pass); + else + lprintf(LOG_WARNING,"%04d !FAILED Password attempt for user %s" + ,sock, user.alias); user.number=0; + if(badlogin(sock,&login_attempts)) + break; continue; } /* Update client display */ - client.user=user.alias; - client_on(sock,&client); + if(user.pass[0]) + client.user=user.alias; + else { /* anonymous */ + sprintf(str,"%s <%.32s>",user.alias,password); + client.user=str; + } + client_on(sock,&client,TRUE /* update */); - lprintf("%04d %s logged in.",sock,user.alias); + lprintf(LOG_INFO,"%04d %s logged in",sock,user.alias); logintime=time(NULL); timeleft=gettimeleft(&scfg,&user,logintime); sprintf(str,"%sftphello.txt",scfg.text_dir); @@ -1544,13 +2690,35 @@ static void ctrl_thread(void* arg) } fclose(fp); } + +#ifdef JAVASCRIPT +#ifdef JS_CX_PER_SESSION + if(js_cx!=NULL) { + + if(js_CreateUserClass(js_cx, js_glob, &scfg)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user class",sock); + + if(js_CreateUserObject(js_cx, js_glob, &scfg, "user", user.number)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user object",sock); + + if(js_CreateClientObject(js_cx, js_glob, "client", &client, sock)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating client object",sock); + + if(js_CreateFileAreaObject(js_cx, js_glob, &scfg, &user + ,startup->html_index_file)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating file area object",sock); + + } +#endif +#endif + if(sysop) sockprintf(sock,"230-Sysop access granted."); sockprintf(sock,"230-%s logged in.",user.alias); if(!(user.exempt&FLAG('D')) && (user.cdt+user.freecdt)>0) sockprintf(sock,"230-You have %lu download credits." ,user.cdt+user.freecdt); - sockprintf(sock,"230 You are allowed %ld minutes of use for this session." + sockprintf(sock,"230 You are allowed %lu minutes of use for this session." ,timeleft/60); sprintf(qwkfile,"%sfile/%04d.qwk",scfg.data_dir,user.number); @@ -1560,6 +2728,7 @@ static void ctrl_thread(void* arg) putuserrec(&scfg,user.number,U_MODEM,LEN_MODEM,"FTP"); putuserrec(&scfg,user.number,U_COMP,LEN_COMP,host_name); putuserrec(&scfg,user.number,U_NOTE,LEN_NOTE,host_ip); + getuserdat(&scfg, &user); /* make user current */ continue; } @@ -1569,9 +2738,12 @@ static void ctrl_thread(void* arg) continue; } + if(!(user.rest&FLAG('G'))) + getuserdat(&scfg, &user); /* get current user data */ + if((timeleft=gettimeleft(&scfg,&user,logintime))<1L) { sockprintf(sock,"421 Sorry, you've run out of time."); - lprintf("%04d Out of time, disconnecting",sock); + lprintf(LOG_WARNING,"%04d Out of time, disconnecting",sock); break; } @@ -1580,7 +2752,7 @@ static void ctrl_thread(void* arg) /********************************/ if(!stricmp(cmd, "REIN")) { - lprintf("%04d %s reinitialized control session",sock,user.alias); + lprintf(LOG_INFO,"%04d %s reinitialized control session",sock,user.alias); user.number=0; sysop=FALSE; filepos=0; @@ -1589,64 +2761,128 @@ static void ctrl_thread(void* arg) } if(!stricmp(cmd, "SITE WHO")) { - sockprintf(sock,"211-Active users"); + sockprintf(sock,"211-Active Telnet Nodes:"); for(i=0;i=SYSOP_LEVEL) { + startup->recycle_now=TRUE; + sockprintf(sock,"211 server will recycle when not in-use"); + continue; + } + if(!stricmp(cmd, "SITE RECYCLE ALL") && user.level>=SYSOP_LEVEL) { + refresh_cfg(&scfg); + sockprintf(sock,"211 ALL servers/nodes will recycle when not in-use"); + continue; + } + if(!strnicmp(cmd,"SITE EXEC ",10) && sysop) { + p=cmd+10; + while(*p && *p<=' ') p++; +#ifdef __unix__ + fp=popen(p,"r"); + if(fp==NULL) + sockprintf(sock,"500 Error %d opening pipe to: %s",errno,p); + else { + while(!feof(fp)) { + if(fgets(str,sizeof(str),fp)==NULL) + break; + sockprintf(sock,"200-%s",str); + } + sockprintf(sock,"200 %s returned %d",p,pclose(fp)); + } +#else + sockprintf(sock,"200 system(%s) returned %d",p,system(p)); +#endif + continue; + } + + +#ifdef SOCKET_DEBUG_CTRL + if(!stricmp(cmd, "SITE DEBUG")) { + sockprintf(sock,"211-Debug"); + for(i=0;ihack_sound[0] && !(startup->options&FTP_OPT_MUTE)) + PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME); +#endif + continue; /* As recommended by RFC2577 */ + } + data_addr.sin_port=htons(data_addr.sin_port); sockprintf(sock,"200 PORT Command successful."); continue; } - if(!strcmp(cmd, "PASV")) { + if(!stricmp(cmd, "PASV")) { if(pasv_sock!=INVALID_SOCKET) - close_socket(&pasv_sock,__LINE__); + ftp_close_socket(&pasv_sock,__LINE__); - if((pasv_sock=open_socket(SOCK_STREAM))==INVALID_SOCKET) { - lprintf("%04d !PASV ERROR %d opening socket", sock,ERROR_VALUE); + if((pasv_sock=ftp_open_socket(SOCK_STREAM))==INVALID_SOCKET) { + lprintf(LOG_WARNING,"%04d !PASV ERROR %d opening socket", sock,ERROR_VALUE); sockprintf(sock,"425 Error %d opening PASV data socket", ERROR_VALUE); continue; } if(startup->options&FTP_OPT_DEBUG_DATA) - lprintf("%04d PASV DATA socket %d opened",sock,pasv_sock); + lprintf(LOG_DEBUG,"%04d PASV DATA socket %d opened",sock,pasv_sock); pasv_addr.sin_port = 0; - if((result=bind(pasv_sock, (struct sockaddr *) &pasv_addr,sizeof(pasv_addr)))!= 0) { - lprintf("%04d !PASV ERROR %d (%d) binding socket", sock, result, ERROR_VALUE); + result=bind(pasv_sock, (struct sockaddr *) &pasv_addr,sizeof(pasv_addr)); + if(result!= 0) { + lprintf(LOG_ERR,"%04d !PASV ERROR %d (%d) binding socket", sock, result, ERROR_VALUE); sockprintf(sock,"425 Error %d binding data socket",ERROR_VALUE); - close_socket(&pasv_sock,__LINE__); + ftp_close_socket(&pasv_sock,__LINE__); continue; } addr_len=sizeof(addr); if((result=getsockname(pasv_sock, (struct sockaddr *)&addr,&addr_len))!=0) { - lprintf("%04d !PASV ERROR %d (%d) getting address/port", sock, result, ERROR_VALUE); + lprintf(LOG_ERR,"%04d !PASV ERROR %d (%d) getting address/port", sock, result, ERROR_VALUE); sockprintf(sock,"425 Error %d getting address/port",ERROR_VALUE); - close_socket(&pasv_sock,__LINE__); + ftp_close_socket(&pasv_sock,__LINE__); continue; } if((result=listen(pasv_sock, 1))!= 0) { - lprintf("%04d !PASV ERROR %d (%d) listening on socket", sock, result, ERROR_VALUE); + lprintf(LOG_ERR,"%04d !PASV ERROR %d (%d) listening on socket", sock, result, ERROR_VALUE); sockprintf(sock,"425 Error %d listening on data socket",ERROR_VALUE); - close_socket(&pasv_sock,__LINE__); + ftp_close_socket(&pasv_sock,__LINE__); continue; } @@ -1676,13 +2912,13 @@ static void ctrl_thread(void* arg) else l=0; if(local_fsys) - avail=getfreediskspace(local_dir); + avail=getfreediskspace(local_dir,0); else - avail=getfreediskspace(scfg.data_dir); /* Change to temp_dir? */ + avail=getfreediskspace(scfg.data_dir,0); /* Change to temp_dir? */ if(l && l>avail) - sockprintf(sock,"504 Only %ld bytes available.",avail); + sockprintf(sock,"504 Only %lu bytes available.",avail); else - sockprintf(sock,"200 %ld bytes available.",avail); + sockprintf(sock,"200 %lu bytes available.",avail); continue; } @@ -1693,7 +2929,8 @@ static void ctrl_thread(void* arg) filepos=atol(p); else filepos=0; - sockprintf(sock,"200 File marker set to %ld",filepos); + sockprintf(sock,"350 Restarting at %lu. Send STORE or RETRIEVE to initiate transfer." + ,filepos); continue; } @@ -1726,8 +2963,10 @@ static void ctrl_thread(void* arg) if(!transfer_inprogress) sockprintf(sock,"226 No tranfer in progress."); else { + lprintf(LOG_WARNING,"%04d %s aborting transfer" + ,sock,user.alias); transfer_aborted=TRUE; - mswait(1); /* give send thread time to abort */ + YIELD(); /* give send thread time to abort */ sockprintf(sock,"226 Transfer aborted."); } continue; @@ -1741,16 +2980,16 @@ static void ctrl_thread(void* arg) else { if(!direxist(p)) { sockprintf(sock,"550 Directory does not exist."); - lprintf("%04d !%s attempted to mount invalid directory: %s" + lprintf(LOG_WARNING,"%04d !%s attempted to mount invalid directory: %s" ,sock, user.alias, p); continue; } local_fsys=TRUE; - sprintf(local_dir,"%.*s",(int)sizeof(local_dir)-1,p); + SAFECOPY(local_dir,p); } sockprintf(sock,"250 %s file system mounted." ,local_fsys ? "Local" : "BBS"); - lprintf("%04d %s mounted %s file system" + lprintf(LOG_INFO,"%04d %s mounted %s file system" ,sock, user.alias, local_fsys ? "local" : "BBS"); continue; } @@ -1765,9 +3004,8 @@ static void ctrl_thread(void* arg) strcat(local_dir,"/"); if(!strnicmp(cmd, "LIST", 4) || !strnicmp(cmd, "NLST", 4)) { - sprintf(fname,"%sftp%d.tx", scfg.data_dir, sock); - if((fp=fopen(fname,"w+b"))==NULL) { - lprintf("%04d !ERROR %d opening %s",sock,errno,fname); + if((fp=fopen(ftp_tmpfname(fname,sock),"w+b"))==NULL) { + lprintf(LOG_ERR,"%04d !ERROR %d opening %s",sock,errno,fname); sockprintf(sock, "451 Insufficient system storage"); continue; } @@ -1778,20 +3016,21 @@ static void ctrl_thread(void* arg) p=cmd+4; while(*p && *p<=' ') p++; - sprintf(path,"%s%s",local_dir, *p ? p : "*"); - lprintf("%04d %s listing: %s", sock, user.alias, path); + SAFEPRINTF2(path,"%s%s",local_dir, *p ? p : "*"); + lprintf(LOG_INFO,"%04d %s listing: %s", sock, user.alias, path); sockprintf(sock, "150 Directory of %s%s", local_dir, p); + + now=time(NULL); + if(localtime_r(&now,&cur_tm)==NULL) + memset(&cur_tm,0,sizeof(cur_tm)); glob(path,0,NULL,&g); for(i=0;i<(int)g.gl_pathc;i++) { if(detail) { f.size=flength(g.gl_pathv[i]); t=fdate(g.gl_pathv[i]); - tm_p=localtime(&t); - if(tm_p==NULL) + if(localtime_r(&t,&tm)==NULL) memset(&tm,0,sizeof(tm)); - else - tm=*tm_p; fprintf(fp,"%crw-r--r-- 1 %-8s local %9ld %s %2d " ,isdir(g.gl_pathv[i]) ? 'd':'-' ,scfg.sys_id @@ -1830,7 +3069,7 @@ static void ctrl_thread(void* arg) if(!strnicmp(tp,BBS_FSYS_DIR,strlen(BBS_FSYS_DIR))) { local_fsys=FALSE; sockprintf(sock,"250 CWD command successful (BBS file system mounted)."); - lprintf("%04d %s mounted BBS file system", sock, user.alias); + lprintf(LOG_INFO,"%04d %s mounted BBS file system", sock, user.alias); continue; } if(!strnicmp(tp,LOCAL_FSYS_DIR,strlen(LOCAL_FSYS_DIR))) { @@ -1839,28 +3078,28 @@ static void ctrl_thread(void* arg) } if(p[1]==':' || !strncmp(p,"\\\\",2)) - strcpy(path,p); + SAFECOPY(path,p); else if(*p=='/' || *p=='\\') - sprintf(path,"%s%s",root_dir(local_dir),p); + SAFEPRINTF2(path,"%s%s",root_dir(local_dir),p); else { - sprintf(fname,"%s%s",local_dir,p); - _fullpath(path,fname,sizeof(path)); + SAFEPRINTF2(fname,"%s%s",local_dir,p); + FULLPATH(path,fname,sizeof(path)); } if(!direxist(path)) { sockprintf(sock,"550 Directory does not exist (%s).",path); - lprintf("%04d !%s attempted to change to an invalid directory: %s" + lprintf(LOG_WARNING,"%04d !%s attempted to change to an invalid directory: %s" ,sock, user.alias, path); } else { - strcpy(local_dir,path); + SAFECOPY(local_dir,path); sockprintf(sock,"250 CWD command successful (%s).", local_dir); } continue; } /* Local CWD */ if(!stricmp(cmd,"CDUP") || !stricmp(cmd,"XCUP")) { - sprintf(path,"%s..",local_dir); - if(_fullpath(local_dir,path,sizeof(local_dir))==NULL) + SAFEPRINTF(path,"%s..",local_dir); + if(FULLPATH(local_dir,path,sizeof(local_dir))==NULL) sockprintf(sock,"550 Directory does not exist."); else sockprintf(sock,"200 CDUP command successful."); @@ -1880,16 +3119,16 @@ static void ctrl_thread(void* arg) p=cmd+4; while(*p && *p<=' ') p++; if(*p=='/') /* absolute */ - sprintf(fname,"%s%s",root_dir(local_dir),p+1); + SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1); else /* relative */ - sprintf(fname,"%s%s",local_dir,p); + SAFEPRINTF2(fname,"%s%s",local_dir,p); - if((i=_mkdir(fname))==0) { + if((i=MKDIR(fname))==0) { sockprintf(sock,"257 \"%s\" directory created",fname); - lprintf("%04d %s created directory: %s",sock,user.alias,fname); + lprintf(LOG_NOTICE,"%04d %s created directory: %s",sock,user.alias,fname); } else { sockprintf(sock,"521 Error %d creating directory: %s",i,fname); - lprintf("%04d !%s attempted to create directory: %s (Error %d)" + lprintf(LOG_WARNING,"%04d !%s attempted to create directory: %s (Error %d)" ,sock,user.alias,fname,i); } continue; @@ -1899,16 +3138,16 @@ static void ctrl_thread(void* arg) p=cmd+4; while(*p && *p<=' ') p++; if(*p=='/') /* absolute */ - sprintf(fname,"%s%s",root_dir(local_dir),p+1); + SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1); else /* relative */ - sprintf(fname,"%s%s",local_dir,p); + SAFEPRINTF2(fname,"%s%s",local_dir,p); - if((i=_rmdir(fname))==0) { + if((i=rmdir(fname))==0) { sockprintf(sock,"250 \"%s\" directory removed",fname); - lprintf("%04d %s removed directory: %s",sock,user.alias,fname); + lprintf(LOG_NOTICE,"%04d %s removed directory: %s",sock,user.alias,fname); } else { sockprintf(sock,"450 Error %d removing directory: %s",i,fname); - lprintf("%04d !%s attempted to remove directory: %s (Error %d)" + lprintf(LOG_WARNING,"%04d !%s attempted to remove directory: %s (Error %d)" ,sock,user.alias,fname,i); } continue; @@ -1918,12 +3157,12 @@ static void ctrl_thread(void* arg) p=cmd+5; while(*p && *p<=' ') p++; if(*p=='/') /* absolute */ - sprintf(ren_from,"%s%s",root_dir(local_dir),p+1); + SAFEPRINTF2(ren_from,"%s%s",root_dir(local_dir),p+1); else /* relative */ - sprintf(ren_from,"%s%s",local_dir,p); + SAFEPRINTF2(ren_from,"%s%s",local_dir,p); if(!fexist(ren_from)) { sockprintf(sock,"550 File not found: %s",ren_from); - lprintf("%04d !%s attempted to rename %s (not found)" + lprintf(LOG_WARNING,"%04d !%s attempted to rename %s (not found)" ,sock,user.alias,ren_from); } else sockprintf(sock,"350 File exists, ready for destination name"); @@ -1934,16 +3173,16 @@ static void ctrl_thread(void* arg) p=cmd+5; while(*p && *p<=' ') p++; if(*p=='/') /* absolute */ - sprintf(fname,"%s%s",root_dir(local_dir),p+1); + SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1); else /* relative */ - sprintf(fname,"%s%s",local_dir,p); + SAFEPRINTF2(fname,"%s%s",local_dir,p); if((i=rename(ren_from, fname))==0) { sockprintf(sock,"250 \"%s\" renamed to \"%s\"",ren_from,fname); - lprintf("%04d %s renamed %s to %s",sock,user.alias,ren_from,fname); + lprintf(LOG_NOTICE,"%04d %s renamed %s to %s",sock,user.alias,ren_from,fname); } else { sockprintf(sock,"450 Error %d renaming file: %s",i,ren_from); - lprintf("%04d !%s attempted to rename file: %s (Error %d)" + lprintf(LOG_WARNING,"%04d !%s attempted to rename file: %s (Error %d)" ,sock,user.alias,ren_from,i); } continue; @@ -1959,13 +3198,13 @@ static void ctrl_thread(void* arg) p+=strlen(LOCAL_FSYS_DIR); /* already mounted */ if(p[1]==':') /* drive specified */ - strcpy(fname,p); + SAFECOPY(fname,p); else if(*p=='/') /* absolute, current drive */ - sprintf(fname,"%s%s",root_dir(local_dir),p+1); + SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1); else /* relative */ - sprintf(fname,"%s%s",local_dir,p); + SAFEPRINTF2(fname,"%s%s",local_dir,p); if(!fexist(fname)) { - lprintf("%04d !%s file not found: %s",sock,user.alias,fname); + lprintf(LOG_WARNING,"%04d !%s file not found: %s",sock,user.alias,fname); sockprintf(sock,"550 File not found: %s",fname); continue; } @@ -1975,11 +3214,8 @@ static void ctrl_thread(void* arg) } if(!strnicmp(cmd,"MDTM ",5)) { t=fdate(fname); - tm_p=gmtime(&t); - if(tm_p==NULL) + if(gmtime_r(&t,&tm)==NULL) /* specifically use GMT/UTC representation */ memset(&tm,0,sizeof(tm)); - else - tm=*tm_p; sockprintf(sock,"213 %u%02u%02u%02u%02u%02u" ,1900+tm.tm_year,tm.tm_mon+1,tm.tm_mday ,tm.tm_hour,tm.tm_min,tm.tm_sec); @@ -1988,17 +3224,18 @@ static void ctrl_thread(void* arg) if(!strnicmp(cmd,"DELE ",5)) { if((i=remove(fname))==0) { sockprintf(sock,"250 \"%s\" removed successfully.",fname); - lprintf("%04d %s deleted file: %s",sock,user.alias,fname); + lprintf(LOG_NOTICE,"%04d %s deleted file: %s",sock,user.alias,fname); } else { sockprintf(sock,"450 Error %d removing file: %s",i,fname); - lprintf("%04d !%s attempted to delete file: %s (Error %d)" + lprintf(LOG_WARNING,"%04d !%s attempted to delete file: %s (Error %d)" ,sock,user.alias,fname,i); } continue; } /* RETR */ - lprintf("%04d %s downloading: %s (%ld bytes)" - ,sock,user.alias,fname,flength(fname)); + lprintf(LOG_INFO,"%04d %s downloading: %s (%lu bytes) in %s mode" + ,sock,user.alias,fname,flength(fname) + ,pasv_sock==INVALID_SOCKET ? "active":"passive"); sockprintf(sock,"150 Opening BINARY mode data connection for file transfer."); filexfer(&data_addr,sock,pasv_sock,&data_sock,fname,filepos ,&transfer_inprogress,&transfer_aborted,FALSE,FALSE @@ -2014,13 +3251,14 @@ static void ctrl_thread(void* arg) p+=strlen(LOCAL_FSYS_DIR); /* already mounted */ if(p[1]==':') /* drive specified */ - strcpy(fname,p); + SAFECOPY(fname,p); else if(*p=='/') /* absolute, current drive */ - sprintf(fname,"%s%s",root_dir(local_dir),p+1); + SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1); else /* relative */ - sprintf(fname,"%s%s",local_dir,p); + SAFEPRINTF2(fname,"%s%s",local_dir,p); - lprintf("%04d %s uploading %s", sock,user.alias,fname); + lprintf(LOG_INFO,"%04d %s uploading: %s in %s mode", sock,user.alias,fname + ,pasv_sock==INVALID_SOCKET ? "active":"passive"); sockprintf(sock,"150 Opening BINARY mode data connection for file transfer."); filexfer(&data_addr,sock,pasv_sock,&data_sock,fname,filepos ,&transfer_inprogress,&transfer_aborted,FALSE,FALSE @@ -2042,7 +3280,7 @@ static void ctrl_thread(void* arg) lib=curlib; if(cmd[4]!=0) - lprintf("%04d LIST/NLST: %s",sock,cmd); + lprintf(LOG_DEBUG,"%04d LIST/NLST: %s",sock,cmd); /* path specified? */ p=cmd+4; @@ -2055,9 +3293,8 @@ static void ctrl_thread(void* arg) parsepath(&p,&user,&lib,&dir); - sprintf(fname,"%sftp%d.tx", scfg.data_dir, sock); - if((fp=fopen(fname,"w+b"))==NULL) { - lprintf("%04d !ERROR %d opening %s",sock,errno,fname); + if((fp=fopen(ftp_tmpfname(fname,sock),"w+b"))==NULL) { + lprintf(LOG_ERR,"%04d !ERROR %d opening %s",sock,errno,fname); sockprintf(sock, "451 Insufficient system storage"); continue; } @@ -2067,45 +3304,56 @@ static void ctrl_thread(void* arg) detail=FALSE; sockprintf(sock,"150 Opening ASCII mode data connection for /bin/ls."); now=time(NULL); - tm_p=localtime(&now); - if(tm_p==NULL) + if(localtime_r(&now,&cur_tm)==NULL) memset(&cur_tm,0,sizeof(cur_tm)); - else - cur_tm=*tm_p; + /* ASCII Index File */ if(startup->options&FTP_OPT_INDEX_FILE && startup->index_file_name[0] && (!stricmp(p,startup->index_file_name) || *p==0 || *p=='*')) { if(detail) - fprintf(fp,"-rw-r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" + fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" ,NAME_LEN ,scfg.sys_id ,lib<0 ? scfg.sys_id : dir<0 - ? scfg.lib[lib]->sname : scfg.dir[dir]->code + ? scfg.lib[lib]->sname : scfg.dir[dir]->code_suffix ,512L ,mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min ,startup->index_file_name); else fprintf(fp,"%s\r\n",startup->index_file_name); } + /* HTML Index File */ + if(startup->options&FTP_OPT_HTML_INDEX_FILE && startup->html_index_file[0] + && (!stricmp(p,startup->html_index_file) || *p==0 || *p=='*')) { + if(detail) + fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" + ,NAME_LEN + ,scfg.sys_id + ,lib<0 ? scfg.sys_id : dir<0 + ? scfg.lib[lib]->sname : scfg.dir[dir]->code_suffix + ,512L + ,mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min + ,startup->html_index_file); + else + fprintf(fp,"%s\r\n",startup->html_index_file); + } + if(lib<0) { /* Root dir */ - lprintf("%04d %s listing: root",sock,user.alias); + lprintf(LOG_INFO,"%04d %s listing: root",sock,user.alias); /* QWK Packet */ if(startup->options&FTP_OPT_ALLOW_QWK/* && fexist(qwkfile)*/) { if(detail) { - if(fexist(qwkfile)) { + if(fexistcase(qwkfile)) { t=fdate(qwkfile); l=flength(qwkfile); } else { t=time(NULL); l=10240; }; - tm_p=localtime(&t); - if(tm_p==NULL) + if(localtime_r(&t,&tm)==NULL) memset(&tm,0,sizeof(tm)); - else - tm=*tm_p; - fprintf(fp,"-rw-r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s.qwk\r\n" + fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s.qwk\r\n" ,NAME_LEN ,scfg.sys_id ,scfg.sys_id @@ -2121,7 +3369,7 @@ static void ctrl_thread(void* arg) if((alias_fp=fopen(aliasfile,"r"))!=NULL) { while(!feof(alias_fp)) { - if(!fgets(aliasline,sizeof(aliasline)-1,alias_fp)) + if(!fgets(aliasline,sizeof(aliasline),alias_fp)) break; alias_dir=FALSE; @@ -2152,7 +3400,7 @@ static void ctrl_thread(void* arg) continue; tp++; if(*tp) { - sprintf(aliasfile,"%s%s",scfg.dir[dir]->path,tp); + SAFEPRINTF2(aliasfile,"%s%s",scfg.dir[dir]->path,tp); np=aliasfile; } else @@ -2165,7 +3413,7 @@ static void ctrl_thread(void* arg) if(detail) { if(alias_dir==TRUE) { - fprintf(fp,"drwxr-xr-x 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" + fprintf(fp,"drwxrwxrwx 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" ,NAME_LEN ,scfg.sys_id ,scfg.lib[scfg.dir[dir]->lib]->sname @@ -2175,12 +3423,9 @@ static void ctrl_thread(void* arg) } else { t=fdate(np); - tm_p=localtime(&t); - if(tm_p==NULL) + if(localtime_r(&t,&tm)==NULL) memset(&tm,0,sizeof(tm)); - else - tm=*tm_p; - fprintf(fp,"-rw-r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" + fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" ,NAME_LEN ,scfg.sys_id ,scfg.sys_id @@ -2201,7 +3446,7 @@ static void ctrl_thread(void* arg) if(!chk_ar(&scfg,scfg.lib[i]->ar,&user)) continue; if(detail) - fprintf(fp,"drwxr-xr-x 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" + fprintf(fp,"dr-xr-xr-x 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" ,NAME_LEN ,scfg.sys_id ,scfg.sys_id @@ -2212,7 +3457,7 @@ static void ctrl_thread(void* arg) fprintf(fp,"%s\r\n",scfg.lib[i]->sname); } } else if(dir<0) { - lprintf("%04d %s listing: %s library",sock,user.alias,scfg.lib[lib]->sname); + lprintf(LOG_INFO,"%04d %s listing: %s library",sock,user.alias,scfg.lib[lib]->sname); for(i=0;ilib!=lib) continue; @@ -2220,21 +3465,21 @@ static void ctrl_thread(void* arg) && !chk_ar(&scfg,scfg.dir[i]->ar,&user)) continue; if(detail) - fprintf(fp,"drwxr-xr-x 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" + fprintf(fp,"drwxrwxrwx 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n" ,NAME_LEN ,scfg.sys_id ,scfg.lib[lib]->sname ,512L ,mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min - ,scfg.dir[i]->code); + ,scfg.dir[i]->code_suffix); else - fprintf(fp,"%s\r\n",scfg.dir[i]->code); + fprintf(fp,"%s\r\n",scfg.dir[i]->code_suffix); } } else if(chk_ar(&scfg,scfg.dir[dir]->ar,&user)) { - lprintf("%04d %s listing: %s/%s directory" - ,sock,user.alias,scfg.lib[lib]->sname,scfg.dir[dir]->code); + lprintf(LOG_INFO,"%04d %s listing: %s/%s directory" + ,sock,user.alias,scfg.lib[lib]->sname,scfg.dir[dir]->code_suffix); - sprintf(path,"%s%s",scfg.dir[dir]->path,*p ? p : "*"); + SAFEPRINTF2(path,"%s%s",scfg.dir[dir]->path,*p ? p : "*"); glob(path,0,NULL,&g); for(i=0;i<(int)g.gl_pathc;i++) { if(isdir(g.gl_pathv[i])) @@ -2242,10 +3487,9 @@ static void ctrl_thread(void* arg) #ifdef _WIN32 GetShortPathName(g.gl_pathv[i], str, sizeof(str)); #else - strcpy(str,g.gl_pathv[i]); + SAFECOPY(str,g.gl_pathv[i]); #endif padfname(getfname(str),f.name); - strupr(f.name); f.dir=dir; if((filedat=getfileixb(&scfg,&f))==FALSE && !(startup->options&FTP_OPT_DIR_FILES)) @@ -2254,15 +3498,19 @@ static void ctrl_thread(void* arg) f.size=flength(g.gl_pathv[i]); getfiledat(&scfg,&f); t=fdate(g.gl_pathv[i]); - tm_p=localtime(&t); - if(tm_p==NULL) + if(localtime_r(&t,&tm)==NULL) memset(&tm,0,sizeof(tm)); - else - tm=*tm_p; - fprintf(fp,"-rw-r--r-- 1 %-*s %-8s %9ld %s %2d " + if(filedat) { + if(f.misc&FM_ANON) + SAFECOPY(str,ANONYMOUS); + else + dotname(f.uler,str); + } else + SAFECOPY(str,scfg.sys_id); + fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9ld %s %2d " ,NAME_LEN - ,filedat ? dotname(f.uler,str) : scfg.sys_id - ,scfg.dir[dir]->code + ,str + ,scfg.dir[dir]->code_suffix ,f.size ,mon[tm.tm_mon],tm.tm_mday); if(tm.tm_year==cur_tm.tm_year) @@ -2278,8 +3526,8 @@ static void ctrl_thread(void* arg) } globfree(&g); } else - lprintf("%04d %s listing: %s/%s directory (empty - no access)" - ,sock,user.alias,scfg.lib[lib]->sname,scfg.dir[dir]->code); + lprintf(LOG_INFO,"%04d %s listing: %s/%s directory (empty - no access)" + ,sock,user.alias,scfg.lib[lib]->sname,scfg.dir[dir]->code_suffix); fclose(fp); filexfer(&data_addr,sock,pasv_sock,&data_sock,fname,0L @@ -2329,12 +3577,12 @@ static void ctrl_thread(void* arg) else if(!strncmp(p,"./",2)) p+=2; - if(lib<0 && alias(p, fname, &user, &dir)==TRUE) { + if(lib<0 && ftpalias(p, fname, &user, &dir)==TRUE) { success=TRUE; credits=TRUE; /* include in d/l stats */ tmpfile=FALSE; delfile=FALSE; - lprintf("%04d %s %.4s by alias: %s" + lprintf(LOG_INFO,"%04d %s %.4s by alias: %s" ,sock,user.alias,cmd,p); p=getfname(fname); if(dir>=0) @@ -2360,7 +3608,7 @@ static void ctrl_thread(void* arg) continue; if(!chk_ar(&scfg,scfg.dir[i]->ar,&user)) continue; - if(!stricmp(scfg.dir[i]->code,p)) + if(!stricmp(scfg.dir[i]->code_suffix,p)) break; } if(ihtml_index_file); + sprintf(str,"%s.qwk",scfg.sys_id); if(lib<0 && startup->options&FTP_OPT_ALLOW_QWK && !stricmp(p,str) && !delecmd) { - lprintf("%04d %s creating/updating QWK packet...",sock,user.alias); - sprintf(str,"%spack%04u.now",scfg.data_dir,user.number); - if((file=open(str,O_WRONLY|O_CREAT,S_IWRITE))==-1) { - lprintf("%04d !ERROR %d opening %s",sock, errno, str); - sockprintf(sock, "451 !ERROR %d creating semaphore file",errno); - filepos=0; - continue; - } - close(file); - t=time(NULL); - while(fexist(str)) { - if(time(NULL)-t>300) - break; - mswait(500); - } - if(fexist(str)) { - lprintf("%04d !TIMEOUT waiting for QWK packet creation",sock); - sockprintf(sock,"451 Time-out waiting for packet creation."); - remove(str); - filepos=0; - continue; - } - if(!fexist(qwkfile)) { - lprintf("%04d No QWK Packet created (no new messages)",sock); - sockprintf(sock,"550 No QWK packet created (no new messages)"); - filepos=0; - continue; + if(!fexistcase(qwkfile)) { + lprintf(LOG_INFO,"%04d %s creating QWK packet...",sock,user.alias); + sprintf(str,"%spack%04u.now",scfg.data_dir,user.number); + if(!ftouch(str)) + lprintf(LOG_ERR,"%04d !ERROR creating semaphore file: %s" + ,sock, str); + t=time(NULL); + while(fexist(str)) { + if(time(NULL)-t>startup->qwk_timeout) + break; + mswait(1000); + } + if(fexist(str)) { + lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for QWK packet creation",sock); + sockprintf(sock,"451 Time-out waiting for packet creation."); + remove(str); + filepos=0; + continue; + } + if(!fexistcase(qwkfile)) { + lprintf(LOG_INFO,"%04d No QWK Packet created (no new messages)",sock); + sockprintf(sock,"550 No QWK packet created (no new messages)"); + filepos=0; + continue; + } } - strcpy(fname,qwkfile); + SAFECOPY(fname,qwkfile); success=TRUE; delfile=TRUE; credits=FALSE; - lprintf("%04d %s downloading QWK packet (%ld bytes)" - ,sock,user.alias,flength(fname)); + if(!getsize && !getdate) + lprintf(LOG_INFO,"%04d %s downloading QWK packet (%lu bytes) in %s mode" + ,sock,user.alias,flength(fname) + ,pasv_sock==INVALID_SOCKET ? "active":"passive"); + /* ASCII Index File */ } else if(startup->options&FTP_OPT_INDEX_FILE && !stricmp(p,startup->index_file_name) && !delecmd) { - sprintf(fname,"%sftp%d.tx", scfg.data_dir, sock); - if((fp=fopen(fname,"w+b"))==NULL) { - lprintf("%04d !ERROR %d opening %s",sock,errno,fname); + if((fp=fopen(ftp_tmpfname(fname,sock),"w+b"))==NULL) { + lprintf(LOG_ERR,"%04d !ERROR %d opening %s",sock,errno,fname); sockprintf(sock, "451 Insufficient system storage"); filepos=0; continue; } if(!getsize && !getdate) - lprintf("%04d %s downloading index",sock,user.alias); + lprintf(LOG_INFO,"%04d %s downloading index for %s in %s mode" + ,sock,user.alias,vpath(lib,dir,str) + ,pasv_sock==INVALID_SOCKET ? "active":"passive"); success=TRUE; credits=FALSE; tmpfile=TRUE; delfile=TRUE; fprintf(fp,"%-*s File/Folder Descriptions\r\n" ,INDEX_FNAME_LEN,startup->index_file_name); + if(startup->options&FTP_OPT_HTML_INDEX_FILE) + fprintf(fp,"%-*s File/Folder Descriptions (HTML)\r\n" + ,INDEX_FNAME_LEN,startup->html_index_file); if(lib<0) { /* File Aliases */ @@ -2430,7 +3685,7 @@ static void ctrl_thread(void* arg) if((alias_fp=fopen(aliasfile,"r"))!=NULL) { while(!feof(alias_fp)) { - if(!fgets(aliasline,sizeof(aliasline)-1,alias_fp)) + if(!fgets(aliasline,sizeof(aliasline),alias_fp)) break; p=aliasline; /* alias pointer */ @@ -2481,9 +3736,9 @@ static void ctrl_thread(void* arg) && !chk_ar(&scfg,scfg.dir[i]->ar,&user)) continue; fprintf(fp,"%-*s %s\r\n" - ,INDEX_FNAME_LEN,scfg.dir[i]->code,scfg.dir[i]->lname); + ,INDEX_FNAME_LEN,scfg.dir[i]->code_suffix,scfg.dir[i]->lname); } - } else { + } else if(chk_ar(&scfg,scfg.dir[dir]->ar,&user)){ sprintf(cmd,"%s*",scfg.dir[dir]->path); glob(cmd,0,NULL,&g); for(i=0;i<(int)g.gl_pathc;i++) { @@ -2492,10 +3747,9 @@ static void ctrl_thread(void* arg) #ifdef _WIN32 GetShortPathName(g.gl_pathv[i], str, sizeof(str)); #else - strcpy(str,g.gl_pathv[i]); + SAFECOPY(str,g.gl_pathv[i]); #endif padfname(getfname(str),f.name); - strupr(f.name); f.dir=dir; if(getfileixb(&scfg,&f)) { f.size=flength(g.gl_pathv[i]); @@ -2507,11 +3761,125 @@ static void ctrl_thread(void* arg) globfree(&g); } fclose(fp); + /* HTML Index File */ + } else if(startup->options&FTP_OPT_HTML_INDEX_FILE + && (!stricmp(p,startup->html_index_file) + || !strnicmp(p,html_index_ext,strlen(html_index_ext))) + && !delecmd) { +#ifdef JAVASCRIPT + if(startup->options&FTP_OPT_NO_JAVASCRIPT) { + lprintf(LOG_ERR,"%04d !JavaScript disabled, cannot generate %s",sock,fname); + sockprintf(sock, "451 JavaScript disabled"); + filepos=0; + continue; + } + if(js_runtime == NULL) { + lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes" + ,sock,startup->js_max_bytes); + + if((js_runtime = JS_NewRuntime(startup->js_max_bytes))==NULL) { + lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",sock); + sockprintf(sock,"451 Error creating JavaScript runtime"); + filepos=0; + continue; + } + } + + if(js_cx==NULL) { /* Context not yet created, create it now */ + if(((js_cx=js_initcx(js_runtime, sock,&js_glob,&js_ftp))==NULL)) { + lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript context",sock); + sockprintf(sock,"451 Error initializing JavaScript context"); + filepos=0; + continue; + } + if(js_CreateUserClass(js_cx, js_glob, &scfg)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user class",sock); + + if(js_CreateFileClass(js_cx, js_glob)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating file class",sock); + + if(js_CreateUserObject(js_cx, js_glob, &scfg, "user", user.number)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user object",sock); + + if(js_CreateClientObject(js_cx, js_glob, "client", &client, sock)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating client object",sock); + + if(js_CreateFileAreaObject(js_cx, js_glob, &scfg, &user + ,startup->html_index_file)==NULL) + lprintf(LOG_ERR,"%04d !JavaScript ERROR creating file area object",sock); + } + + if((js_str=JS_NewStringCopyZ(js_cx, "name"))!=NULL) { + js_val=STRING_TO_JSVAL(js_str); + JS_SetProperty(js_cx, js_ftp, "sort", &js_val); + } + js_val=BOOLEAN_TO_JSVAL(FALSE); + JS_SetProperty(js_cx, js_ftp, "reverse", &js_val); + + if(!strnicmp(p,html_index_ext,strlen(html_index_ext))) { + p+=strlen(html_index_ext); + tp=strrchr(p,'$'); + if(tp!=NULL) + *tp=0; + if(!strnicmp(p,"ext=",4)) { + p+=4; + if(!strcmp(p,"on")) + user.misc|=EXTDESC; + else + user.misc&=~EXTDESC; + if(!(user.rest&FLAG('G'))) + putuserrec(&scfg,user.number,U_MISC,8,ultoa(user.misc,str,16)); + } + else if(!strnicmp(p,"sort=",5)) { + p+=5; + tp=strchr(p,'&'); + if(tp!=NULL) { + *tp=0; + tp++; + if(!stricmp(tp,"reverse")) { + js_val=BOOLEAN_TO_JSVAL(TRUE); + JS_SetProperty(js_cx, js_ftp, "reverse", &js_val); + } + } + if((js_str=JS_NewStringCopyZ(js_cx, p))!=NULL) { + js_val=STRING_TO_JSVAL(js_str); + JS_SetProperty(js_cx, js_ftp, "sort", &js_val); + } + } + } + + js_val=BOOLEAN_TO_JSVAL(INT_TO_BOOL(user.misc&EXTDESC)); + JS_SetProperty(js_cx, js_ftp, "extended_descriptions", &js_val); + +#endif + if((fp=fopen(ftp_tmpfname(fname,sock),"w+b"))==NULL) { + lprintf(LOG_ERR,"%04d !ERROR %d opening %s",sock,errno,fname); + sockprintf(sock, "451 Insufficient system storage"); + filepos=0; + continue; + } + if(!getsize && !getdate) + lprintf(LOG_INFO,"%04d %s downloading HTML index for %s in %s mode" + ,sock,user.alias,vpath(lib,dir,str) + ,pasv_sock==INVALID_SOCKET ? "active":"passive"); + success=TRUE; + credits=FALSE; + tmpfile=TRUE; + delfile=TRUE; +#ifdef JAVASCRIPT + js_val=INT_TO_JSVAL(timeleft); + if(!JS_SetProperty(js_cx, js_ftp, "time_left", &js_val)) + lprintf(LOG_ERR,"%04d !JavaScript ERROR setting user.time_left",sock); + js_generate_index(js_cx, js_ftp, sock, fp, lib, dir, &user); +#endif + fclose(fp); } else if(dir>=0) { if(!chk_ar(&scfg,scfg.dir[dir]->ar,&user)) { - lprintf("%04d !%s has insufficient access to /%s/%s" - ,sock,user.alias,scfg.lib[scfg.dir[dir]->lib]->sname,scfg.dir[dir]->code); + lprintf(LOG_WARNING,"%04d !%s has insufficient access to /%s/%s" + ,sock,user.alias + ,scfg.lib[scfg.dir[dir]->lib]->sname + ,scfg.dir[dir]->code_suffix); sockprintf(sock,"550 Insufficient access."); filepos=0; continue; @@ -2519,51 +3887,54 @@ static void ctrl_thread(void* arg) if(!getsize && !getdate && !delecmd && !chk_ar(&scfg,scfg.dir[dir]->dl_ar,&user)) { - lprintf("%04d !%s has insufficient access to download from /%s/%s" - ,sock,user.alias,scfg.lib[scfg.dir[dir]->lib]->sname,scfg.dir[dir]->code); + lprintf(LOG_WARNING,"%04d !%s has insufficient access to download from /%s/%s" + ,sock,user.alias + ,scfg.lib[scfg.dir[dir]->lib]->sname + ,scfg.dir[dir]->code_suffix); sockprintf(sock,"550 Insufficient access."); filepos=0; continue; } if(delecmd && !dir_op(&scfg,&user,dir)) { - lprintf("%04d !%s has insufficient access to delete files in /%s/%s" - ,sock,user.alias,scfg.lib[scfg.dir[dir]->lib]->sname,scfg.dir[dir]->code); + lprintf(LOG_WARNING,"%04d !%s has insufficient access to delete files in /%s/%s" + ,sock,user.alias + ,scfg.lib[scfg.dir[dir]->lib]->sname + ,scfg.dir[dir]->code_suffix); sockprintf(sock,"550 Insufficient access."); filepos=0; continue; } - sprintf(fname,"%s%s",scfg.dir[dir]->path,p); + SAFEPRINTF2(fname,"%s%s",scfg.dir[dir]->path,p); #ifdef _WIN32 GetShortPathName(fname, str, sizeof(str)); #else - strcpy(str,fname); + SAFECOPY(str,fname); #endif padfname(getfname(str),f.name); - strupr(f.name); f.dir=dir; f.cdt=0; f.size=-1; filedat=getfileixb(&scfg,&f); if(!filedat && !(startup->options&FTP_OPT_DIR_FILES)) { sockprintf(sock,"550 File not found: %s",p); - lprintf("%04d !%s file not in database (%s) for %.4s command" - ,sock,user.alias,fname,cmd); + lprintf(LOG_WARNING,"%04d !%s file (%s%s) not in database for %.4s command" + ,sock,user.alias,vpath(lib,dir,str),p,cmd); filepos=0; continue; } /* Verify credits */ if(!getsize && !getdate && !delecmd - && !(scfg.dir[dir]->misc&DIR_FREE) && !(user.exempt&FLAG('D'))) { + && !is_download_free(&scfg,dir,&user)) { if(filedat) getfiledat(&scfg,&f); else f.cdt=flength(fname); if(f.cdt>(user.cdt+user.freecdt)) { - lprintf("%04d !%s has insufficient credit to download /%s/%s/%s (%lu credits)" + lprintf(LOG_WARNING,"%04d !%s has insufficient credit to download /%s/%s/%s (%lu credits)" ,sock,user.alias,scfg.lib[scfg.dir[dir]->lib]->sname - ,scfg.dir[dir]->code + ,scfg.dir[dir]->code_suffix ,p ,f.cdt); sockprintf(sock,"550 Insufficient credit (%lu required).",f.cdt); @@ -2574,36 +3945,43 @@ static void ctrl_thread(void* arg) if(strcspn(p,ILLEGAL_FILENAME_CHARS)!=strlen(p)) { success=FALSE; - lprintf("%04d !%s illegal filename attempt: %s" + lprintf(LOG_WARNING,"%04d !ILLEGAL FILENAME ATTEMPT by %s: %s" ,sock,user.alias,p); + hacklog(&scfg, "FTP", user.alias, cmd, host_name, &ftp.client_addr); +#ifdef _WIN32 + if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) + PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME); +#endif } else { if(fexist(fname)) { success=TRUE; if(!getsize && !getdate && !delecmd) - lprintf("%04d %s downloading: %s (%ld bytes)" - ,sock,user.alias,fname,flength(fname)); + lprintf(LOG_INFO,"%04d %s downloading: %s (%lu bytes) in %s mode" + ,sock,user.alias,fname,flength(fname) + ,pasv_sock==INVALID_SOCKET ? "active":"passive"); } } } +#if defined(SOCKET_DEBUG_DOWNLOAD) + socket_debug[sock]|=SOCKET_DEBUG_DOWNLOAD; +#endif + if(getsize && success) sockprintf(sock,"213 %lu",flength(fname)); else if(getdate && success) { t=fdate(fname); - tm_p=gmtime(&t); - if(tm_p==NULL) + if(gmtime_r(&t,&tm)==NULL) /* specifically use GMT/UTC representation */ memset(&tm,0,sizeof(tm)); - else - tm=*tm_p; sockprintf(sock,"213 %u%02u%02u%02u%02u%02u" ,1900+tm.tm_year,tm.tm_mon+1,tm.tm_mday ,tm.tm_hour,tm.tm_min,tm.tm_sec); } else if(delecmd && success) { if(remove(fname)!=0) { - lprintf("%04d !ERROR %d deleting %s",sock,errno,fname); + lprintf(LOG_ERR,"%04d !ERROR %d deleting %s",sock,errno,fname); sockprintf(sock,"450 %s could not be deleted (error: %d)" ,fname,errno); } else { - lprintf("%04d %s deleted %s",sock,user.alias,fname); + lprintf(LOG_NOTICE,"%04d %s deleted %s",sock,user.alias,fname); if(filedat) removefiledat(&scfg,&f); sockprintf(sock,"250 %s deleted.",fname); @@ -2616,10 +3994,13 @@ static void ctrl_thread(void* arg) } else { sockprintf(sock,"550 File not found: %s",p); - lprintf("%04d !%s file not found (%s) for %.4s command" - ,sock,user.alias,p,cmd); + lprintf(LOG_WARNING,"%04d !%s file (%s%s) not found for %.4s command" + ,sock,user.alias,vpath(lib,dir,str),p,cmd); } filepos=0; +#if defined(SOCKET_DEBUG_DOWNLOAD) + socket_debug[sock]&=~SOCKET_DEBUG_DOWNLOAD; +#endif continue; } @@ -2636,13 +4017,13 @@ static void ctrl_thread(void* arg) if(*p==0) sockprintf(sock,"501 No file description given."); else { - sprintf(desc,"%.*s",(int)sizeof(desc)-1,p); + SAFECOPY(desc,p); sockprintf(sock,"200 File description set. Ready to STOR file."); } continue; } - if(!strnicmp(cmd, "STOR ", 5)) { + if(!strnicmp(cmd, "STOR ", 5) || !strnicmp(cmd, "APPE ", 5)) { if(user.rest&FLAG('U')) { sockprintf(sock,"553 Insufficient access."); @@ -2650,11 +4031,12 @@ static void ctrl_thread(void* arg) } if(transfer_inprogress==TRUE) { - lprintf("%04d !TRANSFER already in progress (%s)",sock,cmd); + lprintf(LOG_WARNING,"%04d !TRANSFER already in progress (%s)",sock,cmd); sockprintf(sock,"425 Transfer already in progress."); continue; } + append=FALSE; lib=curlib; dir=curdir; p=cmd+5; @@ -2670,6 +4052,7 @@ static void ctrl_thread(void* arg) } else if(!strncmp(p,"./",2)) p+=2; + /* Need to add support for uploading to aliased directories */ if(lib<0 && (tp=strchr(p,'/'))!=NULL) { dir=-1; *tp=0; @@ -2688,9 +4071,10 @@ static void ctrl_thread(void* arg) for(i=0;ilib!=lib) continue; - if(!chk_ar(&scfg,scfg.dir[i]->ar,&user)) + if(i!=scfg.sysop_dir && i!=scfg.upload_dir + && !chk_ar(&scfg,scfg.dir[i]->ar,&user)) continue; - if(!stricmp(scfg.dir[i]->code,p)) + if(!stricmp(scfg.dir[i]->code_suffix,p)) break; } if(ioptions&FTP_OPT_ALLOW_QWK) || stricmp(p,str)) { - lprintf("%04d !%s attempted to upload to invalid directory" + lprintf(LOG_WARNING,"%04d !%s attempted to upload to invalid directory" ,sock,user.alias); sockprintf(sock,"553 Invalid directory."); continue; } sprintf(fname,"%sfile/%04d.rep",scfg.data_dir,user.number); - lprintf("%04d %s uploading %s" - ,sock,user.alias,fname); + lprintf(LOG_INFO,"%04d %s uploading: %s in %s mode" + ,sock,user.alias,fname + ,pasv_sock==INVALID_SOCKET ? "active":"passive"); } else { + + append=(strnicmp(cmd,"APPE",4)==0); + if(!chk_ar(&scfg,scfg.dir[dir]->ul_ar,&user)) { - lprintf("%04d !%s has insufficient access to upload to /%s/%s" - ,sock,user.alias,scfg.lib[scfg.dir[dir]->lib]->sname,scfg.dir[dir]->code); + lprintf(LOG_WARNING,"%04d !%s has insufficient access to upload to /%s/%s" + ,sock,user.alias + ,scfg.lib[scfg.dir[dir]->lib]->sname + ,scfg.dir[dir]->code_suffix); sockprintf(sock,"553 Insufficient access."); continue; } - if(strcspn(p,ILLEGAL_FILENAME_CHARS)!=strlen(p) + if(*p=='-' + || strcspn(p,ILLEGAL_FILENAME_CHARS)!=strlen(p) || trashcan(&scfg,p,"file")) { - lprintf("%04d !%s illegal filename attempt: %s" + lprintf(LOG_WARNING,"%04d !ILLEGAL FILENAME ATTEMPT by %s: %s" ,sock,user.alias,p); sockprintf(sock,"553 Illegal filename attempt"); + hacklog(&scfg, "FTP", user.alias, cmd, host_name, &ftp.client_addr); +#ifdef _WIN32 + if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) + PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME); +#endif continue; } - sprintf(fname,"%s%s",scfg.dir[dir]->path,p); - if(fexist(fname)) { - lprintf("%04d !%s attempted to overwrite existing file: %s" + SAFEPRINTF2(fname,"%s%s",scfg.dir[dir]->path,p); + if((!append && filepos==0 && fexist(fname)) + || (startup->options&FTP_OPT_INDEX_FILE + && !stricmp(p,startup->index_file_name)) + || (startup->options&FTP_OPT_HTML_INDEX_FILE + && !stricmp(p,startup->html_index_file)) + ) { + lprintf(LOG_WARNING,"%04d !%s attempted to overwrite existing file: %s" ,sock,user.alias,fname); sockprintf(sock,"553 File already exists."); continue; } - lprintf("%04d %s uploading %s to /%s/%s" - ,sock,user.alias,fname - ,scfg.lib[scfg.dir[dir]->lib]->sname,scfg.dir[dir]->code); + if(append || filepos) { /* RESUME */ +#ifdef _WIN32 + GetShortPathName(fname, str, sizeof(str)); +#else + SAFECOPY(str,fname); +#endif + padfname(getfname(str),f.name); + f.dir=dir; + f.cdt=0; + f.size=-1; + if(!getfileixb(&scfg,&f) || !getfiledat(&scfg,&f)) { + if(filepos) { + lprintf(LOG_WARNING,"%04d !%s file (%s) not in database for %.4s command" + ,sock,user.alias,fname,cmd); + sockprintf(sock,"550 File not found: %s",p); + continue; + } + append=FALSE; + } + /* Verify user is original uploader */ + if((append || filepos) && stricmp(f.uler,user.alias)) { + lprintf(LOG_WARNING,"%04d !%s cannot resume upload of %s, uploaded by %s" + ,sock,user.alias,fname,f.uler); + sockprintf(sock,"553 Insufficient access (can't resume upload from different user)."); + continue; + } + } + lprintf(LOG_INFO,"%04d %s uploading: %s to %s (%s) in %s mode" + ,sock,user.alias + ,p /* filename */ + ,vpath(lib,dir,str) /* virtual path */ + ,scfg.dir[dir]->path /* actual path */ + ,pasv_sock==INVALID_SOCKET ? "active":"passive"); } sockprintf(sock,"150 Opening BINARY mode data connection for file transfer."); filexfer(&data_addr,sock,pasv_sock,&data_sock,fname,filepos @@ -2742,7 +4173,7 @@ static void ctrl_thread(void* arg) ,dir ,TRUE /* uploading */ ,TRUE /* credits */ - ,FALSE /* append */ + ,append ,desc ); filepos=0; @@ -2776,20 +4207,20 @@ static void ctrl_thread(void* arg) p+=strlen(LOCAL_FSYS_DIR); if(!direxist(p)) { sockprintf(sock,"550 Directory does not exist."); - lprintf("%04d !%s attempted to mount invalid directory: %s" + lprintf(LOG_WARNING,"%04d !%s attempted to mount invalid directory: %s" ,sock, user.alias, p); continue; } - sprintf(local_dir,"%.*s",(int)sizeof(local_dir)-1,p); + SAFECOPY(local_dir,p); local_fsys=TRUE; sockprintf(sock,"250 CWD command successful (local file system mounted)."); - lprintf("%04d %s mounted local file system", sock, user.alias); + lprintf(LOG_INFO,"%04d %s mounted local file system", sock, user.alias); continue; } success=FALSE; /* Directory Alias? */ - if(curlib<0 && alias(p,NULL,&user,&curdir)==TRUE) { + if(curlib<0 && ftpalias(p,NULL,&user,&curdir)==TRUE) { if(curdir>=0) curlib=scfg.dir[curdir]->lib; success=TRUE; @@ -2834,15 +4265,15 @@ static void ctrl_thread(void* arg) if((!success && curdir<0) || (success && tp && *(tp+1))) { if(tp) p=tp+1; - tp=strchr(p,'/'); - if(tp) *tp=0; + tp=lastchar(p); + if(tp && *tp=='/') *tp=0; for(i=0;ilib!=curlib) continue; if(i!=scfg.sysop_dir && i!=scfg.upload_dir && !chk_ar(&scfg,scfg.dir[i]->ar,&user)) continue; - if(!stricmp(scfg.dir[i]->code,p)) + if(!stricmp(scfg.dir[i]->code_suffix,p)) break; } if(isname); else sockprintf(sock,"257 \"/%s/%s\" is current directory." - ,scfg.lib[curlib]->sname,scfg.dir[curdir]->code); + ,scfg.lib[curlib]->sname + ,scfg.dir[curdir]->code_suffix); continue; } - + + if(!strnicmp(cmd, "MKD", 3) || + !strnicmp(cmd,"XMKD",4) || + !strnicmp(cmd,"SITE EXEC",9)) { + lprintf(LOG_WARNING,"%04d !SUSPECTED HACK ATTEMPT by %s: '%s'" + ,sock,user.alias,cmd); + hacklog(&scfg, "FTP", user.alias, cmd, host_name, &ftp.client_addr); +#ifdef _WIN32 + if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) + PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME); +#endif + } sockprintf(sock,"500 Syntax error: '%s'",cmd); - lprintf("%04d !FTP: UNSUPPORTED COMMAND: '%s'",sock,cmd); - } + lprintf(LOG_WARNING,"%04d !UNSUPPORTED COMMAND from %s: '%s'" + ,sock,user.alias,cmd); + } /* while(1) */ + +#if defined(SOCKET_DEBUG_TERMINATE) + socket_debug[sock]|=SOCKET_DEBUG_TERMINATE; +#endif if(transfer_inprogress==TRUE) { - lprintf("%04d Waiting for transfer to complete...",sock); - while(data_sock!=INVALID_SOCKET && transfer_inprogress==TRUE) { - mswait(500); - if(gettimeleft(&scfg,&user,logintime)<1) { - lprintf("%04d Out of time, disconnecting",sock); - sockprintf(sock,"421 Sorry, you've run out of time."); - close_socket(&data_sock,__LINE__); - transfer_aborted=TRUE; + lprintf(LOG_DEBUG,"%04d Waiting for transfer to complete...",sock); + count=0; + while(transfer_inprogress==TRUE) { + if(server_socket==INVALID_SOCKET || terminate_server) { + mswait(2000); /* allow xfer threads to terminate */ + break; } - if((time(NULL)-lastactive)>startup->max_inactivity) { - lprintf("%04d Disconnecting due to to inactivity.",sock); - sockprintf(sock,"421 Disconnecting due to inactivity (%u seconds)." - ,startup->max_inactivity); - close_socket(&data_sock,__LINE__); - transfer_aborted=TRUE; + if(!transfer_aborted) { + if(gettimeleft(&scfg,&user,logintime)<1) { + lprintf(LOG_WARNING,"%04d Out of time, disconnecting",sock); + sockprintf(sock,"421 Sorry, you've run out of time."); + ftp_close_socket(&data_sock,__LINE__); + transfer_aborted=TRUE; + } + if((time(NULL)-lastactive)>startup->max_inactivity) { + lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock); + sockprintf(sock,"421 Disconnecting due to inactivity (%u seconds)." + ,startup->max_inactivity); + ftp_close_socket(&data_sock,__LINE__); + transfer_aborted=TRUE; + } } + if(count && (count%60)==0) + lprintf(LOG_WARNING,"%04d Still waiting for transfer to complete " + "(count=%lu, aborted=%d, lastactive=%lX) ..." + ,sock,count,transfer_aborted,lastactive); + count++; + mswait(1000); } - lprintf("%04d Done waiting for transfer to complete",sock); + lprintf(LOG_DEBUG,"%04d Done waiting for transfer to complete",sock); } - /* Update User Statistics */ if(user.number) { - user.tlast=(ushort)(time(NULL)-logintime); - putuserrec(&scfg,user.number,U_LASTON,8,ultoa(time(NULL),str,16)); - putuserrec(&scfg,user.number,U_TLAST,5,ultoa(user.tlast,str,10)); - adjustuserrec(&scfg,user.number,U_TIMEON,5,user.tlast); + /* Update User Statistics */ + logoutuserdat(&scfg, &user, time(NULL), logintime); + /* Remove QWK-pack semaphore file (if left behind) */ + sprintf(str,"%spack%04u.now",scfg.data_dir,user.number); + remove(str); + lprintf(LOG_INFO,"%04d %s logged off",sock,user.alias); } - if(user.number) - lprintf("%04d %s logged off.",sock,user.alias); - #ifdef _WIN32 if(startup->hangup_sound[0] && !(startup->options&FTP_OPT_MUTE)) PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME); #endif - status(STATUS_WFC); +#ifdef JAVASCRIPT + if(js_cx!=NULL) { + lprintf(LOG_DEBUG,"%04d JavaScript: Destroying context",sock); + JS_DestroyContext(js_cx); /* Free Context */ + } - lprintf("%04d CTRL thread terminated", sock); - active_clients--; - update_clients(); - client_off(sock); + if(js_runtime!=NULL) { + lprintf(LOG_DEBUG,"%04d JavaScript: Destroying runtime",sock); + JS_DestroyRuntime(js_runtime); + } + +#endif + +/* status(STATUS_WFC); server thread should control status display */ - /* Free up resources here */ - close_socket(&sock,__LINE__); if(pasv_sock!=INVALID_SOCKET) - close_socket(&pasv_sock,__LINE__); + ftp_close_socket(&pasv_sock,__LINE__); if(data_sock!=INVALID_SOCKET) - close_socket(&data_sock,__LINE__); + ftp_close_socket(&data_sock,__LINE__); + + client_off(sock); + +#ifdef SOCKET_DEBUG_CTRL + socket_debug[sock]&=~SOCKET_DEBUG_CTRL; +#endif + +#if defined(SOCKET_DEBUG_TERMINATE) + socket_debug[sock]&=~SOCKET_DEBUG_TERMINATE; +#endif + + tmp_sock=sock; + ftp_close_socket(&tmp_sock,__LINE__); + + if(active_clients) + active_clients--, update_clients(); thread_down(); + lprintf(LOG_DEBUG,"%04d CTRL thread terminated (%d clients, %u threads remain, %lu served)" + ,sock, active_clients, thread_count, served); } -static void cleanup(int code) +static void cleanup(int code, int line) { +#ifdef _DEBUG + lprintf(LOG_INFO,"0000 cleanup called from line %d",line); +#endif + free_cfg(&scfg); + free_text(text); + + semfile_list_free(&recycle_semfiles); + semfile_list_free(&shutdown_semfiles); + if(server_socket!=INVALID_SOCKET) - close_socket(&server_socket,__LINE__); - server_socket=INVALID_SOCKET; + ftp_close_socket(&server_socket,__LINE__); update_clients(); #ifdef _WINSOCKAPI_ if(WSAInitialized && WSACleanup()!=0) - lprintf("0000 !WSACleanup ERROR %d",ERROR_VALUE); + lprintf(LOG_ERR,"0000 !WSACleanup ERROR %d",ERROR_VALUE); #endif - lprintf("#### FTP Server thread terminated"); + thread_down(); status("Down"); + if(terminate_server || code) + lprintf(LOG_INFO,"#### FTP Server thread terminated (%u threads remain, %lu clients served)" + ,thread_count, served); if(startup!=NULL && startup->terminated!=NULL) - startup->terminated(code); - thread_down(); + startup->terminated(startup->cbdata,code); } -char* DLLCALL ftp_ver(void) +const char* DLLCALL ftp_ver(void) { static char ver[256]; char compiler[32]; - COMPILER_DESC(compiler); + DESCRIBE_COMPILER(compiler); + + sscanf("$Revision: 1.1.1.2 $", "%*s %s", revision); - sprintf(ver,"Synchronet FTP Server v%s%s " + sprintf(ver,"%s %s%s " "Compiled %s %s with %s" - ,FTP_VERSION + ,FTP_SERVER + ,revision #ifdef _DEBUG ," Debug" #else @@ -2974,23 +4469,32 @@ char* DLLCALL ftp_ver(void) void DLLCALL ftp_server(void* arg) { + char* p; + char path[MAX_PATH+1]; + char error[256]; char compiler[32]; + char str[256]; SOCKADDR_IN server_addr; SOCKADDR_IN client_addr; - int client_addr_len; + socklen_t client_addr_len; SOCKET client_socket; int i; int result; time_t t; time_t start; - LINGER linger; + time_t initialized=0; fd_set socket_set; - SOCKET high_socket_set; ftp_t* ftp; struct timeval tv; + ftp_ver(); + startup=(ftp_startup_t*)arg; +#ifdef _THREAD_SUID_BROKEN + startup->seteuid(TRUE); +#endif + if(startup==NULL) { sbbs_beep(100,500); fprintf(stderr, "No startup structure passed!\n"); @@ -3005,225 +4509,329 @@ void DLLCALL ftp_server(void* arg) return; } - thread_up(); + /* Setup intelligent defaults */ + if(startup->port==0) startup->port=IPPORT_FTP; + if(startup->qwk_timeout==0) startup->qwk_timeout=600; /* seconds */ + if(startup->max_inactivity==0) startup->max_inactivity=300; /* seconds */ + if(startup->sem_chk_freq==0) startup->sem_chk_freq=2; /* seconds */ + if(startup->index_file_name[0]==0) SAFECOPY(startup->index_file_name,"00index"); + if(startup->html_index_file[0]==0) SAFECOPY(startup->html_index_file,"00index.html"); + if(startup->html_index_script[0]==0) { SAFECOPY(startup->html_index_script,"ftp-html.js"); + startup->options|=FTP_OPT_HTML_INDEX_FILE; + } + if(startup->options&FTP_OPT_HTML_INDEX_FILE) + startup->options&=~FTP_OPT_NO_JAVASCRIPT; + else + startup->options|=FTP_OPT_NO_JAVASCRIPT; +#ifdef JAVASCRIPT + if(startup->js_max_bytes==0) startup->js_max_bytes=JAVASCRIPT_MAX_BYTES; + if(startup->js_cx_stack==0) startup->js_cx_stack=JAVASCRIPT_CONTEXT_STACK; + + sprintf(js_server_props.version,"%s %s",FTP_SERVER,revision); + js_server_props.version_detail=ftp_ver(); + js_server_props.clients=&active_clients; + js_server_props.options=&startup->options; + js_server_props.interface_addr=&startup->interface_addr; +#endif - status("Initializing"); -#ifdef __unix__ /* Ignore "Broken Pipe" signal */ - signal(SIGPIPE,SIG_IGN); -#endif + uptime=0; + served=0; + startup->recycle_now=FALSE; + startup->shutdown_now=FALSE; + terminate_server=FALSE; + + do { - lprintf("Synchronet FTP Server Version %s%s" - ,FTP_VERSION + thread_up(FALSE /* setuid */); + + status("Initializing"); + + memset(&scfg, 0, sizeof(scfg)); + + lprintf(LOG_INFO,"Synchronet FTP Server Revision %s%s" + ,revision #ifdef _DEBUG - ," Debug" + ," Debug" #else - ,"" + ,"" #endif - ); + ); - COMPILER_DESC(compiler); + DESCRIBE_COMPILER(compiler); - lprintf("Compiled %s %s with %s", __DATE__, __TIME__, compiler); + lprintf(LOG_INFO,"Compiled %s %s with %s", __DATE__, __TIME__, compiler); - srand(time(NULL)); + srand(time(NULL)); /* Seed random number generator */ + sbbs_random(10); /* Throw away first number */ - if(!(startup->options&FTP_OPT_LOCAL_TIMEZONE)) { - if(PUTENV("TZ=UCT0")) - lprintf("!putenv() FAILED"); - tzset(); + if(!winsock_startup()) { + cleanup(1,__LINE__); + return; + } - if((t=checktime())!=0) { /* Check binary time */ - lprintf("!TIME PROBLEM (%ld)",t); - cleanup(1); + t=time(NULL); + lprintf(LOG_INFO,"Initializing on %.24s with options: %lx" + ,CTIME_R(&t,str),startup->options); + + /* Initial configuration and load from CNF files */ + SAFECOPY(scfg.ctrl_dir, startup->ctrl_dir); + lprintf(LOG_INFO,"Loading configuration files from %s", scfg.ctrl_dir); + scfg.size=sizeof(scfg); + SAFECOPY(error,UNKNOWN_LOAD_ERROR); + if(!load_cfg(&scfg, text, TRUE, error)) { + lprintf(LOG_ERR,"!ERROR %s",error); + lprintf(LOG_ERR,"!Failed to load configuration files"); + cleanup(1,__LINE__); return; } - } - if(!winsock_startup()) { - cleanup(1); - return; - } + if(startup->host_name[0]==0) + SAFECOPY(startup->host_name,scfg.sys_inetaddr); - t=time(NULL); - lprintf("Initializing on %.24s with options: %lx" - ,ctime(&t),startup->options); + if(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&FTP_OPT_LOCAL_TIMEZONE)) { + if(putenv("TZ=UTC0")) + lprintf(LOG_ERR,"!putenv() FAILED"); + tzset(); -#ifdef _WIN32 - if((socket_mutex=CreateMutex(NULL,FALSE,NULL))==NULL) { - lprintf("!ERROR %d creating socket_mutex", GetLastError()); - cleanup(1); - return; - } -#endif + if((t=checktime())!=0) { /* Check binary time */ + lprintf(LOG_ERR,"!TIME PROBLEM (%ld)",t); + cleanup(1,__LINE__); + return; + } + } - /* Initial configuration and load from CNF files */ - memset(&scfg, 0, sizeof(scfg)); - sprintf(scfg.ctrl_dir, "%.*s",(int)sizeof(scfg.ctrl_dir)-1 - ,startup->ctrl_dir); - lprintf("Loading configuration files from %s", scfg.ctrl_dir); - if(!load_cfg(&scfg, NULL)) { - lprintf("!Failed to load configuration files"); - cleanup(1); - return; - } + if(uptime==0) + uptime=time(NULL); /* this must be done *after* setting the timezone */ - if(!startup->max_clients) { - startup->max_clients=scfg.sys_nodes; - if(startup->max_clients<10) - startup->max_clients=10; - } - lprintf("Maximum clients: %d",startup->max_clients); + if(startup->temp_dir[0]) { + SAFECOPY(scfg.temp_dir,startup->temp_dir); + backslash(scfg.temp_dir); + } else + prep_dir(scfg.data_dir, scfg.temp_dir, sizeof(scfg.temp_dir)); - if(!startup->max_inactivity) - startup->max_inactivity=300; /* seconds */ + if(!startup->max_clients) { + startup->max_clients=scfg.sys_nodes; + if(startup->max_clients<10) + startup->max_clients=10; + } + lprintf(LOG_DEBUG,"Maximum clients: %d",startup->max_clients); - lprintf("Maximum inactivity: %d seconds",startup->max_inactivity); + lprintf(LOG_DEBUG,"Maximum inactivity: %d seconds",startup->max_inactivity); - active_clients=0; - update_clients(); + active_clients=0, update_clients(); - strlwr(scfg.sys_id); /* Use lower-case unix-looking System ID for group name */ + strlwr(scfg.sys_id); /* Use lower-case unix-looking System ID for group name */ - for(i=0;isname); - dotname(scfg.lib[i]->sname,scfg.lib[i]->sname); - } + for(i=0;isname); + dotname(scfg.lib[i]->sname,scfg.lib[i]->sname); + } +#if 0 /* this is now handled by load_cfg()->prep_cfg() */ + for(i=0;icode_suffix); +#endif + /* open a socket and wait for a client */ - for(i=0;icode); + if((server_socket=ftp_open_socket(SOCK_STREAM))==INVALID_SOCKET) { + lprintf(LOG_ERR,"!ERROR %d opening socket", ERROR_VALUE); + cleanup(1,__LINE__); + return; + } + lprintf(LOG_DEBUG,"%04d FTP Server socket opened",server_socket); - /* open a socket and wait for a client */ + /*****************************/ + /* Listen for incoming calls */ + /*****************************/ + memset(&server_addr, 0, sizeof(server_addr)); - if((server_socket=open_socket(SOCK_STREAM))==INVALID_SOCKET) { - lprintf("!ERROR %d opening socket", ERROR_VALUE); - cleanup(1); - return; - } + server_addr.sin_addr.s_addr = htonl(startup->interface_addr); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(startup->port); - lprintf("%04d FTP socket opened",server_socket); + if(startup->seteuid!=NULL) + startup->seteuid(FALSE); + result=retry_bind(server_socket, (struct sockaddr *) &server_addr,sizeof(server_addr) + ,startup->bind_retry_count,startup->bind_retry_delay,"FTP Server",lprintf); + if(startup->seteuid!=NULL) + startup->seteuid(TRUE); + if(result!=0) { + lprintf(LOG_ERR,"%04d %s", server_socket, BIND_FAILURE_HELP); + cleanup(1,__LINE__); + return; + } -#if 1 - linger.l_onoff=TRUE; - linger.l_linger=5; /* seconds */ - - if((result=setsockopt(server_socket, SOL_SOCKET, SO_LINGER - ,(char *)&linger, sizeof(linger)))!=0) { - lprintf ("%04d !ERROR %d (%d) setting socket options." - ,server_socket, result, ERROR_VALUE); - cleanup(1); - return; - } -#endif + if((result=listen(server_socket, 1))!= 0) { + lprintf(LOG_ERR,"%04d !ERROR %d (%d) listening on socket" + ,server_socket, result, ERROR_VALUE); + cleanup(1,__LINE__); + return; + } - /*****************************/ - /* Listen for incoming calls */ - /*****************************/ - memset(&server_addr, 0, sizeof(server_addr)); - - server_addr.sin_addr.s_addr = htonl(startup->interface_addr); - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(startup->port); - - if((result=bind(server_socket, (struct sockaddr *) &server_addr - ,sizeof(server_addr)))!=0) { - lprintf("%04d !ERROR %d (%d) binding socket to port %d" - ,server_socket, result, ERROR_VALUE,startup->port); - cleanup(1); - return; - } + lprintf(LOG_NOTICE,"%04d FTP Server thread started on port %d",server_socket,startup->port); + status(STATUS_WFC); - if((result=listen(server_socket, 1))!= 0) { - lprintf("%04d !ERROR %d (%d) listening on socket" - ,server_socket, result, ERROR_VALUE); - cleanup(1); - return; - } + /* Setup recycle/shutdown semaphore file lists */ + semfile_list_init(&shutdown_semfiles,scfg.ctrl_dir,"shutdown","ftp"); + semfile_list_init(&recycle_semfiles,scfg.ctrl_dir,"recycle","ftp"); + SAFEPRINTF(path,"%sftpsrvr.rec",scfg.ctrl_dir); /* legacy */ + semfile_list_add(&recycle_semfiles,path); + if(!initialized) { + initialized=time(NULL); + semfile_list_check(&initialized,&recycle_semfiles); + semfile_list_check(&initialized,&shutdown_semfiles); + } - /* signal caller that we've started up successfully */ - if(startup->started!=NULL) - startup->started(); - lprintf("%04d FTP Server thread started on port %d",server_socket,startup->port); - status(STATUS_WFC); + /* signal caller that we've started up successfully */ + if(startup->started!=NULL) + startup->started(startup->cbdata); + + while(server_socket!=INVALID_SOCKET && !terminate_server) { + + if(active_clients==0) { + if(!(startup->options&FTP_OPT_NO_RECYCLE)) { + if((p=semfile_list_check(&initialized,&recycle_semfiles))!=NULL) { + lprintf(LOG_INFO,"0000 Recycle semaphore file (%s) detected",p); + break; + } +#if 0 /* unused */ + if(startup->recycle_sem!=NULL && sem_trywait(&startup->recycle_sem)==0) + startup->recycle_now=TRUE; +#endif + if(startup->recycle_now==TRUE) { + lprintf(LOG_NOTICE,"0000 Recycle semaphore signaled"); + startup->recycle_now=FALSE; + break; + } + } + if(((p=semfile_list_check(&initialized,&shutdown_semfiles))!=NULL + && lprintf(LOG_INFO,"0000 Shutdown semaphore file (%s) detected",p)) + || (startup->shutdown_now==TRUE + && lprintf(LOG_INFO,"0000 Shutdown semaphore signaled"))) { + startup->shutdown_now=FALSE; + terminate_server=TRUE; + break; + } + } + /* now wait for connection */ - while(server_socket!=INVALID_SOCKET) { + tv.tv_sec=startup->sem_chk_freq; + tv.tv_usec=0; - /* now wait for connection */ + FD_ZERO(&socket_set); + FD_SET(server_socket,&socket_set); - tv.tv_sec=2; - tv.tv_usec=0; + if((i=select(server_socket+1,&socket_set,NULL,NULL,&tv))<1) { + if(i==0) + continue; + if(ERROR_VALUE==EINTR) + lprintf(LOG_NOTICE,"%04d FTP Server listening interrupted", server_socket); + else if(ERROR_VALUE == ENOTSOCK) + lprintf(LOG_NOTICE,"%04d FTP Server sockets closed", server_socket); + else + lprintf(LOG_WARNING,"%04d !ERROR %d selecting sockets",server_socket, ERROR_VALUE); + continue; + } - FD_ZERO(&socket_set); - FD_SET(server_socket,&socket_set); - high_socket_set=server_socket+1; + if(server_socket==INVALID_SOCKET || terminate_server) /* terminated */ + break; - if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) { - if(i==0) { - mswait(1); + client_addr_len = sizeof(client_addr); + client_socket = accept(server_socket, (struct sockaddr *)&client_addr + ,&client_addr_len); + + if(client_socket == INVALID_SOCKET) + { +#if 0 /* is this necessary still? */ + if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR || ERROR_VALUE == EINVAL) { + lprintf(LOG_NOTICE,"0000 FTP socket closed while listening"); + break; + } +#endif + lprintf(LOG_WARNING,"%04d !ERROR %d accepting connection" + ,server_socket, ERROR_VALUE); +#ifdef _WIN32 + if(WSAGetLastError()==WSAENOBUFS) /* recycle (re-init WinSock) on this error */ + break; +#endif continue; } - if(ERROR_VALUE==EINTR) - lprintf("0000 FTP Server listening interrupted"); - else if(ERROR_VALUE == ENOTSOCK) - lprintf("0000 FTP Server sockets closed"); - else - lprintf("0000 !ERROR %d selecting sockets",ERROR_VALUE); - break; - } + if(startup->socket_open!=NULL) + startup->socket_open(startup->cbdata,TRUE); + sockets++; - client_addr_len = sizeof(client_addr); - client_socket = accept(server_socket, (struct sockaddr *)&client_addr - ,&client_addr_len); + if(trashcan(&scfg,inet_ntoa(client_addr.sin_addr),"ip-silent")) { + ftp_close_socket(&client_socket,__LINE__); + continue; + } - if(client_socket == INVALID_SOCKET) - { - if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR) - lprintf("0000 FTP socket closed while listening"); - else - lprintf("0000 !accept failed (ERROR %d)", ERROR_VALUE); - break; - } - if(startup->socket_open!=NULL) - startup->socket_open(TRUE); - sockets++; + if(active_clients>=startup->max_clients) { + lprintf(LOG_WARNING,"%04d !MAXMIMUM CLIENTS (%d) reached, access denied" + ,client_socket, startup->max_clients); + sockprintf(client_socket,"421 Maximum active clients reached, please try again later."); + mswait(3000); + ftp_close_socket(&client_socket,__LINE__); + continue; + } - if(active_clients>=startup->max_clients) { - lprintf("%04d !MAXMIMUM CLIENTS (%d) reached, access denied" - ,client_socket, startup->max_clients); - sockprintf(client_socket,"421 Maximum active clients reached, please try again later."); - mswait(3000); - close_socket(&client_socket,__LINE__); - continue; + if((ftp=malloc(sizeof(ftp_t)))==NULL) { + lprintf(LOG_CRIT,"%04d !ERROR allocating %d bytes of memory for ftp_t" + ,client_socket,sizeof(ftp_t)); + sockprintf(client_socket,"421 System error, please try again later."); + mswait(3000); + ftp_close_socket(&client_socket,__LINE__); + continue; + } + + ftp->socket=client_socket; + ftp->client_addr=client_addr; + + _beginthread (ctrl_thread, 0, ftp); + served++; } - if((ftp=malloc(sizeof(ftp_t)))==NULL) { - lprintf("%04d !ERROR allocating %d bytes of memory for ftp_t" - ,client_socket,sizeof(ftp_t)); - sockprintf(client_socket,"421 System error, please try again later."); - mswait(3000); - close_socket(&client_socket,__LINE__); - continue; +#ifdef _DEBUG + lprintf(LOG_DEBUG,"0000 server_socket: %d",server_socket); + lprintf(LOG_DEBUG,"0000 terminate_server: %d",terminate_server); +#endif + if(active_clients) { + lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..." + ,server_socket, active_clients); + start=time(NULL); + while(active_clients) { + if(time(NULL)-start>startup->max_inactivity) { + lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for %d active clients" + ,server_socket, active_clients); + break; + } + mswait(100); + } } - ftp->socket=client_socket; - ftp->client_addr=client_addr; + if(thread_count>1) { + lprintf(LOG_DEBUG,"%04d Waiting for %d threads to terminate..." + ,server_socket, thread_count-1); + start=time(NULL); + while(thread_count>1) { + if(time(NULL)-start>TIMEOUT_THREAD_WAIT) { + lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for %d threads" + ,server_socket, thread_count-1); + break; + } + mswait(100); + } + } - _beginthread (ctrl_thread, 0, ftp); - } + cleanup(0,__LINE__); - if(active_clients) { - lprintf("0000 Waiting for %d active clients to disconnect...", active_clients); - start=time(NULL); - while(active_clients) { - if(time(NULL)-start>TIMEOUT_THREAD_WAIT) { - lprintf("0000 !TIMEOUT waiting for %d active clients ",active_clients); - break; - } - mswait(100); + if(!terminate_server) { + lprintf(LOG_INFO,"Recycling server..."); + mswait(2000); + if(startup->recycle!=NULL) + startup->recycle(startup->cbdata); } - } - cleanup(0); -} \ No newline at end of file + } while(!terminate_server); +}