Newer
Older
/* Synchronet FTP server */
/****************************************************************************
* @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 *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include <stdlib.h> /* ltoa in GNU C lib */
#include <stdarg.h> /* va_list, varargs */
#include <string.h> /* strrchr */
#include <fcntl.h> /* O_WRONLY, O_RDONLY, etc. */
#include <errno.h> /* EACCES */
#include <ctype.h> /* toupper */
#include <sys/types.h>
#include <sys/stat.h>
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbs.h"
#include "text.h" /* TOTAL_TEXT */
#include "filedat.h"
#include "xpprintf.h" // vasprintf
#include "md5.h"
#include "sauce.h"
#include "git_branch.h"
#include "git_hash.h"
#define FTP_SERVER "Synchronet FTP Server"
const char* server_abbrev = "ftp";
#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 *text[TOTAL_TEXT];
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
static link_list_t current_connections;
#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 is_user_dirop(cfg, dirnum, user, client);
static int lputs(int level, const char* str)
{
mqtt_lputs(&mqtt, TOPIC_SERVER, level, str);
if(level <= LOG_ERR) {
char errmsg[1024];
SAFEPRINTF2(errmsg, "%-4s %s", server_abbrev, str);
errorlog(&scfg, &mqtt, level, 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 || str == NULL || level > startup->log_level)
return 0;
#if defined(_WIN32)
if(IsBadCodePtr((FARPROC)startup->lputs))
return 0;
#endif
return startup->lputs(startup->cbdata,level,str);
}
#if defined(__GNUC__) // Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
static int lprintf(int level, const char *fmt, ...)
{
va_list argptr;
char sbuf[1024];
va_start(argptr,fmt);
vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
sbuf[sizeof(sbuf)-1]=0;
}
#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
static char* server_host_name(void)
{
return startup->host_name[0] ? startup->host_name : scfg.sys_inetaddr;
}
static void set_state(enum server_state state)
if(startup != NULL) {
if(startup->set_state != NULL)
startup->set_state(startup->cbdata, state);
}
static void update_clients(void)
{
if(startup != NULL) {
uint32_t count = protected_uint32_value(active_clients);
if(startup->clients != NULL)
startup->clients(startup->cbdata, count);
}
static void client_on(SOCKET sock, client_t* client, BOOL update)
if(!update)
listAddNodeData(¤t_connections, client->addr, strlen(client->addr) + 1, sock, LAST_NODE);
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,TRUE,sock,client,update);
mqtt_client_on(&mqtt, TRUE, sock, client, update);
}
static void client_off(SOCKET sock)
{
listRemoveTaggedNode(¤t_connections, sock, /* free_data */TRUE);
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
mqtt_client_on(&mqtt, FALSE, sock, NULL, FALSE);
static void thread_up(BOOL setuid)
if(startup != NULL) {
if(startup->thread_up != NULL)
startup->thread_up(startup->cbdata,TRUE, setuid);
}
static int32_t thread_down(void)
int32_t count = protected_uint32_adjust_fetch(&thread_count,-1);
if(startup != NULL) {
if(startup->thread_up != NULL)
startup->thread_up(startup->cbdata,FALSE, FALSE);
}
return count;
char error[256];
startup->socket_open(startup->cbdata,TRUE);
if(set_socket_options(&scfg, sock, "FTP", error, sizeof(error)))
lprintf(LOG_ERR,"%04d !ERROR %s",sock, error);
}
static void ftp_close_socket_cb(SOCKET sock, void *cbdata)
{
if(startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,FALSE);
}
static SOCKET ftp_open_socket(int domain, int type)
{
SOCKET sock;
sock=socket(domain, type, IPPROTO_IP);
if(sock != INVALID_SOCKET)
ftp_open_socket_cb(sock, NULL);
return(sock);
}
#ifdef __BORLANDC__
#pragma argsused
#endif
static int ftp_close_socket(SOCKET* sock, CRYPT_SESSION *sess, int line)
if (*sess != -1) {
cryptDestroySession(*sess);
*sess = -1;
}
lprintf(LOG_WARNING,"0000 !INVALID_SOCKET in close_socket from line %u",line);
shutdown(*sock,SHUT_RDWR); /* required on Unix */
if(startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,FALSE);

