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

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

rswindell
committed
jsuint index;
if((file=JS_NewObject(js_cx, NULL, NULL, NULL))==NULL)
if((js_str=JS_NewStringCopyZ(js_cx, name))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, file, "name", &val))
return(FALSE);
if((js_str=JS_NewStringCopyZ(js_cx, desc))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, file, "description", &val))
return(FALSE);
if((js_str=JS_NewStringCopyZ(js_cx, ext_desc))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, file, "extended_description", &val))
return(FALSE);
val=INT_TO_JSVAL(size);
if(!JS_SetProperty(js_cx, file, "size", &val))
return(FALSE);
val=INT_TO_JSVAL(credits);
if(!JS_SetProperty(js_cx, file, "credits", &val))
return(FALSE);
val=DOUBLE_TO_JSVAL((double)time);
if(!JS_SetProperty(js_cx, file, "time", &val))
return(FALSE);
val=INT_TO_JSVAL((int32)uploaded);
if(!JS_SetProperty(js_cx, file, "uploaded", &val))
return(FALSE);
val=INT_TO_JSVAL((int32)last_downloaded);
if(!JS_SetProperty(js_cx, file, "last_downloaded", &val))
return(FALSE);
val=INT_TO_JSVAL(times_downloaded);
if(!JS_SetProperty(js_cx, file, "times_downloaded", &val))
return(FALSE);
if((js_str=JS_NewStringCopyZ(js_cx, uploader))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, file, "uploader", &val))
return(FALSE);
val=INT_TO_JSVAL(misc);
if(!JS_SetProperty(js_cx, file, "settings", &val))
return(FALSE);
if((js_str=JS_NewStringCopyZ(js_cx, link))==NULL)
return(FALSE);
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, file, "link", &val))
return(FALSE);
if(!JS_GetArrayLength(js_cx, array, &index))
return(FALSE);
val=OBJECT_TO_JSVAL(file);
return(JS_SetElement(js_cx, array, index, &val));
}
BOOL js_generate_index(JSContext* js_cx, JSObject* parent,
SOCKET sock, FILE* fp, int lib, int dir, user_t* user, client_t* client)
{
char str[256];
char path[MAX_PATH+1];
char spath[MAX_PATH+1]; /* script path */
char vpath[MAX_PATH+1]; /* virtual path */
char aliasfile[MAX_PATH+1];
char extdesc[513];
BOOL alias_dir;
BOOL success=FALSE;
FILE* alias_fp;
uint i;
file_t f;
glob_t g;
jsval val;
jsval rval;
JSObject* lib_obj=NULL;
JSObject* dir_obj=NULL;
JSObject* file_array=NULL;
JSObject* dir_array=NULL;
JSObject* js_script=NULL;
long double start=xp_timer();
jsrefcount rc;
lprintf(LOG_DEBUG,"%04d JavaScript: Generating HTML Index for %s"
,sock, genvpath(lib,dir,str));
JS_SetContextPrivate(js_cx, fp);
do { /* pseudo try/catch */
if((file_array=JS_NewArrayObject(js_cx, 0, NULL))==NULL) {
lprintf(LOG_ERR,"%04d !JavaScript FAILED to create file_array",sock);
/* 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);
/* 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;
}
rc=JS_SUSPENDREQUEST(js_cx);
if(strcspn(startup->html_index_script,"/\\")==strlen(startup->html_index_script)) {
sprintf(spath,"%s%s",scfg.mods_dir,startup->html_index_script);
if(scfg.mods_dir[0]==0 || !fexist(spath))
sprintf(spath,"%s%s",scfg.exec_dir,startup->html_index_script);
} else
sprintf(spath,"%.*s",(int)sizeof(spath)-4,startup->html_index_script);
/* Add extension if not specified */
if(!strchr(spath,'.'))
strcat(spath,".js");
if(!fexist(spath)) {
JS_RESUMEREQUEST(js_cx, rc);
lprintf(LOG_ERR,"%04d !HTML JavaScript (%s) doesn't exist",sock,spath);
JS_RESUMEREQUEST(js_cx, rc);
if((js_str=JS_NewStringCopyZ(js_cx, startup->html_index_file))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, parent, "html_index_file", &val)) {
lprintf(LOG_ERR,"%04d !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);
/* curdir */
if((dir_obj=JS_NewObject(js_cx, NULL, 0, NULL))==NULL) {
lprintf(LOG_ERR,"%04d !JavaScript FAILED to create dir_obj",sock);
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);
if(lib>=0) { /* root */
strcat(vpath,scfg.lib[lib]->sname);
if((js_str=JS_NewStringCopyZ(js_cx, scfg.lib[lib]->sname))==NULL)
break;
val=STRING_TO_JSVAL(js_str);
if(!JS_SetProperty(js_cx, lib_obj, "name", &val)) {
lprintf(LOG_ERR,"%04d !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 */
rc=JS_SUSPENDREQUEST(js_cx);
/* File Aliases */
sprintf(path,"%sftpalias.cfg",scfg.ctrl_dir);
if((alias_fp=fopen(path,"r"))!=NULL) {
while(!feof(alias_fp)) {
if(!fgets(aliasline,sizeof(aliasline),alias_fp))
break;
p=aliasline; /* alias pointer */
SKIP_WHITESPACE(p);
if(*p==';') /* comment */
continue;
tp=p; /* terminator pointer */
FIND_WHITESPACE(tp);
if(*tp) *tp=0;
np=tp+1; /* filename pointer */
SKIP_WHITESPACE(np);
tp=np; /* terminator pointer */
FIND_WHITESPACE(tp);
if(*tp) *tp=0;
dp=tp+1; /* description pointer */
SKIP_WHITESPACE(dp);
truncsp(dp);
if(stricmp(dp,BBS_HIDDEN_ALIAS)==0)
continue;
alias_dir=FALSE;
/* Virtual Path? */
if(!strnicmp(np,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
if((dir=getdir(np+strlen(BBS_VIRTUAL_PATH),user,client))<0)
continue; /* No access or invalid virtual path */
tp=strrchr(np,'/');
if(tp==NULL)
continue;
tp++;
if(*tp) {
SAFEPRINTF2(aliasfile,"%s%s",scfg.dir[dir]->path,tp);
np=aliasfile;
}
else
alias_dir=TRUE;
}
if(!alias_dir && !fexist(np))
continue;
SAFEPRINTF2(vpath,"/%s/%s",p,startup->html_index_file);
JS_RESUMEREQUEST(js_cx, rc);
,alias_dir ? dir_array : file_array
,dp /* description */
,NULL /* extdesc */
,flength(np) /* size */
,fdate(np) /* time */
,fdate(np) /* uploaded */
,0 /* last downloaded */
,0 /* times downloaded */
,0 /* misc */
,vpath /* link */
rc=JS_SUSPENDREQUEST(js_cx);
JS_RESUMEREQUEST(js_cx, rc);
/* QWK Packet */
if(startup->options&FTP_OPT_ALLOW_QWK /* && fexist(qwkfile) */) {
sprintf(str,"%s.qwk",scfg.sys_id);
SAFEPRINTF(vpath,"/%s",str);
js_add_file(js_cx
,file_array
,str /* filename */
,"QWK Message Packet" /* description */
,NULL /* extdesc */
,time(NULL) /* time */
,0 /* uploaded */
,0 /* last downloaded */
,0 /* times downloaded */
,0 /* misc */
,str /* link */
);
}
/* Library Folders */
for(i=0;i<scfg.total_libs;i++) {
SAFEPRINTF2(vpath,"/%s/%s",scfg.lib[i]->sname,startup->html_index_file);
js_add_file(js_cx
,dir_array
,scfg.lib[i]->sname /* filename */
,scfg.lib[i]->lname /* description */
,NULL /* extdesc */
,0,0,0,0,0,0,0 /* unused */
,vpath /* link */
for(i=0;i<scfg.total_dirs;i++) {
if(scfg.dir[i]->lib!=lib)
continue;
if(/* i!=scfg.sysop_dir && i!=scfg.upload_dir && */
SAFEPRINTF3(vpath,"/%s/%s/%s"
,scfg.dir[i]->code_suffix
,startup->html_index_file);
js_add_file(js_cx
,dir_array
,scfg.dir[i]->sname /* filename */
,NULL /* extdesc */
,getfiles(&scfg,i) /* size */
,0,0,0,0,0 /* unused */
,scfg.dir[i]->misc /* misc */
,vpath /* link */
SAFEPRINTF(path,"%s*",scfg.dir[dir]->path);
rc=JS_SUSPENDREQUEST(js_cx);
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 */
SAFEPRINTF3(vpath,"/%s/%s/%s"
,scfg.dir[dir]->code_suffix
JS_RESUMEREQUEST(js_cx, rc);
js_add_file(js_cx
,file_array
,getfname(g.gl_pathv[i]) /* filename */
,f.desc /* description */
,f.misc&FM_EXTDESC ? extdesc : NULL
,f.size /* size */
,f.cdt /* credits */
,f.date /* time */
,f.dateuled /* uploaded */
,f.datedled /* last downloaded */
,f.timesdled /* times downloaded */
,f.misc /* misc */
,getfname(g.gl_pathv[i]) /* link */
rc=JS_SUSPENDREQUEST(js_cx);
JS_RESUMEREQUEST(js_cx, rc);
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);
js_PrepareToExecute(js_cx, parent, spath, /* startup_dir: */NULL, parent);
if((success=JS_ExecuteScript(js_cx, parent, js_script, &rval))!=TRUE) {
lprintf(LOG_ERR,"%04d !JavaScript FAILED to execute script (%s)",sock,spath);
lprintf(LOG_DEBUG,"%04d JavaScript: Done executing script: %s (%.2Lf seconds)"
,sock,spath,xp_timer()-start);
JS_DeleteProperty(js_cx, parent, "path");
JS_DeleteProperty(js_cx, parent, "sort");
JS_DeleteProperty(js_cx, parent, "reverse");
JS_DeleteProperty(js_cx, parent, "file_list");
JS_DeleteProperty(js_cx, parent, "dir_list");
JS_DeleteProperty(js_cx, parent, "curlib");
JS_DeleteProperty(js_cx, parent, "curdir");
JS_DeleteProperty(js_cx, parent, "html_index_file");
#endif /* ifdef JAVASCRIPT */
BOOL upload_stats(ulong bytes)
{
char str[MAX_PATH+1];

rswindell
committed
sprintf(str,"%sdsts.dab",scfg.ctrl_dir);
if((file=nopen(str,O_RDWR))==-1)
return(FALSE);
lseek(file,20L,SEEK_SET); /* Skip timestamp, logons and logons today */
read(file,&val,4); /* Uploads today */
val++;
lseek(file,-4L,SEEK_CUR);
write(file,&val,4);
read(file,&val,4); /* Upload bytes today */
val+=bytes;
lseek(file,-4L,SEEK_CUR);
write(file,&val,4);
close(file);
return(TRUE);
}
BOOL download_stats(ulong bytes)
{
char str[MAX_PATH+1];

rswindell
committed
sprintf(str,"%sdsts.dab",scfg.ctrl_dir);
if((file=nopen(str,O_RDWR))==-1)
return(FALSE);
lseek(file,28L,SEEK_SET); /* Skip timestamp, logons and logons today */
read(file,&val,4); /* Downloads today */
val++;
lseek(file,-4L,SEEK_CUR);
write(file,&val,4);
read(file,&val,4); /* Download bytes today */
val+=bytes;
lseek(file,-4L,SEEK_CUR);
write(file,&val,4);
close(file);
return(TRUE);
}
void recverror(SOCKET socket, int rd, int line)
lprintf(LOG_NOTICE,"%04d Socket closed by peer on receive (line %u)"
lprintf(LOG_NOTICE,"%04d Connection reset by peer on receive (line %u)"
lprintf(LOG_NOTICE,"%04d Connection aborted by peer on receive (line %u)"
lprintf(LOG_NOTICE,"%04d !ERROR %d receiving on socket (line %u)"
lprintf(LOG_WARNING,"%04d !ERROR: recv on socket returned unexpected value: %d (line %u)"
static int sock_recvbyte(SOCKET sock, CRYPT_SESSION sess, char *buf, time_t *lastactive)

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

rswindell
committed

rswindell
committed

rswindell
committed

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

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

rswindell
committed
if(xfer.filepos || startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d 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) {
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
,(ulong)((total-last_total)/(now-last_report))
,str);
last_total=total;
if(startup->max_fsize && (xfer.filepos+total) > startup->max_fsize) {
lprintf(LOG_WARNING,"%04d !DATA received %lu bytes of %s exceeds maximum allowed (%lu bytes)"
,xfer.ctrl_sock, xfer.filepos+total, xfer.filename, startup->max_fsize);
sockprintf(xfer.ctrl_sock,sess,"552 File size exceeds maximum allowed (%lu bytes)", startup->max_fsize);
error=TRUE;
break;
}
lprintf(LOG_WARNING,"%04d !DATA Transfer aborted",xfer.ctrl_sock);

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

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

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

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

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

rswindell
committed
/* Send ACK */
sockprintf(xfer.ctrl_sock,sess,"226 Upload complete (%lu cps).",cps);
*xfer.inprogress=FALSE;
static BOOL start_tls(SOCKET *sock, CRYPT_SESSION *sess, BOOL resp)
{
BOOL nodelay;
ulong nb;
int status;
if (get_ssl_cert(&scfg, &estr, &level) == -1) {
if (estr) {
lprintf(level, "%04d FTP %s", estr);
free_crypt_attrstr(estr);
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
if ((status = cryptCreateSession(sess, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
GCES(status, *sock, CRYPT_UNUSED, estr, "creating session");
if (estr) {
lprintf(level, "%04d FTP %s", *sock, estr);
free(estr);
}
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "disabling certificate verification");
if(resp)
sockprintf(*sock, *sess, "431 TLS not available");
if ((status=cryptSetAttribute(*sess, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting private key");
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return FALSE;
}
nodelay = TRUE;
setsockopt(*sock,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
nb=0;
ioctlsocket(*sock,FIONBIO,&nb);
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_NETWORKSOCKET, *sock)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting network socket");
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return TRUE;
}
if (resp)
sockprintf(*sock, -1, "234 Ready to start TLS");
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting session active");
if ((status = cryptSetAttribute(*sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting read timeout");
static void filexfer(union xp_sockaddr* addr, SOCKET ctrl_sock, CRYPT_SESSION ctrl_sess, SOCKET pasv_sock, CRYPT_SESSION pasv_sess, SOCKET* data_sock
,CRYPT_SESSION *data_sess, char* filename, long filepos, BOOL* inprogress, BOOL* aborted
,BOOL delfile, BOOL tmpfile
,time_t* lastactive
,user_t* user
,int dir
,BOOL receiving
,BOOL credits
,BOOL append
ulong l;
xfer_t* xfer;
struct timeval tv;
fd_set socket_set;
if((*inprogress)==TRUE) {
lprintf(LOG_WARNING,"%04d !TRANSFER already in progress",ctrl_sock);
sockprintf(ctrl_sock,ctrl_sess,"425 Transfer already in progress.");
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
return;
}
*inprogress=TRUE;
if(pasv_sock==INVALID_SOCKET) { /* !PASV */
if((*data_sock=socket(addr->addr.sa_family, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
lprintf(LOG_ERR,"%04d !DATA ERROR %d opening socket", ctrl_sock, ERROR_VALUE);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d opening socket",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
*inprogress=FALSE;
return;
}
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d 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));
addr_len = sizeof(server_addr);
if((result=getsockname(ctrl_sock, &server_addr.addr,&addr_len))!=0) {
lprintf(LOG_ERR,"%04d !ERROR %d (%d) getting address/port of command socket (%u)"
,ctrl_sock,result,ERROR_VALUE,pasv_sock);
return;
}
inet_setaddrport(&server_addr, inet_addrport(&server_addr)-1); /* 20? */
if(result!=0) {
inet_setaddrport(&server_addr, 0); /* any user port */
result=bind(*data_sock, &server_addr.addr,addr_len);
}
lprintf(LOG_ERR,"%04d !DATA ERROR %d (%d) binding socket %d"
,ctrl_sock, result, ERROR_VALUE, *data_sock);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d binding socket",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
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
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d connecting to socket",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
return;
}
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d DATA socket %d connected to %s port %u"
if (protected) {
if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
lprintf(LOG_DEBUG,"%04d !DATA ERROR activating TLS"
,ctrl_sock,*data_sock,host_ip,inet_addrport(addr));
sockprintf(ctrl_sock,ctrl_sess,"425 Error activating TLS");
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
*inprogress=FALSE;
ftp_close_socket(data_sock,data_sess,__LINE__);
return;
}
}
if(startup->options&FTP_OPT_DEBUG_DATA) {
lprintf(LOG_ERR,"%04d !ERROR %d (%d) getting address/port of passive socket (%u)"
,ctrl_sock,result,ERROR_VALUE,pasv_sock);
else
lprintf(LOG_DEBUG,"%04d PASV DATA socket %d listening on %s port %u"
/* Setup for select() */
tv.tv_sec=TIMEOUT_SOCKET_LISTEN;
tv.tv_usec=0;
FD_ZERO(&socket_set);
FD_SET(pasv_sock,&socket_set);
#if defined(SOCKET_DEBUG_SELECT)
socket_debug[ctrl_sock]|=SOCKET_DEBUG_SELECT;
result=select(pasv_sock+1,&socket_set,NULL,NULL,&tv);
#if defined(SOCKET_DEBUG_SELECT)
socket_debug[ctrl_sock]&=~SOCKET_DEBUG_SELECT;
if(result<1) {
lprintf(LOG_WARNING,"%04d !PASV select returned %d (error: %d)",ctrl_sock,result,ERROR_VALUE);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d selecting socket for connection",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
*inprogress=FALSE;
return;
}
socket_debug[ctrl_sock]|=SOCKET_DEBUG_ACCEPT;
socket_debug[ctrl_sock]&=~SOCKET_DEBUG_ACCEPT;
if(*data_sock==INVALID_SOCKET) {
lprintf(LOG_WARNING,"%04d !PASV DATA ERROR %d accepting connection on socket %d"
,ctrl_sock,ERROR_VALUE,pasv_sock);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d accepting connection",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
*inprogress=FALSE;
return;
}
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d PASV DATA socket %d connected to %s port %u"
if (protected) {
if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
lprintf(LOG_WARNING,"%04d !PASV ERROR starting TLS", pasv_sock);
sockprintf(ctrl_sock,ctrl_sess,"425 Error negotiating TLS", ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
*inprogress=FALSE;
return;
}
}
do {
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,ctrl_sess,"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__);
break;
}
memset(xfer,0,sizeof(xfer_t));
xfer->ctrl_sock=ctrl_sock;
xfer->data_sock=data_sock;
xfer->credits=credits;
xfer->lastactive=lastactive;
xfer->user=user;
xfer->dir=dir;
xfer->desc=desc;
SAFECOPY(xfer->filename,filename);
protected_uint32_adjust(&thread_count,1);
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 && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename);
*inprogress=FALSE;
/* convert "user name" to "user.name" or "mr. user" to "mr._user" */
char* dotname(char* in, char* out)
{
char ch;
int i;
if(strchr(in,'.')==NULL)
ch='.';
else
ch='_';
for(i=0;in[i];i++)
if(in[i]<=' ')
else
out[i]=in[i];
out[i]=0;
return(out);
}
static BOOL can_list(lib_t *lib, dir_t *dir, user_t *user, client_t *client)
if (!chk_ar(&scfg,lib->ar,user,client))
return FALSE;
if (dir->dirnum == scfg.sysop_dir)
return TRUE;
if (dir->dirnum == scfg.upload_dir)
return TRUE;
if (chk_ar(&scfg, dir->ar, user, client))
return TRUE;
return FALSE;
}
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
static BOOL ftpalias(char* fullalias, char* filename, user_t* user, client_t* client, int* curdir)
{
char* p;
char* tp;
char* fname="";
char line[512];
char alias[512];
char aliasfile[MAX_PATH+1];
int dir=-1;
FILE* fp;
BOOL result=FALSE;
sprintf(aliasfile,"%sftpalias.cfg",scfg.ctrl_dir);
if((fp=fopen(aliasfile,"r"))==NULL)
return(FALSE);
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),fp))
break;
p=line; /* alias */
SKIP_WHITESPACE(p);
if(*p==';') /* comment */
continue;
tp=p; /* terminator */
FIND_WHITESPACE(tp);
if(*tp) *tp=0;
if(stricmp(p,alias)) /* Not a match */
continue;
p=tp+1; /* filename */
SKIP_WHITESPACE(p);
tp=p; /* terminator */
FIND_WHITESPACE(tp);
if(*tp) *tp=0;
if(!strnicmp(p,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
if((dir=getdir(p+strlen(BBS_VIRTUAL_PATH),user,client))<0) {
lprintf(LOG_WARNING,"0000 !Invalid virtual path (%s) for %s",p,user->alias);
/* invalid or no access */
continue;
}
p=strrchr(p,'/');
if(p!=NULL) p++;
if(p!=NULL && filename!=NULL) {
if(*p)
sprintf(filename,"%s%s",scfg.dir[dir]->path,p);
else
sprintf(filename,"%s%s",scfg.dir[dir]->path,fname);
}
} else if(filename!=NULL)
strcpy(filename,p);
result=TRUE; /* success */
break;
}
fclose(fp);
if(curdir!=NULL)
*curdir=dir;
return(result);
}
/*
* Parses a path into *curlib, *curdir, and sets *pp to point to the filename
*/
static int parsepath(char** pp, user_t* user, client_t* client, int* curlib, int* curdir)
{
char filename[MAX_PATH+1];
int lib = *curlib;
int dir = *curdir;
char *p = *pp;
char *tmp;
char *fname = strchr(p, 0);
int ret = 0;
size_t len;
if (*p == '/') {
lib = -1;
dir = -1;
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
while (*p) {
/* Relative path stuff */
if (strcmp(p, "..") == 0) {
if (dir >= 0)
dir = -1;
else if (lib >= 0)
lib = -1;
else
ret = -1;
p += 2;
}
else if(strncmp(p, "../", 3) == 0) {
if (dir >= 0)
dir = -1;
else if (lib >= 0)
lib = -1;
else
ret = -1;
p += 3;
}
else if(strcmp(p, ".") == 0)
else if(strncmp(p, "./", 2) == 0)
p += 2;
/* Path component */
else if (lib < 0) {
for(lib=0;lib<scfg.total_libs;lib++) {
if(!chk_ar(&scfg,scfg.lib[lib]->ar,user,client))
continue;
len = strlen(scfg.lib[lib]->sname);
if (strlen(p) < len)
continue;
if (p[len] != 0 && p[len] != '/')
continue;
if(!strnicmp(scfg.lib[lib]->sname,p,len)) {
p += len;
if (*p)
p++;
break;
}
}
if (lib == scfg.total_libs) {
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
strcpy(filename, p);
tmp = strchr(filename, '/');
if (tmp != NULL)
*tmp = 0;
if (ftpalias(filename, filename, user, client, &dir) == TRUE) {
lib = scfg.dir[dir]->lib;
if (strchr(p, '/') != NULL) {
p = strchr(p, '/');
p++;
}
else
p = strchr(p, 0);
}
else {
ret = -1;
lib = -1;
if (strchr(p, '/') != NULL) {
p = strchr(p, '/');
p++;
}
else
p = strchr(p, 0);
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
else if (dir < 0) {
for(dir=0;dir<scfg.total_dirs;dir++) {
if(scfg.dir[dir]->lib!=lib)
continue;
if (!can_list(scfg.lib[lib], scfg.dir[dir], user, client))
continue;
len = strlen(scfg.dir[dir]->code_suffix);
if (strlen(p) < len)
continue;
if (p[len] != 0 && p[len] != '/')
continue;
if(!strnicmp(scfg.dir[dir]->code_suffix,p,len)) {
p += len;
if (*p)
p++;
break;
}
}
if (dir == scfg.total_dirs) {
ret = -1;
dir = -1;
if (strchr(p, '/') != NULL) {
p = strchr(p, '/');
p++;
}
else
p = strchr(p, 0);
else { // Filename
if (strchr(p, '/') != NULL) {
ret = -1;
p = strchr(p, '/');
p++;
}
else {
fname = p;
p += strlen(fname);
}
*curdir = dir;
*curlib = lib;
*pp = fname;
return ret;
}
char* root_dir(char* path)
{
char* p;
static char root[MAX_PATH+1];
if(!strncmp(root,"\\\\",2)) { /* network path */
p=strchr(root+2,'\\');
if(p) p=strchr(p+1,'\\');
if(p) *(p+1)=0; /* truncate at \\computer\sharename\ */
}
else if(!strncmp(root+1,":/",2) || !strncmp(root+1,":\\",2))
root[3]=0;
else if(*root=='/' || *root=='\\')
root[1]=0;
return(root);
}
char* genvpath(int lib, int dir, char* str)
{
strcpy(str,"/");
if(lib<0)
return(str);
strcat(str,scfg.lib[lib]->sname);
if(dir<0)
return(str);
strcat(str,scfg.dir[dir]->code_suffix);
return(str);
}
void ftp_printfile(SOCKET sock, CRYPT_SESSION sess, const char* name, unsigned code)
{
char path[MAX_PATH+1];
char buf[512];
FILE* fp;
unsigned i;
SAFEPRINTF2(path,"%sftp%s.txt",scfg.text_dir,name);
if((fp=fopen(path,"rb"))!=NULL) {
i=0;
while(!feof(fp)) {
if(!fgets(buf,sizeof(buf),fp))
break;
truncsp(buf);
if(!i)
else
i++;
}
fclose(fp);
}
}
static BOOL ftp_hacklog(char* prot, char* user, char* text, char* host, union xp_sockaddr* addr)
{
#ifdef _WIN32
if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE))
PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif
return hacklog(&scfg, prot, user, text, host, addr);
}
/****************************************************************************/
/* Consecutive failed login (possible password hack) attempt tracking */
/****************************************************************************/
static BOOL badlogin(SOCKET sock, CRYPT_SESSION sess, ulong* login_attempts, char* user, char* passwd, char* host, union xp_sockaddr* addr)
{
ulong count;
if(addr!=NULL) {
count=loginFailure(startup->login_attempt_list, addr, "FTP", user, passwd);
if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold)
ftp_hacklog("FTP LOGIN", user, passwd, host, addr);
if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold) {
filter_ip(&scfg, "FTP", "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
if(count > *login_attempts)
*login_attempts=count;
} else
(*login_attempts)++;
mswait(startup->login_attempt.delay); /* As recommended by RFC2577 */
return(TRUE);
}
ftp_printfile(sock,sess,"badlogin",530);
sockprintf(sock,sess,"530 Invalid login.");
return(FALSE);
}
static char* ftp_tmpfname(char* fname, char* ext, SOCKET sock)
safe_snprintf(fname,MAX_PATH,"%sSBBS_FTP.%x%x%x%lx.%s"
,scfg.temp_dir,getpid(),sock,rand(),clock(),ext);
return(fname);
static BOOL send_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, const char *format, ...)
{
va_list va;
char *str;
if (fp == NULL && sock == INVALID_SOCKET)
return FALSE;
va_start(va, format);
if (vasprintf(&str, format, va) == -1)
return FALSE;
if (fp != NULL)
fprintf(fp, "%s\r\n", str);
else
sockprintf(sock, sess, " %s", str);
free(str);
return TRUE;
}
static char *get_unique(const char *path, char *uniq)
{
BYTE digest[MD5_DIGEST_SIZE];
if (path == NULL)
return NULL;
MD5_calc(digest, path, strlen(path));
MD5_hex((BYTE*)uniq, digest);
return uniq;
}
static BOOL send_mlsx_entry(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned feats, const char *type, const char *perm, uint64_t size, time_t modify, const char *owner, const char *unique, time_t ul, const char *fname)
{
char line[1024];
char *end;
BOOL need_owner = FALSE;
end=line;
*end=0;
if (type != NULL && (feats & MLSX_TYPE))
end += sprintf(end, "Type=%s;", type);
if (perm != NULL && (feats & MLSX_PERM))
end += sprintf(end, "Perm=%s;", perm);
if (size != UINT64_MAX && (feats & MLSX_SIZE))
end += sprintf(end, "Size=%" PRIu64 ";", size);
end += sprintf(end, "Modify=%04d%02d%02d%02d%02d%02d;",
t.tm_year+1900, t.tm_mon+1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
if (unique != NULL && (feats & MLSX_UNIQUE))
end += sprintf(end, "Unique=%s;", unique);
if (ul != 0 && (feats & MLSX_CREATE)) {
t = *gmtime(&modify);
end += sprintf(end, "Create=%04d%02d%02d%02d%02d%02d;",
t.tm_year+1900, t.tm_mon+1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
}
// Owner can contain percents, so let send_mlsx() deal with it
if (owner != NULL && (feats & MLSX_OWNER)) {
strcat(end, "UNIX.ownername=%s;");
need_owner = TRUE;
}
strcat(end, " %s");
if (need_owner)
return send_mlsx(fp, sock, sess, line, owner, fname==NULL ? "" : fname);
return send_mlsx(fp, sock, sess, line, fname==NULL ? "" : fname);
}
static BOOL write_local_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned feats, const char *path, BOOL full_path)
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
{
const char *type;
char permstr[11];
char *p;
BOOL is_file = FALSE;
if (!strcmp(path, "."))
type="cdir";
else if (!strcmp(path, ".."))
type="pdir";
else if (isdir(path))
type="dir";
else {
is_file = TRUE;
type="file";
}
// TODO: Check for deletability 'd'
// TODO: Check for renamability 'f'
p = permstr;
if (is_file) {
if (access(path, W_OK) == 0) {
// Can append ('a') and write ('w')
*(p++)='a';
*(p++)='w';
}
if (access(path, R_OK) == 0) {
// Can read ('r')
*(p++)='r';
}
}
else {
// TODO: Check these on Windows...
if (access(path, W_OK) == 0) {
// Can create files ('c'), directories ('m') and delete files ('p')
*(p++)='c';
*(p++)='m';
*(p++)='p';
}
if (access(path, R_OK) == 0) {
// Can change to the directory ('e'), and list files ('l')
*(p++)='e';
*(p++)='l';
}
}
*p=0;
if (is_file)
full_path = FALSE;
return send_mlsx_entry(fp, sock, sess, feats, type, permstr, (uint64_t)flength(path), fdate(path), NULL, NULL, 0, full_path ? path : getfname(path));
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
}
/*
* Nobody can do anything but list files and change to dirs.
*/
static void get_libperm(lib_t *lib, user_t *user, client_t *client, char *permstr)
{
char *p = permstr;
if (chk_ar(&scfg,lib->ar,user,client)) {
//*(p++) = 'a'; // File may be appended to
//*(p++) = 'c'; // Files may be created in dir
//*(p++) = 'd'; // Item may be depeted (dir or file)
*(p++) = 'e'; // Can change to the dir
//*(p++) = 'f'; // Item may be renamed
*(p++) = 'l'; // Directory contents can be listed
//*(p++) = 'm'; // New subdirectories may be created
//*(p++) = 'p'; // Files/Dirs in directory may be deleted
//*(p++) = 'r'; // File may be retrieved
//*(p++) = 'w'; // File may be overwritten
}
*p=0;
}
static BOOL can_upload(lib_t *lib, dir_t *dir, user_t *user, client_t *client)
{
if (!chk_ar(&scfg,lib->ar,user,client))
return FALSE;
if (user->rest & FLAG('U'))
return FALSE;
if (dir_op(&scfg, user, client, dir->dirnum))
return TRUE;
// The rest can only upload if there's room
if(dir->maxfiles && getfiles(&scfg,dir->dirnum)>=dir->maxfiles)
return FALSE;
if (dir->dirnum == scfg.sysop_dir)
return TRUE;
if (dir->dirnum == scfg.upload_dir)
return TRUE;
if (chk_ar(&scfg, dir->ul_ar,user,client))
return TRUE;
if ((user->exempt & FLAG('U')))
return TRUE;
return FALSE;
}
static BOOL can_delete_files(lib_t *lib, dir_t *dir, user_t *user, client_t *client)
{
if (!chk_ar(&scfg,lib->ar,user,client))
return FALSE;
if (user->rest&FLAG('D'))
return FALSE;
if (!chk_ar(&scfg,dir->ar,user,client))
return FALSE;
if (dir_op(&scfg,user,client,dir->dirnum))
return TRUE;
if (user->exempt&FLAG('R'))
return TRUE;
return FALSE;
}
static void get_dirperm(lib_t *lib, dir_t *dir, user_t *user, client_t *client, char *permstr)
{
char *p = permstr;
//*(p++) = 'a'; // File may be appended to
if (can_upload(lib, dir, user, client))
*(p++) = 'c'; // Files may be created in dir
//*(p++) = 'd'; // Item may be depeted (dir or file)
if (can_list(lib, dir, user, client)) {
*(p++) = 'e'; // Can change to the dir
//*(p++) = 'f'; // Item may be renamed
*(p++) = 'l'; // Directory contents can be listed
}
//*(p++) = 'm'; // New subdirectories may be created
if (can_delete_files(lib, dir, user, client))
*(p++) = 'p'; // Files/Dirs in directory may be deleted
//*(p++) = 'r'; // File may be retrieved
//*(p++) = 'w'; // File may be overwritten
*p=0;
}
static BOOL can_append(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
if (!chk_ar(&scfg,lib->ar,user,client))
return FALSE;
if (user->rest&FLAG('U'))
return FALSE;
if (dir->dirnum != scfg.sysop_dir && dir->dirnum != scfg.upload_dir && !chk_ar(&scfg,dir->ar,user,client))
return FALSE;
if(!dir_op(&scfg,user,client,dir->dirnum) && !(user->exempt&FLAG('U'))) {
if(!chk_ar(&scfg,dir->ul_ar,user,client))
return FALSE;
}
if(!getfileixb(&scfg,file) || !getfiledat(&scfg,file))
return FALSE;
if (stricmp(file->uler,user->alias))
return FALSE;
// Check credits?
return TRUE;
}
static BOOL can_delete(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
if (user->rest&FLAG('D'))
return FALSE;
if (!chk_ar(&scfg,lib->ar,user,client))
return FALSE;
if (!chk_ar(&scfg,dir->ar,user,client))
return FALSE;
if (!dir_op(&scfg, user, client, dir->dirnum))
return FALSE;
if (!(user->exempt&FLAG('R')))
return FALSE;
if(!getfileixb(&scfg,file) && !(startup->options&FTP_OPT_DIR_FILES) && !(dir->misc&DIR_FILES))
return FALSE;
return TRUE;
}
static BOOL can_download(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
if (user->rest&FLAG('D'))
return FALSE;
if (!chk_ar(&scfg,lib->ar,user,client))
return FALSE;
if (!chk_ar(&scfg,dir->ar,user,client))
return FALSE;
if (!chk_ar(&scfg,dir->dl_ar,user,client))
return FALSE;
if(!getfileixb(&scfg,file) && !(startup->options&FTP_OPT_DIR_FILES) && !(dir->misc&DIR_FILES))
return FALSE;
// TODO: Verify credits
return TRUE;
}
static void get_fileperm(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file, char *permstr)
{
char *p = permstr;
if (can_append(lib, dir, user, client, file))
*(p++) = 'a'; // File may be appended to
//*(p++) = 'c'; // Files may be created in dir
if (can_delete(lib, dir, user, client, file))
*(p++) = 'd'; // Item may be depeted (dir or file)
//*(p++) = 'e'; // Can change to the dir
//*(p++) = 'f'; // Item may be renamed
//*(p++) = 'l'; // Directory contents can be listed
//*(p++) = 'm'; // New subdirectories may be created
//*(p++) = 'p'; // Files/Dirs in directory may be deleted
if (can_download(lib, dir, user, client, file))
*(p++) = 'r'; // File may be retrieved
//*(p++) = 'w'; // File may be overwritten
*p = 0;
}
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
static void get_owner_name(file_t *file, char *namestr)
{
char *p;
if (file) {
if (file->misc & FM_ANON)
strcpy(namestr, ANONYMOUS);
else
strcpy(namestr, file->uler);
}
else
strcpy(namestr, scfg.sys_id);
// Now ensure it's an RCHAR string.
for (p=namestr; *p; p++) {
if (*p >= '!' && *p <= ')')
continue;
else if (*p >= '+' && *p <= ':')
continue;
else if (*p >= '?' && *p <= 'Z')
continue;
else if (*p == '\\')
continue;
else if (*p == '^')
continue;
else if (*p == '_')
continue;
else if (*p >= 'a' && *p <= 'z')
continue;
else if (*p == ' ')
*p = '.';
else
*p = '_';
}
}
static void ctrl_thread(void* arg)
{
unsigned mlsx_feats = (MLSX_TYPE | MLSX_PERM | MLSX_SIZE | MLSX_MODIFY | MLSX_OWNER | MLSX_UNIQUE | MLSX_CREATE);
char buf[512];
char str[128];
char uniq[33];
char* cmd;
char* p;
char* np;
char* tp;
char* dp;
char* ap;
char* filespec;
char* mode="active";
char old_char;
char fname[MAX_PATH+1];
char qwkfile[MAX_PATH+1];
char aliasfile[MAX_PATH+1];
char aliaspath[MAX_PATH+1];
char mls_path[MAX_PATH+1];
char *mls_fname;
char permstr[11];
char aliasline[512];
char desc[501]="";
char sys_pass[128];
char host_name[256];
char host_ip[INET6_ADDRSTRLEN];
char data_ip[INET6_ADDRSTRLEN];
uint16_t data_port;
char path[MAX_PATH+1];
char local_dir[MAX_PATH+1];
char ren_from[MAX_PATH+1]="";
char html_index_ext[MAX_PATH+1];

sbbs
committed
unsigned h1,h2,h3,h4;
u_short p1,p2; /* For PORT command */
int i;
int rd;
int result;
int lib;
int dir;
int curlib=-1;
int curdir=-1;
int orglib;
int orgdir;
long filepos=0L;
long timeleft;
ulong l;
ulong login_attempts=0;
ulong avail; /* disk space */
ulong count;
BOOL detail;
BOOL success;
BOOL getdate;
BOOL getsize;

rswindell
committed
BOOL delecmd;
BOOL delfile;
BOOL tmpfile;
BOOL credits;
BOOL filedat=FALSE;
BOOL transfer_inprogress;
BOOL transfer_aborted;
BOOL sysop=FALSE;
BOOL local_fsys=FALSE;
BOOL alias_dir;
BOOL reuseaddr;
FILE* fp;
FILE* alias_fp;
SOCKET sock;
SOCKET tmp_sock;
SOCKET pasv_sock=INVALID_SOCKET;
SOCKET data_sock=INVALID_SOCKET;
union xp_sockaddr addr;
union xp_sockaddr data_addr;
union xp_sockaddr pasv_addr;
ftp_t ftp=*(ftp_t*)arg;
user_t user;
time_t t;
time_t now;
time_t logintime=0;
time_t file_date;

rswindell
committed
glob_t g;
node_t node;
client_t client;
struct tm tm;
struct tm cur_tm;

rswindell
committed
JSRuntime* js_runtime=NULL;
JSObject* js_ftp;
js_callback_t js_callback;
CRYPT_SESSION sess = -1;
BOOL got_pbsz = FALSE;
BOOL protection = FALSE;
SetThreadName("sbbs/ftpControl");
thread_up(TRUE /* setuid */);
lastactive=time(NULL);
sock=ftp.socket;
/* Default data port is ctrl port-1 */
lprintf(LOG_DEBUG,"%04d CTRL thread started", sock);
free(arg);
if(startup->answer_sound[0] && !(startup->options&FTP_OPT_MUTE))
PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);

rswindell
committed
transfer_inprogress = FALSE;
transfer_aborted = FALSE;
l=1;
if((i=ioctlsocket(sock, FIONBIO, &l))!=0) {
lprintf(LOG_ERR,"%04d !ERROR %d (%d) disabling socket blocking"
sockprintf(sock,sess,"425 Error %d disabling socket blocking"
thread_down();
return;
}
memset(&user,0,sizeof(user));
lprintf(LOG_INFO,"%04d CTRL connection accepted from: %s port %u"
if(startup->options&FTP_OPT_NO_HOST_LOOKUP)
strcpy(host_name,"<no name>");
else {
if(getnameinfo(&ftp.client_addr.addr, sizeof(ftp.client_addr), host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD)!=0)
strcpy(host_name,"<no name>");
}
if(!(startup->options&FTP_OPT_NO_HOST_LOOKUP))
lprintf(LOG_INFO,"%04d Hostname: %s", sock, host_name);
ulong banned = loginBanned(&scfg, startup->login_attempt_list, sock, host_name, startup->login_attempt, &attempted);
if(banned || trashcan(&scfg,host_ip,"ip")) {
if(banned) {
char ban_duration[128];
lprintf(LOG_NOTICE, "%04d !TEMPORARY BAN of %s (%u login attempts, last: %s) - remaining: %s"
,sock, host_ip, attempted.count-attempted.dupes, attempted.user, seconds_to_str(banned, ban_duration));
} else
lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", sock, host_ip);
sockprintf(sock,sess,"550 Access denied.");
ftp_close_socket(&sock,&sess,__LINE__);
thread_down();
return;
}
if(trashcan(&scfg,host_name,"host")) {
lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", sock, host_name);
sockprintf(sock,sess,"550 Access denied.");
ftp_close_socket(&sock,&sess,__LINE__);
thread_down();
return;
}
/* For PASV mode */
addr_len=sizeof(pasv_addr);
lprintf(LOG_ERR,"%04d !ERROR %d (%d) getting address/port", sock, result, ERROR_VALUE);
sockprintf(sock,sess,"425 Error %d getting address/port",ERROR_VALUE);
ftp_close_socket(&sock,&sess,__LINE__);
thread_down();
return;
}
protected_uint32_adjust(&active_clients, 1);
update_clients();
/* Initialize client display */
client.size=sizeof(client);
client.time=time32(NULL);
SAFECOPY(client.addr,host_ip);
SAFECOPY(client.host,host_name);
client.user=STR_UNKNOWN_USER;
client_on(sock,&client,FALSE /* update */);
&& (login_attempts=loginAttempts(startup->login_attempt_list, &ftp.client_addr)) > 1) {
lprintf(LOG_DEBUG,"%04d Throttling suspicious connection from: %s (%u login attempts)"
mswait(login_attempts*startup->login_attempt.throttle);
}
sockprintf(sock,sess,"220-%s (%s)",scfg.sys_name, startup->host_name);
sockprintf(sock,sess," Synchronet FTP Server %s-%s Ready"
,revision,PLATFORM_DESC);

rswindell
committed
sprintf(str,"%sftplogin.txt",scfg.text_dir);
if((fp=fopen(str,"rb"))!=NULL) {
while(!feof(fp)) {
if(!fgets(buf,sizeof(buf),fp))
break;
truncsp(buf);
socket_debug[sock]|=SOCKET_DEBUG_CTRL;
socket_debug[sock]|=SOCKET_DEBUG_READLINE;
rd = sockreadline(sock, sess, buf, sizeof(buf), &lastactive);
socket_debug[sock]&=~SOCKET_DEBUG_READLINE;
if(transfer_inprogress==TRUE) {
lprintf(LOG_WARNING,"%04d Aborting transfer due to receive error",sock);
}
truncsp(buf);
lastactive=time(NULL);
cmd=buf;
while(((BYTE)*cmd)==TELNET_IAC) {
cmd++;
lprintf(LOG_DEBUG,"%04d RX%s: Telnet cmd: %s",sock,sess == -1 ? "" : "S", telnet_cmd_desc(*cmd));
cmd++;
}
while(*cmd && *cmd<' ') {
lprintf(LOG_DEBUG,"%04d RX%s: %d (0x%02X)",sock,sess == -1 ? "" : "S", (BYTE)*cmd,(BYTE)*cmd);
cmd++;
}
if(!(*cmd))
continue;
if(startup->options&FTP_OPT_DEBUG_RX)
lprintf(LOG_DEBUG,"%04d RX%s: %s", sock, sess == -1 ? "" : "S", cmd);
continue;
}
if(!stricmp(cmd, "HELP SITE") || !stricmp(cmd, "SITE HELP")) {
sockprintf(sock,sess,"214-The following SITE commands are recognized (* => unimplemented):");
sockprintf(sock,sess," HELP VER WHO UPTIME");
if(user.level>=SYSOP_LEVEL)
" RECYCLE [ALL]");
sockprintf(sock,sess,"214 Direct comments to sysop@%s.",scfg.sys_inetaddr);
continue;
}
if(!strnicmp(cmd, "HELP",4)) {
sockprintf(sock,sess,"214-The following commands are recognized (* => unimplemented, # => extension):");
sockprintf(sock,sess," USER PASS CWD XCWD CDUP XCUP PWD XPWD");
sockprintf(sock,sess," QUIT REIN PORT PASV LIST NLST NOOP HELP");
sockprintf(sock,sess," SIZE MDTM RETR STOR REST ALLO ABOR SYST");
sockprintf(sock,sess," TYPE STRU MODE SITE RNFR* RNTO* DELE* DESC#");
sockprintf(sock,sess," FEAT# OPTS# EPRT EPSV AUTH# PBSZ# PROT# CCC#");
sockprintf(sock,sess," MLSD#");
sockprintf(sock,sess,"214 Direct comments to sysop@%s.",scfg.sys_inetaddr);
continue;
}
if(!stricmp(cmd, "FEAT")) {
sockprintf(sock,sess,"211-The following additional (post-RFC949) features are supported:");
sockprintf(sock,sess," DESC");
sockprintf(sock,sess," MDTM");
sockprintf(sock,sess," SIZE");
sockprintf(sock,sess," REST STREAM");
sockprintf(sock,sess," AUTH TLS");
sockprintf(sock,sess," PBSZ");
sockprintf(sock,sess," PROT");
sockprintf(sock,sess," MLST Type%s;Perm%s;Size%s;Modify%s;UNIX.ownername%s;",
(mlsx_feats & MLSX_TYPE) ? "*" : "",
(mlsx_feats & MLSX_PERM) ? "*" : "",
(mlsx_feats & MLSX_SIZE) ? "*" : "",
(mlsx_feats & MLSX_MODIFY) ? "*" : "",
(mlsx_feats & MLSX_OWNER) ? "*" : "",
(mlsx_feats & MLSX_UNIQUE) ? "*" : "",
(mlsx_feats & MLSX_CREATE) ? "*" : ""
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
if(!strnicmp(cmd, "OPTS MLST",9)) {
if (cmd[9] == 0) {
mlsx_feats = 0;
continue;
}
if (cmd[9] != ' ') {
sockprintf(sock,sess,"501 Option not supported.");
continue;
}
mlsx_feats = 0;
for (p = cmd; *p; p++)
*p = toupper(*p);
if (strstr(cmd, "TYPE;"))
mlsx_feats |= MLSX_TYPE;
if (strstr(cmd, "PERM;"))
mlsx_feats |= MLSX_PERM;
if (strstr(cmd, "SIZE;"))
mlsx_feats |= MLSX_SIZE;
if (strstr(cmd, "MODIFY;"))
mlsx_feats |= MLSX_MODIFY;
if (strstr(cmd, "UNIX.OWNERNAME;"))
mlsx_feats |= MLSX_OWNER;
if (strstr(cmd, "UNIQUE;"))
mlsx_feats |= MLSX_UNIQUE;
if (strstr(cmd, "CREATE;"))
mlsx_feats |= MLSX_CREATE;
sockprintf(sock,sess,"200 %s%s%s%s%s",
(mlsx_feats & MLSX_TYPE) ? "Type;" : "",
(mlsx_feats & MLSX_PERM) ? "Perm;" : "",
(mlsx_feats & MLSX_SIZE) ? "Size;" : "",
(mlsx_feats & MLSX_MODIFY) ? "Modify;" : "",
(mlsx_feats & MLSX_OWNER) ? "UNIX.ownername;" : "",
(mlsx_feats & MLSX_UNIQUE) ? "Unique;" : "",
(mlsx_feats & MLSX_CREATE) ? "Create;" : ""
if(!strnicmp(cmd, "OPTS",4)) {
sockprintf(sock,sess,"501 Option not supported.");
continue;
}
if(!stricmp(cmd, "QUIT")) {
ftp_printfile(sock,sess,"bye",221);
sockprintf(sock,sess,"221 Goodbye. Closing control connection.");
break;
}
if(!strnicmp(cmd, "USER ",5)) {
sysop=FALSE;
user.number=0;
p=cmd+5;
SKIP_WHITESPACE(p);
user.number=matchuser(&scfg,user.alias,FALSE /*sysop_alias*/);
if(!user.number && (stricmp(user.alias,"anonymous") == 0 || stricmp(user.alias, "ftp") == 0))
user.number=matchuser(&scfg,"guest",FALSE);
if(user.number && getuserdat(&scfg, &user)==0 && user.pass[0]==0)
sockprintf(sock,sess,"331 User name okay, give your full e-mail address as password.");
continue;
}
if(!strnicmp(cmd, "PASS ",5) && user.alias[0]) {
user.number=0;
p=cmd+5;
SKIP_WHITESPACE(p);
user.number=matchuser(&scfg,user.alias,FALSE /*sysop_alias*/);
if(scfg.sys_misc&SM_ECHO_PW)

rswindell
committed
lprintf(LOG_WARNING,"%04d !UNKNOWN USER: '%s' (password: %s)",sock,user.alias,p);

rswindell
committed
lprintf(LOG_WARNING,"%04d !UNKNOWN USER: '%s'",sock,user.alias);
if(badlogin(sock, sess, &login_attempts, user.alias, p, host_name, &ftp.client_addr))
continue;
}
if((i=getuserdat(&scfg, &user))!=0) {
lprintf(LOG_ERR,"%04d !ERROR %d getting data for user #%d (%s)"
,sock,i,user.number,user.alias);
user.number=0;
continue;
}
if(user.misc&(DELETED|INACTIVE)) {
lprintf(LOG_WARNING,"%04d !DELETED or INACTIVE user #%d (%s)"
,sock,user.number,user.alias);
user.number=0;
if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
continue;
}
if(user.rest&FLAG('T')) {
lprintf(LOG_WARNING,"%04d !T RESTRICTED user #%d (%s)"
,sock,user.number,user.alias);
user.number=0;
if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
if(user.ltoday>=scfg.level_callsperday[user.level]
&& !(user.exempt&FLAG('L'))) {
lprintf(LOG_WARNING,"%04d !MAXIMUM LOGONS (%d) reached for %s"
,sock,scfg.level_callsperday[user.level],user.alias);
user.number=0;
continue;
}
if(user.rest&FLAG('L') && user.ltoday>=1) {
lprintf(LOG_WARNING,"%04d !L RESTRICTED user #%d (%s) already on today"
,sock,user.number,user.alias);
user.number=0;
continue;
}
SAFEPRINTF2(sys_pass,"%s:%s",user.pass,scfg.sys_pass);
if(!user.pass[0]) { /* Guest/Anonymous */
if(trashcan(&scfg,password,"email")) {
lprintf(LOG_NOTICE,"%04d !BLOCKED e-mail address: %s",sock,password);
user.number=0;
if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
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(LOG_INFO,"%04d Sysop access granted to %s", sock, user.alias);
sysop=TRUE;
}
else if(stricmp(password,user.pass)) {

rswindell
committed
if(scfg.sys_misc&SM_ECHO_PW)
lprintf(LOG_WARNING,"%04d !FAILED Password attempt for user %s: '%s' expected '%s'"

rswindell
committed
,sock, user.alias, password, user.pass);
else
lprintf(LOG_WARNING,"%04d !FAILED Password attempt for user %s"

rswindell
committed
,sock, user.alias);
if(badlogin(sock, sess, &login_attempts, user.alias, password, host_name, &ftp.client_addr))
continue;
}
/* Update client display */
if(user.pass[0]) {
client.user=user.alias;
loginSuccess(startup->login_attempt_list, &ftp.client_addr);
} else { /* anonymous */
sprintf(str,"%s <%.32s>",user.alias,password);
client.user=str;
}
client_on(sock,&client,TRUE /* update */);
lprintf(LOG_INFO,"%04d %s logged in (%u today, %u total)"
,sock,user.alias,user.ltoday+1, user.logons+1);
timeleft=(long)gettimeleft(&scfg,&user,logintime);
#ifdef JAVASCRIPT
#ifdef JS_CX_PER_SESSION
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, &client)==NULL)
lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user object",sock);
if(js_CreateClientObject(js_cx, js_glob, "client", &client, sock, -1)==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
sockprintf(sock,sess,"230-Sysop access granted.");
sockprintf(sock,sess,"230-%s logged in.",user.alias);
if(!(user.exempt&FLAG('D')) && (user.cdt+user.freecdt)>0)
,user.cdt+user.freecdt);
sockprintf(sock,sess,"230 You are allowed %lu minutes of use for this session."

rswindell
committed
sprintf(qwkfile,"%sfile/%04d.qwk",scfg.data_dir,user.number);
/* Adjust User Total Logons/Logons Today */
user.logons++;
user.ltoday++;
SAFECOPY(user.modem,"FTP");
SAFECOPY(user.comp,host_name);
user.logontime=(time32_t)logintime;
putuserdat(&scfg, &user);
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
if (!strnicmp(cmd, "AUTH ", 5)) {
if(!stricmp(cmd, "AUTH TLS")) {
if (sess != -1) {
sockprintf(sock,sess,"534 Already in TLS mode");
continue;
}
if (start_tls(&sock, &sess, TRUE))
break;
user.number=0;
sysop=FALSE;
filepos=0;
got_pbsz = FALSE;
protection = FALSE;
continue;
}
sockprintf(sock,sess,"504 TLS is the only AUTH supported");
continue;
}
if (!strnicmp(cmd, "PBSZ ", 5)) {
if(!stricmp(cmd, "PBSZ 0") && sess != -1) {
got_pbsz = TRUE;
sockprintf(sock,sess,"200 OK");
continue;
}
if (sess == -1) {
sockprintf(sock,sess,"503 Need AUTH TLS first");
continue;
}
if (strspn(cmd+5, "0123456789") == strlen(cmd+5)) {
sockprintf(sock,sess,"200 PBSZ=0");
continue;
}
sockprintf(sock,sess,"501 Unable to parse buffer size");
continue;
}
if (!strnicmp(cmd, "PROT ", 5)) {
if (sess == -1) {
sockprintf(sock,sess,"503 No AUTH yet");
continue;
}
if(!strnicmp(cmd, "PROT P",6) && sess != -1 && got_pbsz) {
protection = TRUE;
sockprintf(sock,sess,"200 Accepted");
continue;
}
if(!strnicmp(cmd, "PROT C",6) && sess != -1 && got_pbsz) {
protection = FALSE;
sockprintf(sock,sess,"200 Accepted");
continue;
}
sockprintf(sock,sess,"536 Only C and P are supported in TLS mode");
continue;
}
if(!stricmp(cmd, "CCC")) {
if (sess == -1) {
sockprintf(sock,sess,"533 Not in TLS mode");
continue;
}
sockprintf(sock,sess,"200 Accepted");
cryptDestroySession(sess);
sess = -1;
continue;
}
sockprintf(sock,sess,"530 Please login with USER and PASS.");
if(!(user.rest&FLAG('G')))
getuserdat(&scfg, &user); /* get current user data */
if((timeleft=(long)gettimeleft(&scfg,&user,logintime))<1L) {
lprintf(LOG_WARNING,"%04d Out of time, disconnecting",sock);
break;
}
/********************************/
/* These commands require login */
/********************************/
if(!stricmp(cmd, "REIN")) {
lprintf(LOG_INFO,"%04d %s reinitialized control session",sock,user.alias);
user.number=0;
sysop=FALSE;
filepos=0;
sockprintf(sock,sess,"220 Control session re-initialized. Ready for re-login.");
if (sess != -1) {
cryptDestroySession(sess);
sess = -1;
}
got_pbsz = FALSE;
protection = FALSE;
continue;
}
if(!stricmp(cmd, "SITE WHO")) {
for(i=0;i<scfg.sys_nodes && i<scfg.sys_lastnode;i++) {
if((result=getnodedat(&scfg, i+1, &node, 0))!=0) {
sockprintf(sock,sess," Error %d getting data for Telnet Node %d",result,i+1);
continue;
}
if(node.status==NODE_INUSE)
sockprintf(sock,sess," Node %3d: %s",i+1, username(&scfg,node.useron,str));
sockprintf(sock,sess,"211 End (%d active FTP clients)", protected_uint32_value(active_clients));
continue;
}
if(!stricmp(cmd, "SITE VER")) {
if(!stricmp(cmd, "SITE UPTIME")) {
sockprintf(sock,sess,"211 %s (%lu served)",sectostr((uint)(time(NULL)-uptime),str),served);
continue;
}
if(!stricmp(cmd, "SITE RECYCLE") && user.level>=SYSOP_LEVEL) {
startup->recycle_now=TRUE;
sockprintf(sock,sess,"211 server will recycle when not in-use");
continue;
}
if(!stricmp(cmd, "SITE RECYCLE ALL") && user.level>=SYSOP_LEVEL) {
refresh_cfg(&scfg);
sockprintf(sock,sess,"211 ALL servers/nodes will recycle when not in-use");
continue;
}
if(!strnicmp(cmd,"SITE EXEC ",10) && sysop) {
p=cmd+10;
SKIP_WHITESPACE(p);
#ifdef __unix__
fp=popen(p,"r");
if(fp==NULL)
sockprintf(sock,sess,"500 Error %d opening pipe to: %s",errno,p);
else {
while(!feof(fp)) {
if(fgets(str,sizeof(str),fp)==NULL)
break;
}
#else
sockprintf(sock,sess,"200 system(%s) returned %d",p,system(p));
if(!stricmp(cmd, "SITE DEBUG")) {
for(i=0;i<sizeof(socket_debug);i++)
if(socket_debug[i]!=0)
sockprintf(sock,sess,"211-socket %d = 0x%X",i,socket_debug[i]);
sockprintf(sock,sess,"211 End");
continue;
}
if(strnicmp(cmd, "PORT ",5)==0 || strnicmp(cmd, "EPRT ",5)==0 || strnicmp(cmd, "LPRT ",5)==0) {
if(pasv_sock!=INVALID_SOCKET) {
ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
}
SKIP_WHITESPACE(p);
if(strnicmp(cmd, "PORT ",5)==0) {
sscanf(p,"%u,%u,%u,%u,%hd,%hd",&h1,&h2,&h3,&h4,&p1,&p2);
data_addr.in.sin_family=AF_INET;
data_addr.in.sin_addr.s_addr=htonl((h1<<24)|(h2<<16)|(h3<<8)|h4);
char delim = *p;
int prot;
memset(&data_addr, 0, sizeof(data_addr));
if(*p)
p++;
prot=strtol(p,NULL,/* base: */10);
switch(prot) {
case 1:
FIND_CHAR(p,delim);
if(*p)
p++;
ap = p;
old_char = *p;
*p = 0;
data_addr.in.sin_addr.s_addr=inet_addr(ap);
*p = old_char;
if (*p)
p++;
data_port=atoi(p);
data_addr.in.sin_family=AF_INET;
break;
case 2:
FIND_CHAR(p,delim);
if(*p)
p++;
strncpy(addr_str, p, sizeof(addr_str));
addr_str[sizeof(addr_str)-1]=0;
tp=addr_str;
FIND_CHAR(tp, delim);
*tp=0;
if(inet_ptoaddr(addr_str, &data_addr, sizeof(data_addr))==NULL) {
lprintf(LOG_WARNING,"%04d Unable to parse IPv6 address %s",sock,addr_str);
sockprintf(sock,sess,"522 Unable to parse IPv6 address (1)");
continue;
}
FIND_CHAR(p,delim);
if(*p)
p++;
data_port=atoi(p);
data_addr.in6.sin6_family=AF_INET6;
break;
default:
lprintf(LOG_WARNING,"%04d UNSUPPORTED protocol: %d", sock, prot);
sockprintf(sock,sess,"522 Network protocol not supported, use (1)");
continue;
}
}
else { /* LPRT */
if(sscanf(p,"%u,%u",&h1, &h2)!=2) {
lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
FIND_CHAR(p,',');
if(*p)
p++;
FIND_CHAR(p,',');
if(*p)
p++;
switch(h1) {
case 4: /* IPv4 */
if(h2 != 4) {
lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
sockprintf(sock,sess, "501 IPv4 Address is the wrong length");
continue;
}
for(h1 = 0; h1 < h2; h1++) {
((unsigned char *)(&data_addr.in.sin_addr))[h1]=atoi(p);
FIND_CHAR(p,',');
if(*p)
p++;
}
if(atoi(p)!=2) {
lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
continue;
}
FIND_CHAR(p,',');
if(*p)
p++;
for(h1 = 0; h1 < 2; h1++) {
((unsigned char *)(&data_port))[1-h1]=atoi(p);
FIND_CHAR(p,',');
if(*p)
p++;
}
data_addr.in.sin_family=AF_INET;
break;
case 6: /* IPv6 */
if(h2 != 16) {
lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
sockprintf(sock,sess, "501 IPv6 Address is the wrong length");
continue;
}
for(h1 = 0; h1 < h2; h1++) {
((unsigned char *)(&data_addr.in6.sin6_addr))[h1]=atoi(p);
FIND_CHAR(p,',');
if(*p)
p++;
}
if(atoi(p)!=2) {
lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
continue;
}
FIND_CHAR(p,',');
if(*p)
p++;
for(h1 = 0; h1 < 2; h1++) {
((unsigned char *)(&data_port))[1-h1]=atoi(p);
FIND_CHAR(p,',');
if(*p)
p++;
}
data_addr.in6.sin6_family=AF_INET6;
break;
default:
lprintf(LOG_ERR, "Unable to parse LPRT %s", p);
inet_addrtop(&data_addr, data_ip, sizeof(data_ip));
if(data_port< IPPORT_RESERVED) {
lprintf(LOG_WARNING,"%04d !SUSPECTED BOUNCE ATTACK ATTEMPT by %s to %s port %u"
,sock,user.alias
ftp_hacklog("FTP BOUNCE", user.alias, cmd, host_name, &ftp.client_addr);
continue; /* As recommended by RFC2577 */
}
mode="active";
if(stricmp(cmd, "PASV")==0 || stricmp(cmd, "P@SW")==0 /* Kludge required for SMC Barricade V1.2 */
|| stricmp(cmd, "EPSV")==0 || strnicmp(cmd, "EPSV ", 5)==0 || stricmp(cmd, "LPSV")==0) {
if(pasv_sock!=INVALID_SOCKET)
ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
if((pasv_sock=ftp_open_socket(pasv_addr.addr.sa_family, SOCK_STREAM))==INVALID_SOCKET) {
lprintf(LOG_WARNING,"%04d !PASV ERROR %d opening socket", sock,ERROR_VALUE);
sockprintf(sock,sess,"425 Error %d opening PASV data socket", ERROR_VALUE);
reuseaddr=FALSE;
if((result=setsockopt(pasv_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr)))!=0) {
lprintf(LOG_WARNING,"%04d !PASV ERROR %d disabling REUSEADDR socket option"
,sock,ERROR_VALUE);
sockprintf(sock,sess,"425 Error %d disabling REUSEADDR socket option", ERROR_VALUE);
continue;
}
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d PASV DATA socket %d opened",sock,pasv_sock);
for(port=startup->pasv_port_low; port<=startup->pasv_port_high; port++) {
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d PASV DATA trying to bind socket to port %u"
,sock,port);
if((result=bind(pasv_sock, &pasv_addr.addr,xp_sockaddr_len(&pasv_addr)))==0)
break;
if(port==startup->pasv_port_high)
break;
lprintf(LOG_ERR,"%04d !PASV ERROR %d (%d) binding socket to port %u"
,sock, result, ERROR_VALUE, port);
sockprintf(sock,sess,"425 Error %d binding data socket",ERROR_VALUE);
ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d PASV DATA socket %d bound to port %u",sock,pasv_sock,port);
lprintf(LOG_ERR,"%04d !PASV ERROR %d (%d) getting address/port"
,sock, result, ERROR_VALUE);
sockprintf(sock,sess,"425 Error %d getting address/port",ERROR_VALUE);
ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
continue;
}
if((result=listen(pasv_sock, 1))!= 0) {
lprintf(LOG_ERR,"%04d !PASV ERROR %d (%d) listening on port %u"
,sock, result, ERROR_VALUE,port);
sockprintf(sock,sess,"425 Error %d listening on data socket",ERROR_VALUE);
ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
if(strnicmp(cmd, "EPSV", 4)==0)
sockprintf(sock,sess,"229 Entering Extended Passive Mode (|||%hu|)", port);
else if (stricmp(cmd,"LPSV")==0) {
switch(addr.addr.sa_family) {
case AF_INET:
sockprintf(sock,sess, "228 Entering Long Passive Mode (4, 4, %d, %d, %d, %d, 2, %d, %d)"
,((unsigned char *)&(addr.in.sin_addr))[0]
,((unsigned char *)&(addr.in.sin_addr))[1]
,((unsigned char *)&(addr.in.sin_addr))[2]
,((unsigned char *)&(addr.in.sin_addr))[3]
,((unsigned char *)&(addr.in.sin_port))[0]
,((unsigned char *)&(addr.in.sin_port))[1]);
break;
case AF_INET6:
sockprintf(sock,sess, "228 Entering Long Passive Mode (6, 16, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, 2, %d, %d)"
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
,((unsigned char *)&(addr.in6.sin6_addr))[0]
,((unsigned char *)&(addr.in6.sin6_addr))[1]
,((unsigned char *)&(addr.in6.sin6_addr))[2]
,((unsigned char *)&(addr.in6.sin6_addr))[3]
,((unsigned char *)&(addr.in6.sin6_addr))[4]
,((unsigned char *)&(addr.in6.sin6_addr))[5]
,((unsigned char *)&(addr.in6.sin6_addr))[6]
,((unsigned char *)&(addr.in6.sin6_addr))[7]
,((unsigned char *)&(addr.in6.sin6_addr))[8]
,((unsigned char *)&(addr.in6.sin6_addr))[9]
,((unsigned char *)&(addr.in6.sin6_addr))[10]
,((unsigned char *)&(addr.in6.sin6_addr))[11]
,((unsigned char *)&(addr.in6.sin6_addr))[12]
,((unsigned char *)&(addr.in6.sin6_addr))[13]
,((unsigned char *)&(addr.in6.sin6_addr))[14]
,((unsigned char *)&(addr.in6.sin6_addr))[15]
,((unsigned char *)&(addr.in6.sin6_port))[0]
,((unsigned char *)&(addr.in6.sin6_port))[1]);
break;
}
}
else {
/* Choose IP address to use in passive response */
ip_addr=0;
/* TODO: IPv6 this here lookup */
if(startup->options&FTP_OPT_LOOKUP_PASV_IP
&& (host=gethostbyname(startup->host_name))!=NULL)
ip_addr=ntohl(*((ulong*)host->h_addr_list[0]));
if(ip_addr==0 && (ip_addr=startup->pasv_ip_addr.s_addr)==0)
ip_addr=ntohl(pasv_addr.in.sin_addr.s_addr);
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_INFO,"%04d PASV DATA IP address in response: %u.%u.%u.%u (subject to NAT)"
,sock
,(ip_addr>>24)&0xff
,(ip_addr>>16)&0xff
,(ip_addr>>8)&0xff
,ip_addr&0xff
);
sockprintf(sock,sess,"227 Entering Passive Mode (%u,%u,%u,%u,%hu,%hu)"
,(ip_addr>>24)&0xff
,(ip_addr>>16)&0xff
,(ip_addr>>8)&0xff
,ip_addr&0xff
,(port>>8)&0xff
,port&0xff
);
mode="passive";
continue;
}
if(!strnicmp(cmd, "TYPE ",5)) {
continue;
}
if(!strnicmp(cmd, "ALLO",4)) {
p=cmd+5;
SKIP_WHITESPACE(p);
if(*p)
l=atol(p);
else
l=0;
if(local_fsys)
avail=getfreediskspace(local_dir,0);
avail=getfreediskspace(scfg.data_dir,0); /* Change to temp_dir? */
continue;
}
if(!strnicmp(cmd, "REST",4)) {
p=cmd+4;
SKIP_WHITESPACE(p);
if(*p)
filepos=atol(p);
else
filepos=0;
sockprintf(sock,sess,"350 Restarting at %lu. Send STORE or RETRIEVE to initiate transfer."
continue;
}
if(!strnicmp(cmd, "MODE ",5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
continue;
}
if(!strnicmp(cmd, "STRU ",5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
continue;
}
if(!stricmp(cmd, "SYST")) {
continue;
}
if(!stricmp(cmd, "ABOR")) {
if(!transfer_inprogress)
lprintf(LOG_WARNING,"%04d %s aborting transfer"
,sock,user.alias);
YIELD(); /* give send thread time to abort */
}
continue;
}
if(!strnicmp(cmd,"SMNT ",5) && sysop && !(startup->options&FTP_OPT_NO_LOCAL_FSYS)) {
p=cmd+5;
SKIP_WHITESPACE(p);
if(!stricmp(p,BBS_FSYS_DIR))
local_fsys=FALSE;
else {
if(!direxist(p)) {
lprintf(LOG_WARNING,"%04d !%s attempted to mount invalid directory: %s"
,sock, user.alias, p);
continue;
}
local_fsys=TRUE;
,local_fsys ? "Local" : "BBS");
lprintf(LOG_INFO,"%04d %s mounted %s file system"
,sock, user.alias, local_fsys ? "local" : "BBS");
continue;
}
/****************************/
/* Local File System Access */
/****************************/
if(sysop && local_fsys && !(startup->options&FTP_OPT_NO_LOCAL_FSYS)) {
if(local_dir[0]
&& local_dir[strlen(local_dir)-1]!='\\'
&& local_dir[strlen(local_dir)-1]!='/')
strcat(local_dir,"/");
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
if(!strnicmp(cmd, "MLS", 3)) {
if (cmd[3] == 'T' || cmd[3] == 'D') {
if (cmd[3] == 'D') {
if((fp=fopen(ftp_tmpfname(fname,"lst",sock),"w+b"))==NULL) {
lprintf(LOG_ERR,"%04d !ERROR %d opening %s",sock,errno,fname);
sockprintf(sock,sess, "451 Insufficient system storage");
continue;
}
}
p=cmd+4;
SKIP_WHITESPACE(p);
filespec=p;
if (!local_dir[0])
strcpy(local_dir, "/");
SAFEPRINTF2(path,"%s%s",local_dir, filespec);
p=FULLPATH(NULL, path, 0);
strcpy(path, p);
free(p);
if (cmd[3] == 'T') {
if (access(path, 0) == -1) {
sockprintf(sock,sess, "550 No such path %s", path);
continue;
}
sockprintf(sock,sess, "250- Listing %s", path);
}
else {
if (access(path, 0) == -1) {
sockprintf(sock,sess, "550 No such path %s", path);
continue;
}
if (!isdir(path)) {
sockprintf(sock,sess, "501 Not a directory");
continue;
}
sockprintf(sock,sess, "150 Directory of %s", path);
backslash(path);
strcat(path, "*");
}
lprintf(LOG_INFO,"%04d %s MLSx listing: %s in %s mode", sock, user.alias, path, mode);
now=time(NULL);
if(localtime_r(&now,&cur_tm)==NULL)
memset(&cur_tm,0,sizeof(cur_tm));
if (cmd[3] == 'T') {
write_local_mlsx(NULL, sock, sess, mlsx_feats, path, TRUE);
sockprintf(sock, sess, "250 End");
}
else {
glob(path,0,NULL,&g);
for(i=0;i<(int)g.gl_pathc;i++)
write_local_mlsx(fp, INVALID_SOCKET, -1, mlsx_feats, g.gl_pathv[i], FALSE);
globfree(&g);
fclose(fp);
filexfer(&data_addr,sock,sess,pasv_sock,pasv_sess,&data_sock,&data_sess,fname,0L
,&transfer_inprogress,&transfer_aborted
,TRUE /* delfile */
,TRUE /* tmpfile */
,&lastactive,&user,&client,-1,FALSE,FALSE,FALSE,NULL,protection);
}
}
if(!strnicmp(cmd, "LIST", 4) || !strnicmp(cmd, "NLST", 4)) {
if(!strnicmp(cmd, "LIST", 4))
detail=TRUE;
else
detail=FALSE;
if((fp=fopen(ftp_tmpfname(fname,"lst",sock),"w+b"))==NULL) {
lprintf(LOG_ERR,"%04d !ERROR %d opening %s",sock,errno,fname);
sockprintf(sock,sess, "451 Insufficient system storage");
continue;
}
SKIP_WHITESPACE(p);
if(*p=='-') { /* -Letc */
FIND_WHITESPACE(p);
SKIP_WHITESPACE(p);
filespec=p;
if(*filespec==0)
filespec="*";
SAFEPRINTF2(path,"%s%s",local_dir, filespec);
lprintf(LOG_INFO,"%04d %s listing: %s in %s mode", sock, user.alias, path, mode);
sockprintf(sock,sess, "150 Directory of %s%s", local_dir, filespec);
now=time(NULL);
if(localtime_r(&now,&cur_tm)==NULL)
memset(&cur_tm,0,sizeof(cur_tm));

rswindell
committed
glob(path,0,NULL,&g);
for(i=0;i<(int)g.gl_pathc;i++) {

rswindell
committed
f.size=flength(g.gl_pathv[i]);
t=fdate(g.gl_pathv[i]);
if(localtime_r(&t,&tm)==NULL)
fprintf(fp,"%crw-r--r-- 1 %-8s local %9"PRId32" %s %2d "

rswindell
committed
,isdir(g.gl_pathv[i]) ? 'd':'-'
,ftp_mon[tm.tm_mon],tm.tm_mday);
if(tm.tm_year==cur_tm.tm_year)
fprintf(fp,"%02d:%02d %s\r\n"
,tm.tm_hour,tm.tm_min

rswindell
committed
,getfname(g.gl_pathv[i]));
else
fprintf(fp,"%5d %s\r\n"
,1900+tm.tm_year

rswindell
committed
,getfname(g.gl_pathv[i]));

rswindell
committed
fprintf(fp,"%s\r\n",getfname(g.gl_pathv[i]));

rswindell
committed
globfree(&g);
filexfer(&data_addr,sock,sess,pasv_sock,pasv_sess,&data_sock,&data_sess,fname,0L
,&transfer_inprogress,&transfer_aborted
,TRUE /* delfile */
,TRUE /* tmpfile */
,&lastactive,&user,&client,-1,FALSE,FALSE,FALSE,NULL,protection);
continue;
} /* Local LIST/NLST */
if(!strnicmp(cmd, "CWD ", 4) || !strnicmp(cmd,"XCWD ",5)) {
if(!strnicmp(cmd,"CWD ",4))
p=cmd+4;
else
p=cmd+5;
SKIP_WHITESPACE(p);
tp=p;
if(*tp=='/' || *tp=='\\') /* /local: and /bbs: are valid */
tp++;
if(!strnicmp(tp,BBS_FSYS_DIR,strlen(BBS_FSYS_DIR))) {
local_fsys=FALSE;
sockprintf(sock,sess,"250 CWD command successful (BBS file system mounted).");
lprintf(LOG_INFO,"%04d %s mounted BBS file system", sock, user.alias);
continue;
}
if(!strnicmp(tp,LOCAL_FSYS_DIR,strlen(LOCAL_FSYS_DIR))) {
tp+=strlen(LOCAL_FSYS_DIR); /* already mounted */
p=tp;
}
if(p[1]==':' || !strncmp(p,"\\\\",2))
else if(*p=='/' || *p=='\\') {
SAFEPRINTF2(path,"%s%s",root_dir(local_dir),p+1);
p = FULLPATH(NULL, path, 0);
SAFECOPY(path, p);
free(p);
}
SAFEPRINTF2(fname,"%s%s",local_dir,p);
}
if(!direxist(path)) {
sockprintf(sock,sess,"550 Directory does not exist (%s).",path);
lprintf(LOG_WARNING,"%04d !%s attempted to change to an invalid directory: %s"
,sock, user.alias, path);
} else {
sockprintf(sock,sess,"250 CWD command successful (%s).", local_dir);
}
continue;
} /* Local CWD */
if(!stricmp(cmd,"CDUP") || !stricmp(cmd,"XCUP")) {
SAFEPRINTF(path,"%s..",local_dir);
if(FULLPATH(local_dir,path,sizeof(local_dir))==NULL)
continue;
}
if(!stricmp(cmd, "PWD") || !stricmp(cmd,"XPWD")) {
if(strlen(local_dir)>3)
local_dir[strlen(local_dir)-1]=0; /* truncate '/' */
,local_dir);
continue;
} /* Local PWD */
if(!strnicmp(cmd, "MKD ", 4) || !strnicmp(cmd,"XMKD",4)) {
p=cmd+4;
SKIP_WHITESPACE(p);
SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
SAFEPRINTF2(fname,"%s%s",local_dir,p);
lprintf(LOG_NOTICE,"%04d %s created directory: %s",sock,user.alias,fname);
sockprintf(sock,sess,"521 Error %d creating directory: %s",i,fname);
lprintf(LOG_WARNING,"%04d !%s attempted to create directory: %s (Error %d)"
,sock,user.alias,fname,i);
}
continue;
}
if(!strnicmp(cmd, "RMD ", 4) || !strnicmp(cmd,"XRMD",4)) {
p=cmd+4;
SKIP_WHITESPACE(p);
SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
SAFEPRINTF2(fname,"%s%s",local_dir,p);
lprintf(LOG_NOTICE,"%04d %s removed directory: %s",sock,user.alias,fname);
sockprintf(sock,sess,"450 Error %d removing directory: %s",i,fname);
lprintf(LOG_WARNING,"%04d !%s attempted to remove directory: %s (Error %d)"
,sock,user.alias,fname,i);
}
continue;
}
if(!strnicmp(cmd, "RNFR ",5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
SAFEPRINTF2(ren_from,"%s%s",root_dir(local_dir),p+1);
SAFEPRINTF2(ren_from,"%s%s",local_dir,p);
lprintf(LOG_WARNING,"%04d !%s attempted to rename %s (not found)"
,sock,user.alias,ren_from);
} else
sockprintf(sock,sess,"350 File exists, ready for destination name");
Loading
Loading full blame...