rswindell
committed
if(result!=0) {
if(ERROR_VALUE!=ENOTSOCK)
lprintf(LOG_WARNING,"%04d !ERROR %d closing socket from line %u",*sock,ERROR_VALUE,line);
*sock=INVALID_SOCKET;
return(result);
}
#define GCES(status, sock, session, estr, action) do { \
int GCES_level; \
get_crypt_error_string(status, session, &estr, action, &GCES_level);\
if (estr != NULL) { \
lprintf(GCES_level, "%04d TLS %s", sock, estr); \
free_crypt_attrstr(estr); \
estr=NULL; \
#if defined(__GNUC__) // Catch printf-format errors with sockprintf
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
#endif
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...)
int maxlen;
int result;
va_list argptr;
char sbuf[1024];
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 */
if(!socket_writable(sock, 300000)) {
lprintf(LOG_WARNING,"%04d !WARNING socket not ready for write" ,sock);
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);
}
void recverror(SOCKET socket, int rd, int line)
lprintf(LOG_NOTICE,"%04d Socket closed by peer on receive (line %u)"
if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_NOTICE,"%04d Connection reset by peer on receive (line %u)"
else if(ERROR_VALUE==ECONNABORTED)
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)
if(ftp_set==NULL || terminate_server) {
sockprintf(sock,sess,"421 Server downed, aborting.");
lprintf(LOG_WARNING,"%04d Server downed, aborting",sock);
if ((ret = cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, 0)) != CRYPT_OK)
GCES(ret, sock, sess, estr, "setting read timeout");
/* Successive reads will be with the full timeout after a socket_readable() */
cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity);
switch(ret) {
case CRYPT_OK:
break;
case CRYPT_ERROR_TIMEOUT:
if (!first) {
GCES(ret, sock, sess, estr, "popping data");
return -1;
}
break;
if((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
if (!socket_readable(sock, startup->max_inactivity * 1000)) {
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);
if (!socket_readable(sock, startup->max_inactivity * 1000)) {
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);
#ifdef SOCKET_DEBUG_RECV_CHAR
socket_debug[sock]|=SOCKET_DEBUG_RECV_CHAR;
#endif
i=recv(sock, buf, 1, 0);
#ifdef SOCKET_DEBUG_RECV_CHAR
socket_debug[sock]&=~SOCKET_DEBUG_RECV_CHAR;
#endif
return i;
}
}
int sockreadline(SOCKET socket, CRYPT_SESSION sess, char* buf, int len, time_t* lastactive)
{
char ch;
int i,rd=0;
buf[0]=0;
if(socket==INVALID_SOCKET) {
lprintf(LOG_WARNING,"INVALID SOCKET in call to sockreadline");
return(0);
}
while(rd<len-1) {
i = sock_recvbyte(socket, sess, &ch, lastactive);
if(i<1) {
if (sess != -1)
recverror(socket,i,__LINE__);

rswindell
committed
return(i);
}
if(ch=='\n' /* && rd>=1 */) { /* Mar-9-2003: terminate on sole LF */
if(rd>0 && buf[rd-1]=='\r')
buf[rd-1]=0;
else
buf[rd]=0;
terminate_server=TRUE;
int ftp_remove(SOCKET sock, int line, const char* fname, const char* username)
{
int ret=0;
if(fexist(fname) && (ret=remove(fname))!=0) {
if(fexist(fname)) // In case there was a race condition (other host deleted file first)
lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) (line %d) removing file: %s", sock, username, errno, strerror(errno), line, fname);
return ret;
}
BOOL* inprogress;
BOOL* aborted;
BOOL delfile;
BOOL tmpfile;
BOOL credits;
BOOL append;
off_t 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 str[256];
char username[128];
uint64_t l;
off_t total=0;
off_t last_total=0;
off_t 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;
free(arg);
SetThreadName("sbbs/ftpSend");
thread_up(TRUE /* setuid */);
length=flength(xfer.filename);
if(length < 1) {
if(xfer.tmpfile) {
if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 No files");
} else {
lprintf(LOG_WARNING, "%04d <%s> !DATA cannot send file (%s) with size of %"PRIdOFF" bytes"
,xfer.ctrl_sock, xfer.user->alias, xfer.filename, length);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 Invalid file size: %"PRIdOFF, length);
}
ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
*xfer.inprogress=FALSE;
thread_down();
return;
}
if((fp=fnopen(NULL,xfer.filename,O_RDONLY|O_BINARY))==NULL /* non-shareable open failed */
&& (fp=fopen(xfer.filename,"rb"))==NULL) { /* shareable open failed */
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
,xfer.ctrl_sock, xfer.user->alias, errno, strerror(errno), __LINE__, xfer.filename);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 ERROR %d (%s) opening %s", errno, strerror(errno), xfer.filename);
if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
thread_down();
#ifdef SOCKET_DEBUG_SENDTHREAD
socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SENDTHREAD;
#endif
if(xfer.filepos < 0)
xfer.filepos = 0;
if(startup->options&FTP_OPT_DEBUG_DATA || xfer.filepos)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d sending %s from offset %"PRIdOFF
,xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos);
fseeko(fp,xfer.filepos,SEEK_SET);
while((xfer.filepos+total)<length) {
/* Periodic progress report */
if(total && now>=last_report+XFER_REPORT_INTERVAL) {
if(xfer.filepos)
sprintf(str," from offset %"PRIdOFF,xfer.filepos);
else
str[0]=0;
lprintf(LOG_INFO,"%04d <%s> DATA Sent %"PRIdOFF" bytes (%"PRIdOFF" total) of %s (%lu cps)%s"
,xfer.ctrl_sock, xfer.user->alias, total,length,xfer.filename
,(ulong)((total-last_total)/(now-last_report))
,str);
last_total=total;
last_report=now;
}
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer aborted.");
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer locally aborted",xfer.ctrl_sock, xfer.user->alias);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer locally aborted.");
/* Check socket for writability */
if (!socket_writable(*xfer.data_sock, 1000))
fseeko(fp,xfer.filepos+total,SEEK_SET);
rd=fread(buf,sizeof(char),sizeof(buf),fp);
socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SEND;
#endif
if (*xfer.data_sess != -1) {
int status = cryptPushData(*xfer.data_sess, buf, rd, &wr);
if (status != CRYPT_OK) {
GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "pushing data");
wr = -1;
}
else {
status = cryptFlushData(*xfer.data_sess);
if (status != CRYPT_OK) {
GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "flushing data");
wr = -1;
}
}
}
else
wr=sendsocket(*xfer.data_sock,buf,rd);
socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SEND;
#endif
if(ERROR_VALUE==EWOULDBLOCK) {
/*lprintf(LOG_WARNING,"%04d DATA send would block, retrying",xfer.ctrl_sock);*/
YIELD();
continue;
}
else if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_WARNING,"%04d <%s> DATA Connection reset by peer, sending on socket %d"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_WARNING,"%04d <%s> DATA Connection aborted by peer, sending on socket %d"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d sending on data socket %d"
,xfer.ctrl_sock, xfer.user->alias,ERROR_VALUE,*xfer.data_sock);
/* Send NAK */
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Error %d sending on DATA channel"
,ERROR_VALUE);
error=TRUE;
break;
}
if(wr==0) {
lprintf(LOG_WARNING,"%04d <%s> !DATA socket %d disconnected",xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 DATA channel disconnected");
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) sending on socket %d"
,xfer.ctrl_sock, xfer.user->alias, wr, ERROR_VALUE, *xfer.data_sock);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"451 DATA send error");
error=TRUE;
break;
}
total+=wr;
*xfer.lastactive=time(NULL);
//YIELD();
if((i=ferror(fp))!=0)
lprintf(LOG_ERR,"%04d <%s> !DATA FILE ERROR %d (%d, %s)"
,xfer.ctrl_sock, xfer.user->alias, i, errno, strerror(errno));
ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__); /* Signal end of file */
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket closed",xfer.ctrl_sock, xfer.user->alias);
dur=(long)(time(NULL)-start);
cps=(ulong)(dur ? total/dur : total*2);
lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %"PRIdOFF" bytes sent in %lu seconds (%lu cps)"
,xfer.user->alias
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"226 Download complete (%lu cps).",cps);
if(xfer.dir>=0 && !xfer.tmpfile) {
if(!loadfile(&scfg, xfer.dir, getfname(xfer.filename), &f, file_detail_normal)) {
lprintf(LOG_ERR, "%04d <%s> DATA downloaded: %s (not found in filebase!)"
,xfer.ctrl_sock
,xfer.user->alias
,xfer.filename);
} else {
f.hdr.times_downloaded++;
f.hdr.last_downloaded = time32(NULL);
updatefile(&scfg, &f);
lprintf(LOG_INFO,"%04d <%s> DATA downloaded: %s (%u times total)"
,xfer.ctrl_sock
,xfer.user->alias
,xfer.filename
,f.hdr.times_downloaded);
/**************************/
/* Update Uploader's Info */
/**************************/
uploader.number = 0;
if(f.from_ext != NULL)
uploader.number = atoi(f.from_ext);
if(uploader.number == 0)
uploader.number=matchuser(&scfg, f.from, TRUE /*sysop_alias*/);
&& uploader.number!=xfer.user->number
&& getuserdat(&scfg,&uploader)==0
&& uploader.firston < (time_t)f.hdr.when_imported.time) {
l=f.cost;
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;
adjustuserval(&scfg, uploader.number, USER_MIN, mod);
sprintf(tmp,"%lu minute",mod);
} else {
mod=(ulong)(l*(scfg.dir[f.dir]->dn_pct/100.0));
adjustuserval(&scfg, uploader.number, USER_CDT, mod);
u32toac(mod,tmp,',');
if(!(scfg.dir[f.dir]->misc&DIR_QUIET)) {
const char* prefix = xfer.filepos ? "partially FTP-" : "FTP-";
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 */
if(mod == 0)
safe_snprintf(str,sizeof(str),text[FreeDownloadUserMsg]
,getfname(xfer.filename)
,prefix
else
safe_snprintf(str,sizeof(str),text[DownloadUserMsg]
,getfname(xfer.filename)
,prefix
,username,tmp);
putsmsg(&scfg,uploader.number,str);
}
mqtt_file_download(&mqtt, xfer.user, &f, total, xfer.client);
smb_freefilemem(&f);
if(!xfer.tmpfile && !xfer.delfile && !(scfg.dir[f.dir]->misc&DIR_NOSTAT))
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))
(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
#if defined(SOCKET_DEBUG_SENDTHREAD)
socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SENDTHREAD;
#endif
thread_down();
}
static void receive_thread(void* arg)
{
char str[128];
char extdesc[LEN_EXTDESC + 1] = "";
off_t total=0;
off_t last_total=0;
ulong dur;
ulong cps;
BOOL error=FALSE;
xfer_t xfer;
time_t now;
time_t start;
time_t last_report;
free(arg);
SetThreadName("sbbs/ftpReceive");
thread_up(TRUE /* setuid */);
if((fp=fopen(xfer.filename,xfer.append ? "ab" : "wb"))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
,xfer.ctrl_sock, xfer.user->alias, errno, strerror(errno), __LINE__, xfer.filename);
sockprintf(xfer.ctrl_sock,sess,"450 ERROR %d (%s) opening %s", errno, strerror(errno), xfer.filename);
thread_down();
if(xfer.append)
xfer.filepos=filelength(fileno(fp));
if(xfer.filepos < 0)
xfer.filepos = 0;

rswindell
committed
if(xfer.filepos || startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d receiving %s from offset %"PRIdOFF
,xfer.ctrl_sock,xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos);
fseeko(fp,xfer.filepos,SEEK_SET);
// Determine the maximum file size to allow, accounting for minimum free space
char path[MAX_PATH + 1];
SAFECOPY(path, xfer.filename);
*getfname(path) = '\0';
int64_t avail = getfreediskspace(path, 1);
if(avail <= scfg.min_dspace)
avail = 0;
else
avail -= scfg.min_dspace;
int64_t max_fsize = xfer.filepos + avail;
if(startup->max_fsize > 0 && startup->max_fsize < max_fsize)
max_fsize = startup->max_fsize;
if(startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> DATA Limiting uploaded file size to %" PRIu64 " (%s) bytes"
,xfer.ctrl_sock, xfer.user->alias, max_fsize
,byte_estimate_to_str(max_fsize, tmp, sizeof(tmp), 1, 1));
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 %"PRIdOFF,xfer.filepos);
else
str[0]=0;
lprintf(LOG_INFO,"%04d <%s> DATA Received %"PRIdOFF" bytes of %s (%lu cps)%s"
,xfer.ctrl_sock
,xfer.user->alias
,total,xfer.filename
,(ulong)((total-last_total)/(now-last_report))
,str);
last_total=total;
if(xfer.filepos + total > max_fsize) {
lprintf(LOG_WARNING,"%04d <%s> !DATA received %"PRIdOFF" bytes of %s exceeds maximum allowed (%"PRIu64" bytes)"
,xfer.ctrl_sock, xfer.user->alias, xfer.filepos+total, xfer.filename, startup->max_fsize);
sockprintf(xfer.ctrl_sock,sess,"552 File size exceeds maximum allowed (%"PRIu64" bytes)", startup->max_fsize);
error=TRUE;
break;
}
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);

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

rswindell
committed
/* Send NAK */
sockprintf(xfer.ctrl_sock,sess,"426 Transfer locally aborted.");
/* Check socket for readability */
if (!socket_readable(*xfer.data_sock, 1000))
#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");
if (status != CRYPT_ERROR_COMPLETE)
rd = SOCKET_ERROR;
}
else {
rd=recv(*xfer.data_sock,buf,sizeof(buf),0);
}
#if defined(SOCKET_DEBUG_RECV_BUF)
socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_RECV_BUF;
if(rd<1) {
if(rd==0) { /* Socket closed */
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d closed by client"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
break;
}
if(rd==SOCKET_ERROR) {