-
Rob Swindell authored
The lower of the configured maximum file size (for the FTP server) and the available disk space minus the configured minimum free disk space is used as the maximum file size to allow upload. Appended files are accounted for too.
Rob Swindell authoredThe lower of the configured maximum file size (for the FTP server) and the available disk space minus the configured minimum free disk space is used as the maximum file size to allow upload. Appended files are accounted for too.
ftpsrvr.c 157.88 KiB
/* 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. *
****************************************************************************/
/* ANSI C Library headers */
#include <stdio.h>
#include <stdlib.h> /* ltoa in GNU C lib */
#include <stdarg.h> /* va_list, varargs */
#include <string.h> /* strrchr */
#include <fcntl.h> /* O_WRONLY, O_RDONLY, etc. */
#include <errno.h> /* EACCES */
#include <ctype.h> /* toupper */
#include <sys/types.h>
#include <sys/stat.h>
/* Synchronet-specific headers */
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbs.h"
#include "text.h" /* TOTAL_TEXT */
#include "ftpsrvr.h"
#include "filedat.h"
#include "telnet.h"
#include "multisock.h"
#include "ssl.h"
#include "cryptlib.h"
#include "xpprintf.h" // vasprintf
#include "md5.h"
#include "sauce.h"
#include "git_branch.h"
#include "git_hash.h"
/* Constants */
#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)
#define MLSX_CREATE (1<<6)
static ftp_startup_t* startup=NULL;
static scfg_t scfg;
static struct mqtt mqtt;
static struct xpms_set *ftp_set = 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 */
#endif
char* genvpath(int lib, int dir, char* str);
typedef struct {
SOCKET socket;
union xp_sockaddr client_addr;
socklen_t client_addr_len;
} ftp_t;
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;
va_end(argptr);
return lputs(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;
return (TRUE);
}
lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
return (FALSE);
}
#else /* No WINSOCK */
#define winsock_startup() (TRUE)
#define SOCKLIB_DESC NULL
#endif
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);
mqtt_server_state(&mqtt, 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;
}
static void ftp_open_socket_cb(SOCKET sock, void *cbdata)
{
char error[256];
if(startup!=NULL && startup->socket_open!=NULL)
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)
{
int result;
if (*sess != -1) {
cryptDestroySession(*sess);
*sess = -1;
}
if((*sock)==INVALID_SOCKET) {
lprintf(LOG_WARNING,"0000 !INVALID_SOCKET in close_socket from line %u",line);
return(-1);
}
shutdown(*sock,SHUT_RDWR); /* required on Unix */
result=closesocket(*sock);
if(startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,FALSE);
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; \
} \
} while (0)
#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 len;
int maxlen;
int result;
va_list argptr;
char sbuf[1024];
char *estr;
va_start(argptr,fmt);
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);
len+=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);
return(0);
}
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 {
GCES(result, sock, sess, estr, "sending data");
if (result != CRYPT_ERROR_TIMEOUT)
return 0;
}
result = cryptFlushData(sess);
if (result != CRYPT_OK) {
GCES(result, sock, sess, estr, "flushing data");
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)
{
if(rd==0)
lprintf(LOG_NOTICE,"%04d Socket closed by peer on receive (line %u)"
,socket, line);
else if(rd==SOCKET_ERROR) {
if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_NOTICE,"%04d Connection reset by peer on receive (line %u)"
,socket, line);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_NOTICE,"%04d Connection aborted by peer on receive (line %u)"
,socket, line);
else
lprintf(LOG_NOTICE,"%04d !ERROR %d receiving on socket (line %u)"
,socket, ERROR_VALUE, line);
} else
lprintf(LOG_WARNING,"%04d !ERROR: recv on socket returned unexpected value: %d (line %u)"
,socket, rd, line);
}
static int sock_recvbyte(SOCKET sock, CRYPT_SESSION sess, char *buf, time_t *lastactive)
{
int len=0;
int ret;
int i;
char *estr;
BOOL first = TRUE;
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 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;
case CRYPT_ERROR_COMPLETE:
return 0;
default:
GCES(ret, sock, sess, estr, "popping data");
if (ret < -1)
return ret;
return -2;
}
first = FALSE;
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);
}
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);
}
}
}
}
else {
while (1) {
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);
}
continue;
}
#ifdef SOCKET_DEBUG_RECV_CHAR
socket_debug[sock]|=SOCKET_DEBUG_RECV_CHAR;
#endif
i=recv(sock, buf, 1, 0);
#ifdef SOCKET_DEBUG_RECV_CHAR
socket_debug[sock]&=~SOCKET_DEBUG_RECV_CHAR;
#endif
return i;
}
}
}
int sockreadline(SOCKET socket, CRYPT_SESSION sess, char* buf, int len, time_t* lastactive)
{
char ch;
int i,rd=0;
buf[0]=0;
if(socket==INVALID_SOCKET) {
lprintf(LOG_WARNING,"INVALID SOCKET in call to sockreadline");
return(0);
}
while(rd<len-1) {
i = sock_recvbyte(socket, sess, &ch, lastactive);
if(i<1) {
if (sess != -1)
recverror(socket,i,__LINE__);
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;
return(rd);
}
void ftp_terminate(void)
{
lprintf(LOG_INFO,"FTP Server terminate");
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;
}
typedef struct {
SOCKET ctrl_sock;
CRYPT_SESSION ctrl_sess;
SOCKET* data_sock;
CRYPT_SESSION* data_sess;
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 tmp[128];
char username[128];
char host_ip[INET6_ADDRSTRLEN];
int i;
int rd;
int wr;
long mod;
uint64_t l;
off_t total=0;
off_t last_total=0;
ulong dur;
ulong cps;
off_t length;
BOOL error=FALSE;
FILE* fp;
file_t f;
xfer_t xfer;
time_t now;
time_t start;
time_t last_report;
user_t uploader;
union xp_sockaddr addr;
socklen_t addr_len;
char *estr;
xfer=*(xfer_t*)arg;
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);
ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
*xfer.inprogress=FALSE;
thread_down();
return;
}
#ifdef SOCKET_DEBUG_SENDTHREAD
socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SENDTHREAD;
#endif
*xfer.aborted=FALSE;
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);
last_report=start=time(NULL);
while((xfer.filepos+total)<length) {
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 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;
}
if(*xfer.aborted==TRUE) {
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer aborted.");
error=TRUE;
break;
}
if(ftp_set==NULL || terminate_server) {
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer locally aborted",xfer.ctrl_sock, xfer.user->alias);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer locally aborted.");
error=TRUE;
break;
}
/* Check socket for writability */
if (!socket_writable(*xfer.data_sock, 1000))
continue;
fseeko(fp,xfer.filepos+total,SEEK_SET);
rd=fread(buf,sizeof(char),sizeof(buf),fp);
if(rd<1) /* EOF or READ error */
break;
#ifdef SOCKET_DEBUG_SEND
socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SEND;
#endif
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);
#ifdef SOCKET_DEBUG_SEND
socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SEND;
#endif
if(wr<1) {
if(wr==SOCKET_ERROR) {
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);
else
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d sending on data socket %d"
,xfer.ctrl_sock, xfer.user->alias,ERROR_VALUE,*xfer.data_sock);
/* Send NAK */
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Error %d sending on DATA channel"
,ERROR_VALUE);
error=TRUE;
break;
}
if(wr==0) {
lprintf(LOG_WARNING,"%04d <%s> !DATA socket %d disconnected",xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 DATA channel disconnected");
error=TRUE;
break;
}
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) sending on socket %d"
,xfer.ctrl_sock, xfer.user->alias, wr, ERROR_VALUE, *xfer.data_sock);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"451 DATA send error");
error=TRUE;
break;
}
total+=wr;
*xfer.lastactive=time(NULL);
//YIELD();
}
if((i=ferror(fp))!=0)
lprintf(LOG_ERR,"%04d <%s> !DATA FILE ERROR %d (%d, %s)"
,xfer.ctrl_sock, xfer.user->alias, i, errno, strerror(errno));
ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__); /* Signal end of file */
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket closed",xfer.ctrl_sock, xfer.user->alias);
if(!error) {
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.ctrl_sock
,xfer.user->alias
,total,dur,cps);
sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"226 Download complete (%lu cps).",cps);
if(xfer.dir>=0 && !xfer.tmpfile) {
memset(&f,0,sizeof(f));
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*/);
if(uploader.number
&& 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
,username);
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))
inc_download_stats(&scfg, 1, (ulong)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(ftp_set!=NULL && !terminate_server)
*xfer.inprogress=FALSE;
if(xfer.tmpfile) {
if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
}
else if(xfer.delfile && !error)
(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 buf[8192];
char extdesc[LEN_EXTDESC + 1] = "";
char tmp[MAX_PATH+1];
int rd;
off_t total=0;
off_t last_total=0;
ulong dur;
ulong cps;
BOOL error=FALSE;
BOOL filedat;
FILE* fp;
file_t f;
xfer_t xfer;
time_t now;
time_t start;
time_t last_report;
CRYPT_SESSION sess = -1;
char *estr;
xfer=*(xfer_t*)arg;
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);
ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
*xfer.inprogress=FALSE;
thread_down();
return;
}
if(xfer.append)
xfer.filepos=filelength(fileno(fp));
if(xfer.filepos < 0)
xfer.filepos = 0;
*xfer.aborted=FALSE;
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';
uint64_t avail = getfreediskspace(path, 1);
if(avail <= scfg.min_dspace)
avail = 0;
else
avail -= scfg.min_dspace;
uint64_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;
last_report=now;
}
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;
}
if(*xfer.aborted==TRUE) {
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);
/* Send NAK */
sockprintf(xfer.ctrl_sock,sess,"426 Transfer aborted.");
error=TRUE;
break;
}
if(ftp_set==NULL || terminate_server) {
lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer locally aborted",xfer.ctrl_sock, xfer.user->alias);
/* Send NAK */
sockprintf(xfer.ctrl_sock,sess,"426 Transfer locally aborted.");
error=TRUE;
break;
}
/* Check socket for readability */
if (!socket_readable(*xfer.data_sock, 1000))
continue;
#if defined(SOCKET_DEBUG_RECV_BUF)
socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_RECV_BUF;
#endif
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;
#endif
if(rd<1) {
if(rd==0) { /* Socket closed */
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d closed by client"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
break;
}
if(rd==SOCKET_ERROR) {
if(ERROR_VALUE==EWOULDBLOCK) {
/*lprintf(LOG_WARNING,"%04d DATA recv would block, retrying",xfer.ctrl_sock);*/
YIELD();
continue;
}
else if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_WARNING,"%04d <%s> DATA Connection reset by peer, receiving on socket %d"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_WARNING,"%04d <%s> DATA Connection aborted by peer, receiving on socket %d"
,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
else
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d receiving on data socket %d"
,xfer.ctrl_sock, xfer.user->alias,ERROR_VALUE,*xfer.data_sock);
/* Send NAK */
sockprintf(xfer.ctrl_sock,sess,"426 Error %d receiving on DATA channel"
,ERROR_VALUE);
error=TRUE;
break;
}
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR recv returned %d on socket %d"
,xfer.ctrl_sock, xfer.user->alias,rd,*xfer.data_sock);
/* 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();
}
fclose(fp);
ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
if(error && startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d closed",xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
if(xfer.filepos+total < startup->min_fsize) {
lprintf(LOG_WARNING,"%04d <%s> DATA received %"PRIdOFF" bytes for %s, less than minimum required (%"PRIu64" bytes)"
,xfer.ctrl_sock, xfer.user->alias, xfer.filepos+total, xfer.filename, startup->min_fsize);
sockprintf(xfer.ctrl_sock,sess,"550 File size less than minimum required (%"PRIu64" bytes)"
,startup->min_fsize);
error=TRUE;
}
if(error) {
if(!xfer.append)
(void)ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
} else {
dur=(long)(time(NULL)-start);
cps=(ulong)(dur ? total/dur : total*2);
lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %"PRIdOFF" bytes received in %lu seconds (%lu cps)"
,xfer.ctrl_sock
,xfer.user->alias
,total,dur,cps);
if(xfer.dir>=0) {
memset(&f,0,sizeof(f));
f.dir = xfer.dir;
smb_hfield_str(&f, SMB_FILENAME, getfname(xfer.filename));
smb_hfield_str(&f, SENDER, xfer.user->alias);
filedat=findfile(&scfg, xfer.dir, f.name, NULL);
if(scfg.dir[f.dir]->misc&DIR_AONLY) /* Forced anonymous */
f.hdr.attr |= MSG_ANONYMOUS;
off_t cdt = flength(xfer.filename);
smb_hfield_bin(&f, SMB_COST, cdt);
char fdesc[LEN_FDESC + 1] = "";
/* Description specified with DESC command? */
if(xfer.desc != NULL)
SAFECOPY(fdesc, xfer.desc);
/* Necessary for DIR and LIB ARS keyword support in subsequent chk_ar()'s */
SAFECOPY(xfer.user->curdir, scfg.dir[f.dir]->code);
/* FILE_ID.DIZ support */
if(scfg.dir[f.dir]->misc&DIR_DIZ) {
lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ from: %s",xfer.ctrl_sock, xfer.user->alias,xfer.filename);
if(extract_diz(&scfg, &f, /* diz_fnames */NULL, tmp, sizeof(tmp))) {
struct sauce_charinfo sauce;
lprintf(LOG_DEBUG,"%04d <%s> DATA Parsing DIZ: %s",xfer.ctrl_sock, xfer.user->alias,tmp);
char* lines = read_diz(tmp, &sauce);
format_diz(lines, extdesc, sizeof(extdesc), sauce.width, sauce.ice_color);
free_diz(lines);
if(!fdesc[0]) { /* use for normal description */
prep_file_desc(extdesc, fdesc); /* strip control chars and dupe chars */
}
file_sauce_hfields(&f, &sauce);
ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias);
} else
lprintf(LOG_DEBUG,"%04d <%s> DATA DIZ does not exist in: %s",xfer.ctrl_sock, xfer.user->alias ,xfer.filename);
} /* FILE_ID.DIZ support */
if(f.desc == NULL)
smb_new_hfield_str(&f, SMB_FILEDESC, fdesc);
if(filedat) {
if(updatefile(&scfg, &f))
lprintf(LOG_INFO,"%04d <%s> DATA updated file: %s"
,xfer.ctrl_sock, xfer.user->alias, f.name);
else
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR updating file (%s) in database"
,xfer.ctrl_sock, xfer.user->alias, f.name);
/* need to update the index here */
} else {
if(addfile(&scfg, &f, extdesc, /* metatdata: */NULL, xfer.client))
lprintf(LOG_INFO,"%04d <%s> DATA uploaded file: %s"
,xfer.ctrl_sock, xfer.user->alias, f.name);
else
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR adding file (%s) to database"
,xfer.ctrl_sock, xfer.user->alias, f.name);
}
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=(uint32_t)adjustuserval(&scfg, xfer.user->number, USER_MIN
,((ulong)(total*(scfg.dir[f.dir]->up_pct/100.0))/cps)/60);
else
xfer.user->cdt=adjustuserval(&scfg, xfer.user->number, USER_CDT
,cdt*(uint64_t)(scfg.dir[f.dir]->up_pct/100.0));
}
if(!(scfg.dir[f.dir]->misc&DIR_NOSTAT))
inc_upload_stats(&scfg, 1, (ulong)total);
mqtt_file_upload(&mqtt, xfer.user, &f, total, xfer.client);
smb_freefilemem(&f);
}
/* Send ACK */
sockprintf(xfer.ctrl_sock,sess,"226 Upload complete (%lu cps).",cps);
}
if(ftp_set!=NULL && !terminate_server)
*xfer.inprogress=FALSE;
thread_down();
}
// Returns TRUE upon error?!?
static BOOL start_tls(SOCKET *sock, CRYPT_SESSION *sess, BOOL resp)
{
BOOL nodelay;
ulong nb;
int status;
char *estr = NULL;
int level;
if (get_ssl_cert(&scfg, &estr, &level) == -1) {
if (estr) {
lprintf(level, "%04d TLS %s", *sock, estr);
free_crypt_attrstr(estr);
}
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return FALSE;
}
if ((status = cryptCreateSession(sess, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
GCES(status, *sock, CRYPT_UNUSED, estr, "creating session");
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return FALSE;
}
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "disabling certificate verification");
cryptDestroySession(*sess);
*sess = -1;
if(resp)
sockprintf(*sock, *sess, "431 TLS not available");
return FALSE;
}
lock_ssl_cert();
if ((status=cryptSetAttribute(*sess, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
unlock_ssl_cert();
GCES(status, *sock, *sess, estr, "setting private key");
cryptDestroySession(*sess);
*sess = -1;
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return FALSE;
}
nodelay = TRUE;
(void)setsockopt(*sock,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
nb=0;
ioctlsocket(*sock,FIONBIO,&nb);
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_NETWORKSOCKET, *sock)) != CRYPT_OK) {
unlock_ssl_cert();
GCES(status, *sock, *sess, estr, "setting network socket");
cryptDestroySession(*sess);
*sess = -1;
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return TRUE;
}
if (resp)
sockprintf(*sock, -1, "234 Ready to start TLS");
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
unlock_ssl_cert();
GCES(status, *sock, *sess, estr, "setting session active");
return TRUE;
}
unlock_ssl_cert();
if (startup->max_inactivity) {
if ((status = cryptSetAttribute(*sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting read timeout");
return TRUE;
}
}
return FALSE;
}
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, off_t filepos, BOOL* inprogress, BOOL* aborted
,BOOL delfile, BOOL tmpfile
,time_t* lastactive
,user_t* user
,client_t* client
,int dir
,BOOL receiving
,BOOL credits
,BOOL append
,char* desc,BOOL protected)
{
int result;
ulong l;
socklen_t addr_len;
union xp_sockaddr server_addr;
BOOL reuseaddr;
xfer_t* xfer;
char host_ip[INET6_ADDRSTRLEN];
if((*inprogress)==TRUE) {
lprintf(LOG_WARNING,"%04d <%s> !DATA TRANSFER already in progress",ctrl_sock, user->alias);
sockprintf(ctrl_sock,ctrl_sess,"425 Transfer already in progress.");
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
return;
}
*inprogress=TRUE;
if(*data_sock!=INVALID_SOCKET)
ftp_close_socket(data_sock,data_sess,__LINE__);
inet_addrtop(addr, host_ip, sizeof(host_ip));
if(pasv_sock==INVALID_SOCKET) { /* !PASV */
if((*data_sock=socket(addr->addr.sa_family, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d opening socket", ctrl_sock, user->alias, ERROR_VALUE);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d opening socket",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
return;
}
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d opened",ctrl_sock, user->alias,*data_sock);
/* Use port-1 for all data connections */
reuseaddr=TRUE;
(void)setsockopt(*data_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr));
addr_len = sizeof(server_addr);
if((result=getsockname(ctrl_sock, &server_addr.addr,&addr_len))!=0) {
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) getting address/port of command socket (%u)"
,ctrl_sock, user->alias,result,ERROR_VALUE,pasv_sock);
return;
}
inet_setaddrport(&server_addr, inet_addrport(&server_addr)-1); /* 20? */
result=bind(*data_sock, &server_addr.addr,addr_len);
if(result!=0) {
inet_setaddrport(&server_addr, 0); /* any user port */
result=bind(*data_sock, &server_addr.addr,addr_len);
}
if(result!=0) {
lprintf(LOG_ERR,"%04d <%s> DATA ERROR %d (%d) binding socket %d"
,ctrl_sock, user->alias, result, ERROR_VALUE, *data_sock);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d binding socket",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
ftp_close_socket(data_sock,data_sess,__LINE__);
return;
}
result=connect(*data_sock, &addr->addr,xp_sockaddr_len(addr));
if(result!=0) {
lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d (%d) connecting to client %s port %u on socket %d"
,ctrl_sock, user->alias,result,ERROR_VALUE
,host_ip,inet_addrport(addr),*data_sock);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d connecting to socket",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
ftp_close_socket(data_sock,data_sess,__LINE__);
return;
}
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d connected to %s port %u"
,ctrl_sock, user->alias,*data_sock,host_ip,inet_addrport(addr));
if (protected) {
if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
lprintf(LOG_DEBUG,"%04d <%s> !DATA ERROR activating TLS"
,ctrl_sock, user->alias);
sockprintf(ctrl_sock,ctrl_sess,"425 Error activating TLS");
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
ftp_close_socket(data_sock,data_sess,__LINE__);
return;
}
}
} else { /* PASV */
if(startup->options&FTP_OPT_DEBUG_DATA) {
addr_len=sizeof(*addr);
if((result=getsockname(pasv_sock, &addr->addr,&addr_len))!=0)
lprintf(LOG_ERR,"%04d <%s> PASV !DATA ERROR %d (%d) getting address/port of passive socket (%u)"
,ctrl_sock, user->alias,result,ERROR_VALUE,pasv_sock);
else
lprintf(LOG_DEBUG,"%04d <%s> PASV DATA socket %d listening on %s port %u"
,ctrl_sock, user->alias,pasv_sock,host_ip,inet_addrport(addr));
}
if (!socket_readable(pasv_sock, TIMEOUT_SOCKET_LISTEN * 1000)) {
lprintf(LOG_WARNING,"%04d <%s> PASV !WARNING socket not readable"
,ctrl_sock, user->alias);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d selecting socket for connection",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
return;
}
addr_len=sizeof(*addr);
#ifdef SOCKET_DEBUG_ACCEPT
socket_debug[ctrl_sock]|=SOCKET_DEBUG_ACCEPT;
#endif
*data_sock=accept(pasv_sock,&addr->addr,&addr_len);
#ifdef SOCKET_DEBUG_ACCEPT
socket_debug[ctrl_sock]&=~SOCKET_DEBUG_ACCEPT;
#endif
if(*data_sock==INVALID_SOCKET) {
lprintf(LOG_WARNING,"%04d <%s> PASV !DATA ERROR %d accepting connection on socket %d"
,ctrl_sock, user->alias,ERROR_VALUE,pasv_sock);
sockprintf(ctrl_sock,ctrl_sess,"425 Error %d accepting connection",ERROR_VALUE);
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
return;
}
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> PASV DATA socket %d connected to %s port %u"
,ctrl_sock, user->alias,*data_sock,host_ip,inet_addrport(addr));
if (protected) {
if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
lprintf(LOG_WARNING,"%04d <%s> PASV !DATA ERROR starting TLS", pasv_sock, user->alias);
sockprintf(ctrl_sock,ctrl_sess,"425 Error negotiating TLS");
if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*inprogress=FALSE;
return;
}
}
}
do {
l=1;
if(ioctlsocket(*data_sock, FIONBIO, &l)!=0) {
lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d disabling socket blocking"
,ctrl_sock, user->alias, 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 <%s> !DATA MALLOC FAILURE LINE %d",ctrl_sock, user->alias,__LINE__);
sockprintf(ctrl_sock,ctrl_sess,"425 MALLOC FAILURE");
break;
}
memset(xfer,0,sizeof(xfer_t));
xfer->ctrl_sock=ctrl_sock;
xfer->ctrl_sess=ctrl_sess;
xfer->data_sock=data_sock;
xfer->data_sess=data_sess;
xfer->inprogress=inprogress;
xfer->aborted=aborted;
xfer->delfile=delfile;
xfer->tmpfile=tmpfile;
xfer->append=append;
xfer->filepos=filepos;
xfer->credits=credits;
xfer->lastactive=lastactive;
xfer->user=user;
xfer->client=client;
xfer->dir=dir;
xfer->desc=desc;
SAFECOPY(xfer->filename,filename);
(void)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))
(void)ftp_remove(ctrl_sock, __LINE__, filename, user->alias);
*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(in == NULL) {
strcpy(out, "(null)");
return out;
}
if(strchr(in,'.')==NULL)
ch='.';
else
ch='_';
for(i=0;in[i];i++)
if(in[i]<=' ')
out[i]=ch;
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;
}
static int getdir_from_vpath(scfg_t* cfg, const char* vpath, user_t* user, client_t* client, BOOL include_upload_only)
{
int dir = -1;
int lib = -1;
char* filename = NULL;
enum parsed_vpath result = parse_vpath(cfg, vpath, user, client, include_upload_only, &lib, &dir, &filename);
if(result == PARSED_VPATH_DIR || result == PARSED_VPATH_FULL)
return dir;
return -1;
}
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;
SAFECOPY(alias,fullalias);
p = getfname(alias);
if(p) {
if(p != alias)
*(p-1) = 0;
if(*p) {
if(filename == NULL && p != alias) // CWD command and a filename specified
return FALSE;
fname = p;
}
}
SAFEPRINTF(aliasfile,"%sftpalias.cfg",scfg.ctrl_dir);
if((fp=fopen(aliasfile,"r"))==NULL)
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(filename == NULL /* CWD? */ && (*lastchar(p) != '/' || (*fname != 0 && strcmp(fname, alias)))) {
fclose(fp);
return FALSE;
}
if(!strnicmp(p,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
if((dir=getdir_from_vpath(&scfg, p+strlen(BBS_VIRTUAL_PATH), user, client, true))<0) {
lprintf(LOG_WARNING,"0000 <%s> !Invalid virtual path: %s",user->alias, p);
/* 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;
p++;
}
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)
p++;
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]->vdir);
if (strlen(p) < len)
continue;
if (p[len] != 0 && p[len] != '/')
continue;
if(!strnicmp(scfg.lib[lib]->vdir,p,len)) {
p += len;
if (*p)
p++;
break;
}
}
if (lib == scfg.total_libs) {
SAFECOPY(filename, p);
tmp = strchr(filename, '/');
if (tmp != NULL)
*tmp = 0;
if (ftpalias(filename, filename, user, client, &dir) == TRUE && dir >= 0) {
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);
}
}
}
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]->vdir);
if (strlen(p) < len)
continue;
if (p[len] != 0 && p[len] != '/')
continue;
if(!strnicmp(scfg.dir[dir]->vdir,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];
SAFECOPY(root,path);
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]->vdir);
strcat(str,"/");
if(dir<0)
return(str);
strcat(str,scfg.dir[dir]->vdir);
strcat(str,"/");
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)
sockprintf(sock,sess,"%u-%s",code,buf);
else
sockprintf(sock,sess," %s",buf);
i++;
}
fclose(fp);
}
}
static BOOL ftp_hacklog(char* prot, char* user, char* text, char* host, union xp_sockaddr* addr)
{
#ifdef _WIN32
if(startup->sound.hack[0] && !sound_muted(&scfg))
PlaySound(startup->sound.hack, NULL, SND_ASYNC|SND_FILENAME);
#endif
return hacklog(&scfg, &mqtt, 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, client_t* client, union xp_sockaddr* addr)
{
ulong count;
if(addr!=NULL) {
count=loginFailure(startup->login_attempt_list, addr, client->protocol, user, passwd);
mqtt_user_login_fail(&mqtt, client, user);
if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold)
ftp_hacklog("FTP LOGIN", user, passwd, client->host, addr);
if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold) {
char reason[128];
SAFEPRINTF(reason, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS (%lu)", count);
filter_ip(&scfg, client->protocol, reason, client->host, client->addr, user, /* fname: */NULL);
}
if(count > *login_attempts)
*login_attempts=count;
} else
(*login_attempts)++;
mswait(startup->login_attempt.delay); /* As recommended by RFC2577 */
if((*login_attempts)>=3) {
sockprintf(sock,sess,"421 Too many failed login attempts.");
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(),(ulong)clock(),ext);
return(fname);
}
#if defined(__GNUC__) // Catch printf-format errors
static BOOL send_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, const char *format, ...) __attribute__ ((format (printf, 4, 5)));
#endif
static BOOL send_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, const char *format, ...)
{
va_list va;
char *str;
int rval;
if (fp == NULL && sock == INVALID_SOCKET)
return FALSE;
va_start(va, format);
rval = vasprintf(&str, format, va);
va_end(va);
if (rval == -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(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;
struct tm t;
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);
if (modify != 0 && (feats & MLSX_MODIFY)) {
t = *gmtime(&modify);
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(&ul);
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)
{
const char *type;
char permstr[11];
char *p;
BOOL is_file = FALSE;
struct stat st;
if(stat(path, &st) != 0)
return FALSE;
if (!strcmp(path, "."))
type="cdir";
else if (!strcmp(path, ".."))
type="pdir";
else if (*lastchar(path) == '/') /* is directory */
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)st.st_size, st.st_mtime, NULL, NULL, st.st_ctime, full_path ? path : getfname(path));
}
/*
* 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, lib->ul_ar, user, client))
return FALSE;
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) || !chk_ar(&scfg, lib->ul_ar, user, client))
return FALSE;
}
if (file->from == NULL || stricmp(file->from, user->alias) != 0)
return FALSE;
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;
return TRUE;
}
static BOOL can_download(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
return can_user_download(&scfg, dir->dirnum, user, client, /* reason */NULL);
}
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;
}
static void get_owner_name(file_t *file, char *namestr)
{
char *p;
if (file) {
if (file->hdr.attr & MSG_ANONYMOUS)
strcpy(namestr, ANONYMOUS);
else
strcpy(namestr, file->from);
}
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 password[64];
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]="";
WORD port;
uint32_t ip_addr;
socklen_t addr_len;
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;
uint64_t avail; /* disk space */
ulong count;
BOOL detail;
BOOL success;
BOOL getdate;
BOOL getsize;
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 append;
BOOL reuseaddr;
FILE* fp;
FILE* alias_fp;
SOCKET sock;
SOCKET tmp_sock;
SOCKET pasv_sock=INVALID_SOCKET;
CRYPT_SESSION pasv_sess=-1;
SOCKET data_sock=INVALID_SOCKET;
CRYPT_SESSION data_sess=-1;
HOSTENT* host;
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 lastactive;
time_t file_date;
off_t file_size;
node_t node;
client_t client;
struct tm tm;
struct tm cur_tm;
login_attempt_t attempted;
CRYPT_SESSION sess = -1;
BOOL got_pbsz = FALSE;
BOOL protection = FALSE;
SetThreadName("sbbs/ftpControl");
thread_up(TRUE /* setuid */);
lastactive=time(NULL);
sock=ftp.socket;
memcpy(&data_addr, &ftp.client_addr, ftp.client_addr_len);
/* Default data port is ctrl port-1 */
data_port = inet_addrport(&data_addr)-1;
lprintf(LOG_DEBUG,"%04d CTRL thread started", sock);
free(arg);
#ifdef _WIN32
if(startup->sound.answer[0] && !sound_muted(&scfg))
PlaySound(startup->sound.answer, NULL, SND_ASYNC|SND_FILENAME);
#endif
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"
,sock, i, ERROR_VALUE);
sockprintf(sock,sess,"425 Error %d disabling socket blocking"
,ERROR_VALUE);
ftp_close_socket(&sock,&sess,__LINE__);
thread_down();
return;
}
memset(&user,0,sizeof(user));
inet_addrtop(&ftp.client_addr, host_ip, sizeof(host_ip));
lprintf(LOG_INFO,"%04d CTRL connection accepted from: %s port %u"
,sock, host_ip, inet_addrport(&ftp.client_addr));
SAFECOPY(host_name, STR_NO_HOSTNAME);
if(!(startup->options&FTP_OPT_NO_HOST_LOOKUP)) {
getnameinfo(&ftp.client_addr.addr, sizeof(ftp.client_addr), host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD);
lprintf(LOG_INFO,"%04d Hostname: %s [%s]", sock, host_name, host_ip);
}
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 (%lu 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);
if((result=getsockname(sock, &pasv_addr.addr,&addr_len))!=0) {
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;
}
(void)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.port=inet_addrport(&ftp.client_addr);
client.protocol="FTP";
SAFECOPY(client.user, STR_UNKNOWN_USER);
client.usernum = 0;
client_on(sock,&client,FALSE /* update */);
if(startup->login_attempt.throttle
&& (login_attempts=loginAttempts(startup->login_attempt_list, &ftp.client_addr)) > 1) {
lprintf(LOG_DEBUG,"%04d Throttling suspicious connection from: %s (%lu login attempts)"
,sock, host_ip, login_attempts);
mswait(login_attempts*startup->login_attempt.throttle);
}
sockprintf(sock,sess,"220-%s (%s)",scfg.sys_name, server_host_name());
sockprintf(sock,sess," Synchronet FTP Server %s%c-%s Ready"
,VERSION, REVISION, PLATFORM_DESC);
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);
sockprintf(sock,sess," %s",buf);
}
fclose(fp);
}
sockprintf(sock,sess,"220 Please enter your user name.");
#ifdef SOCKET_DEBUG_CTRL
socket_debug[sock]|=SOCKET_DEBUG_CTRL;
#endif
while(1) {
#ifdef SOCKET_DEBUG_READLINE
socket_debug[sock]|=SOCKET_DEBUG_READLINE;
#endif
rd = sockreadline(sock, sess, buf, sizeof(buf), &lastactive);
#ifdef SOCKET_DEBUG_READLINE
socket_debug[sock]&=~SOCKET_DEBUG_READLINE;
#endif
if(rd<1) {
if(transfer_inprogress==TRUE) {
lprintf(LOG_WARNING,"%04d <%s> !Aborting transfer due to CTRL socket receive error", sock, user.number ? user.alias : host_ip);
transfer_aborted=TRUE;
}
break;
}
truncsp(buf);
lastactive=time(NULL);
cmd=buf;
while(((BYTE)*cmd)==TELNET_IAC) {
cmd++;
lprintf(LOG_DEBUG,"%04d <%s> RX%s: Telnet cmd: %s", sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", telnet_cmd_desc(*cmd));
cmd++;
}
while(*cmd && *cmd<' ') {
lprintf(LOG_DEBUG,"%04d <%s> RX%s: %d (0x%02X)",sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", (BYTE)*cmd,(BYTE)*cmd);
cmd++;
}
if(!(*cmd))
continue;
if(startup->options&FTP_OPT_DEBUG_RX)
lprintf(LOG_DEBUG,"%04d <%s> RX%s: %s", sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", cmd);
if(!stricmp(cmd, "NOOP")) {
sockprintf(sock,sess,"200 NOOP command successful.");
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)
sockprintf(sock,sess,
" RECYCLE [ALL]");
if(sysop)
sockprintf(sock,sess,
" EXEC <cmd>");
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;Unique%s;Create%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) ? "*" : ""
);
sockprintf(sock,sess," TVFS");
sockprintf(sock,sess,"211 End");
continue;
}
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%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;" : ""
);
continue;
}
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);
truncsp(p);
SAFECOPY(user.alias,p);
user.number = find_login_id(&scfg, user.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.");
else
sockprintf(sock,sess,"331 User name okay, need password.");
user.number=0;
continue;
}
if(!strnicmp(cmd, "PASS ",5) && user.alias[0]) {
user.number=0;
p=cmd+5;
SKIP_WHITESPACE(p);
SAFECOPY(password,p);
user.number = find_login_id(&scfg, user.alias);
if(!user.number) {
if(scfg.sys_misc&SM_ECHO_PW)
lprintf(LOG_WARNING,"%04d !UNKNOWN USER: '%s' (password: %s)",sock,user.alias,p);
else
lprintf(LOG_WARNING,"%04d !UNKNOWN USER: '%s'",sock,user.alias);
if(badlogin(sock, sess, &login_attempts, user.alias, p, &client, &ftp.client_addr))
break;
continue;
}
if((i=getuserdat(&scfg, &user))!=0) {
lprintf(LOG_ERR,"%04d <%s> !ERROR %d getting data for user #%d"
,sock, user.alias, i, user.number);
sockprintf(sock,sess,"530 Database error %d",i);
user.number=0;
continue;
}
if(user.misc&(DELETED|INACTIVE)) {
lprintf(LOG_WARNING,"%04d <%s> !DELETED or INACTIVE user #%d"
,sock,user.alias,user.number);
user.number=0;
if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
break;
continue;
}
if(user.rest&FLAG('T')) {
lprintf(LOG_WARNING,"%04d <%s> !T RESTRICTED user #%d"
,sock,user.alias,user.number);
user.number=0;
if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
break;
continue;
}
if(user.ltoday>=scfg.level_callsperday[user.level]
&& !(user.exempt&FLAG('L'))) {
lprintf(LOG_WARNING,"%04d <%s> !MAXIMUM LOGONS (%d) reached for level %u"
,sock,user.alias,scfg.level_callsperday[user.level], user.level);
sockprintf(sock,sess,"530 Maximum logons per day reached.");
user.number=0;
continue;
}
if(user.rest&FLAG('L') && user.ltoday>=1) {
lprintf(LOG_WARNING,"%04d <%s> !L RESTRICTED user already on today"
,sock,user.alias);
sockprintf(sock,sess,"530 Maximum logons per day reached.");
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 <%s> !BLOCKED e-mail address: %s", sock, user.alias, password);
user.number=0;
if(badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
break;
continue;
}
lprintf(LOG_INFO,"%04d <%s> identity: %s",sock,user.alias,password);
putuserstr(&scfg, user.number, USER_NETMAIL, password);
}
else if(user.level>=SYSOP_LEVEL && !stricmp(password,sys_pass)) {
if(scfg.sys_misc&SM_R_SYSOP) {
lprintf(LOG_INFO,"%04d <%s> Sysop access granted", sock, user.alias);
sysop=TRUE;
} else
lprintf(LOG_NOTICE, "%04d <%s> Remote sysop access disabled", sock, user.alias);
}
else if(stricmp(password,user.pass)) {
if(scfg.sys_misc&SM_ECHO_PW)
lprintf(LOG_WARNING,"%04d <%s> !FAILED Password attempt: '%s' expected '%s'"
,sock, user.alias, password, user.pass);
else
lprintf(LOG_WARNING,"%04d <%s> !FAILED Password attempt"
,sock, user.alias);
user.number=0;
if(badlogin(sock, sess, &login_attempts, user.alias, password, &client, &ftp.client_addr))
break;
continue;
}
/* Update client display */
if(user.pass[0]) {
SAFECOPY(client.user, user.alias);
loginSuccess(startup->login_attempt_list, &ftp.client_addr);
} else { /* anonymous */
SAFEPRINTF2(client.user, "%s <%.32s>", user.alias, password);
}
client.usernum = user.number;
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);
logintime=time(NULL);
timeleft=(long)gettimeleft(&scfg,&user,logintime);
ftp_printfile(sock,sess,"hello",230);
if(sysop)
sockprintf(sock,sess,"230-Sysop access granted.");
sockprintf(sock,sess,"230-%s logged in.",user.alias);
if(!(user.exempt&FLAG('D')) && user_available_credits(&user)>0)
sockprintf(sock,sess,"230-You have %" PRIu64 " download credits."
,user_available_credits(&user));
sockprintf(sock,sess,"230 You are allowed %lu minutes of use for this session."
,timeleft/60);
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);
SAFECOPY(user.ipaddr,host_ip);
user.logontime=(time32_t)logintime;
putuserdat(&scfg, &user);
mqtt_user_login(&mqtt, &client);
#ifdef _WIN32
if(startup->sound.login[0] && !sound_muted(&scfg))
PlaySound(startup->sound.login, NULL, SND_ASYNC|SND_FILENAME);
#endif
continue;
}
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) || sess == -1) {
lprintf(LOG_WARNING, "%04d <%s> failed to initialize TLS successfully", sock, host_ip);
break;
}
user.number=0;
sysop=FALSE;
filepos=0;
got_pbsz = FALSE;
protection = FALSE;
lprintf(LOG_INFO, "%04d <%s> initialized TLS successfully", sock, host_ip);
client.protocol = "FTPS";
client_on(sock, &client, /* update: */TRUE);
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;
}
if(!user.number) {
sockprintf(sock,sess,"530 Please login with USER and PASS.");
continue;
}
if(!(user.rest&FLAG('G')))
getuserdat(&scfg, &user); /* get current user data */
if((timeleft=(long)gettimeleft(&scfg,&user,logintime))<1L) {
sockprintf(sock,sess,"421 Sorry, you've run out of time.");
lprintf(LOG_WARNING,"%04d <%s> Out of time, disconnecting",sock, user.alias);
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")) {
sockprintf(sock,sess,"211-Active Telnet Nodes:");
for(i=0;i<scfg.sys_nodes && i<scfg.sys_lastnode;i++) {
if((result=getnodedat(&scfg, i+1, &node, FALSE, NULL))!=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")) {
sockprintf(sock,sess,"211 %s",ftp_ver());
continue;
}
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;
sockprintf(sock,sess,"200-%s",str);
}
sockprintf(sock,sess,"200 %s returned %d",p,pclose(fp));
}
#else
sockprintf(sock,sess,"200 system(%s) returned %d",p,system(p));
#endif
continue;
}
#ifdef SOCKET_DEBUG_CTRL
if(!stricmp(cmd, "SITE DEBUG")) {
sockprintf(sock,sess,"211-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;
}
#endif
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__);
}
memcpy(&data_addr, &ftp.client_addr, ftp.client_addr_len);
p=cmd+5;
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);
data_port = (p1<<8)|p2;
} else if(strnicmp(cmd, "EPRT ", 5)==0) { /* EPRT */
char delim = *p;
int prot;
char addr_str[INET6_ADDRSTRLEN];
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;
FIND_CHAR(p,delim);
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 <%s> !Unable to parse IPv6 address: %s",sock, user.alias, 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 <%s> !UNSUPPORTED protocol: %d", sock, user.alias, 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, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, p);
sockprintf(sock,sess, "521 Address family not supported");
continue;
}
FIND_CHAR(p,',');
if(*p)
p++;
FIND_CHAR(p,',');
if(*p)
p++;
switch(h1) {
case 4: /* IPv4 */
if(h2 != 4) {
lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, 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, "%04d <%s> !Unable to parse LPRT %s", sock, user.alias, p);
sockprintf(sock,sess, "501 IPv4 Port is the wrong length");
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, "%04d <%s> !Unable to parse LPRT: %s",sock, user.alias, 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, "%04d <%s> !Unable to parse LPRT: %s",sock, user.alias, p);
sockprintf(sock,sess, "501 IPv6 Port is the wrong length");
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, "%04d <%s> !Unable to parse LPRT: %s",sock, user.alias, p);
sockprintf(sock,sess, "521 Address family not supported");
continue;
}
}
inet_addrtop(&data_addr, data_ip, sizeof(data_ip));
bool bounce_allowed = (startup->options & FTP_OPT_ALLOW_BOUNCE) && !(user.rest & FLAG('G'));
if(data_port < IPPORT_RESERVED
|| (memcmp(&data_addr, &ftp.client_addr, ftp.client_addr_len) != 0 && !bounce_allowed)) {
lprintf(LOG_WARNING,"%04d <%s> !SUSPECTED BOUNCE ATTACK ATTEMPT to %s port %u"
,sock,user.alias
,data_ip,data_port);
ftp_hacklog("FTP BOUNCE", user.alias, cmd, host_name, &ftp.client_addr);
sockprintf(sock,sess,"504 Bad port number.");
continue; /* As recommended by RFC2577 */
}
inet_setaddrport(&data_addr, data_port);
sockprintf(sock,sess,"200 PORT Command successful.");
mode="active";
continue;
}
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 <%s> !PASV ERROR %d opening socket", sock, user.alias, ERROR_VALUE);
sockprintf(sock,sess,"425 Error %d opening PASV data socket", ERROR_VALUE);
continue;
}
reuseaddr=FALSE;
if((result=setsockopt(pasv_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr)))!=0) {
lprintf(LOG_WARNING,"%04d <%s> !PASV ERROR %d disabling REUSEADDR socket option"
,sock, user.alias, 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 <%s> PASV DATA socket %d opened",sock, user.alias, 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 <%s> PASV DATA trying to bind socket to port %u"
,sock, user.alias, port);
inet_setaddrport(&pasv_addr, port);
if((result=bind(pasv_sock, &pasv_addr.addr,xp_sockaddr_len(&pasv_addr)))==0)
break;
if(port==startup->pasv_port_high)
break;
}
if(result!= 0) {
lprintf(LOG_ERR,"%04d <%s> !PASV ERROR %d (%d) binding socket to port %u"
,sock, user.alias, result, ERROR_VALUE, port);
sockprintf(sock,sess,"425 Error %d binding data socket",ERROR_VALUE);
ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
continue;
}
if(startup->options&FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG,"%04d <%s> PASV DATA socket %d bound to port %u",sock, user.alias, pasv_sock, port);
addr_len=sizeof(addr);
if((result=getsockname(pasv_sock, &addr.addr,&addr_len))!=0) {
lprintf(LOG_ERR,"%04d <%s> !PASV ERROR %d (%d) getting address/port"
,sock, user.alias, 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 <%s> !PASV ERROR %d (%d) listening on port %u"
,sock, user.alias, result, ERROR_VALUE,port);
sockprintf(sock,sess,"425 Error %d listening on data socket",ERROR_VALUE);
ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
continue;
}
port=inet_addrport(&addr);
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)"
,((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(server_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 <%s> PASV DATA IP address in response: %u.%u.%u.%u (subject to NAT)"
,sock
,user.alias
,(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
,(ushort)((port>>8)&0xff)
,(ushort)(port&0xff)
);
}
mode="passive";
continue;
}
if(!strnicmp(cmd, "TYPE ",5)) {
sockprintf(sock,sess,"200 All files sent in BINARY mode.");
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);
else
avail=getfreediskspace(scfg.data_dir,0); /* Change to temp_dir? */
if(l && l>avail)
sockprintf(sock,sess,"504 Only %" PRIu64 " bytes available.",avail);
else
sockprintf(sock,sess,"200 %" PRIu64 " bytes available.",avail);
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 %"PRIdOFF". Send STORE or RETRIEVE to initiate transfer."
,filepos);
continue;
}
if(!strnicmp(cmd, "MODE ",5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
if(toupper(*p)!='S')
sockprintf(sock,sess,"504 Only STREAM mode supported.");
else
sockprintf(sock,sess,"200 STREAM mode.");
continue;
}
if(!strnicmp(cmd, "STRU ",5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
if(toupper(*p)!='F')
sockprintf(sock,sess,"504 Only FILE structure supported.");
else
sockprintf(sock,sess,"200 FILE structure.");
continue;
}
if(!stricmp(cmd, "SYST")) {
sockprintf(sock,sess,"215 UNIX Type: L8");
continue;
}
if(!stricmp(cmd, "ABOR")) {
if(!transfer_inprogress)
sockprintf(sock,sess,"226 No transfer in progress.");
else {
lprintf(LOG_WARNING,"%04d <%s> aborting transfer"
,sock,user.alias);
transfer_aborted=TRUE;
YIELD(); /* give send thread time to abort */
sockprintf(sock,sess,"226 Transfer aborted.");
}
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)) {
sockprintf(sock,sess,"550 Directory does not exist.");
lprintf(LOG_WARNING,"%04d <%s> !attempted to mount invalid directory: '%s'"
,sock, user.alias, p);
continue;
}
local_fsys=TRUE;
SAFECOPY(local_dir,p);
}
sockprintf(sock,sess,"250 %s file system mounted."
,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,"/");
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 <%s> !ERROR %d (%s) line %d opening %s"
,sock, user.alias, errno, strerror(errno), __LINE__, 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: local %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 {
time_t start = time(NULL);
glob_t g;
glob(path, GLOB_MARK, NULL, &g);
for(i=0;i<(int)g.gl_pathc;i++) {
char fpath[MAX_PATH + 1];
SAFECOPY(fpath, g.gl_pathv[i]);
if(*lastchar(fpath) == '/')
*lastchar(fpath) = 0;
write_local_mlsx(fp, INVALID_SOCKET, -1, mlsx_feats, fpath, FALSE);
}
lprintf(LOG_INFO, "%04d <%s> %s-listing (%ld bytes) of local %s (%lu files) created in %ld seconds"
,sock, user.alias, cmd, ftell(fp), path
,(ulong)g.gl_pathc, (long)time(NULL) - start);
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);
}
continue;
}
}
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 <%s> !ERROR %d (%s) line %d opening %s"
,sock, user.alias, errno, strerror(errno), __LINE__, fname);
sockprintf(sock,sess, "451 Insufficient system storage");
continue;
}
p=cmd+4;
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> %slisting: local %s in %s mode"
,sock, user.alias, detail ? "detailed ":"", 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));
time_t start = time(NULL);
glob_t g;
glob(path, GLOB_MARK, NULL, &g);
for(i=0;i<(int)g.gl_pathc;i++) {
char fpath[MAX_PATH + 1];
SAFECOPY(fpath, g.gl_pathv[i]);
if(*lastchar(fpath) == '/')
*lastchar(fpath) = 0;
if(detail) {
struct stat st;
if(stat(fpath, &st) != 0)
continue;
if(localtime_r(&st.st_mtime,&tm)==NULL)
memset(&tm,0,sizeof(tm));
fprintf(fp,"%crw-r--r-- 1 %-8s local %9"PRId64" %s %2d "
,*lastchar(g.gl_pathv[i]) == '/' ? 'd':'-'
,scfg.sys_id
,(int64_t)st.st_size
,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
,getfname(fpath));
else
fprintf(fp,"%5d %s\r\n"
,1900+tm.tm_year
,getfname(fpath));
} else
fprintf(fp,"%s\r\n", getfname(fpath));
}
lprintf(LOG_INFO, "%04d <%s> %slisting (%ld bytes) of local %s (%lu files) created in %ld seconds"
,sock, user.alias, detail ? "detailed ":"", ftell(fp), path
,(ulong)g.gl_pathc, (long)time(NULL) - start);
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);
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))
SAFECOPY(path,p);
else if(*p=='/' || *p=='\\') {
SAFEPRINTF2(path,"%s%s",root_dir(local_dir),p+1);
p = FULLPATH(NULL, path, 0);
SAFECOPY(path, p);
free(p);
}
else {
SAFEPRINTF2(fname,"%s%s",local_dir,p);
FULLPATH(path,fname,sizeof(path));
}
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 {
SAFECOPY(local_dir,path);
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)
sockprintf(sock,sess,"550 Directory does not exist.");
else
sockprintf(sock,sess,"200 CDUP command successful.");
continue;
}
if(!stricmp(cmd, "PWD") || !stricmp(cmd,"XPWD")) {
if(strlen(local_dir)>3)
local_dir[strlen(local_dir)-1]=0; /* truncate '/' */
sockprintf(sock,sess,"257 \"%s\" is current directory."
,local_dir);
continue;
} /* Local PWD */
if(!strnicmp(cmd, "MKD ", 4) || !strnicmp(cmd,"XMKD",4)) {
p=cmd+4;
SKIP_WHITESPACE(p);
if(*p=='/') /* absolute */
SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
else /* relative */
SAFEPRINTF2(fname,"%s%s",local_dir,p);
if(MKDIR(fname) == 0) {
sockprintf(sock,sess,"257 \"%s\" directory created",fname);
lprintf(LOG_NOTICE,"%04d <%s> created directory: %s",sock,user.alias,fname);
} else {
sockprintf(sock,sess,"521 Error %d creating directory: %s",errno,fname);
lprintf(LOG_WARNING,"%04d <%s> !ERROR %d (%s) attempting to create directory: %s"
,sock,user.alias,errno,strerror(errno),fname);
}
continue;
}
if(!strnicmp(cmd, "RMD ", 4) || !strnicmp(cmd,"XRMD",4)) {
p=cmd+4;
SKIP_WHITESPACE(p);
if(*p=='/') /* absolute */
SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
else /* relative */
SAFEPRINTF2(fname,"%s%s",local_dir,p);
if(rmdir(fname) == 0) {
sockprintf(sock,sess,"250 \"%s\" directory removed",fname);
lprintf(LOG_NOTICE,"%04d <%s> removed directory: %s",sock,user.alias,fname);
} else {
sockprintf(sock,sess,"450 Error %d removing directory: %s", errno, fname);
lprintf(LOG_WARNING,"%04d <%s> !ERROR %d (%s) removing directory: %s"
,sock, user.alias, errno, strerror(errno), fname);
}
continue;
}
if(!strnicmp(cmd, "RNFR ",5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
if(*p=='/') /* absolute */
SAFEPRINTF2(ren_from,"%s%s",root_dir(local_dir),p+1);
else /* relative */
SAFEPRINTF2(ren_from,"%s%s",local_dir,p);
if(!fexist(ren_from)) {
sockprintf(sock,sess,"550 File not found: %s",ren_from);
lprintf(LOG_WARNING,"%04d <%s> !ERROR renaming %s (not found)"
,sock,user.alias,ren_from);
} else
sockprintf(sock,sess,"350 File exists, ready for destination name");
continue;
}
if(!strnicmp(cmd, "RNTO ",5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
if(*p=='/') /* absolute */
SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
else /* relative */
SAFEPRINTF2(fname,"%s%s",local_dir,p);
if(rename(ren_from, fname) == 0) {
sockprintf(sock,sess,"250 \"%s\" renamed to \"%s\"",ren_from,fname);
lprintf(LOG_NOTICE,"%04d <%s> renamed %s to %s",sock,user.alias,ren_from,fname);
} else {
sockprintf(sock,sess,"450 Error %d renaming file: %s", errno, ren_from);
lprintf(LOG_WARNING,"%04d <%s> !ERRROR %d (%s) renaming file: %s"
,sock, user.alias, errno, strerror(errno), ren_from);
}
continue;
}
if(!strnicmp(cmd, "RETR ", 5) || !strnicmp(cmd,"SIZE ",5)
|| !strnicmp(cmd, "MDTM ",5) || !strnicmp(cmd, "DELE ",5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
if(!strnicmp(p,LOCAL_FSYS_DIR,strlen(LOCAL_FSYS_DIR)))
p+=strlen(LOCAL_FSYS_DIR); /* already mounted */
if(p[1]==':') /* drive specified */
SAFECOPY(fname,p);
else if(*p=='/') /* absolute, current drive */
SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
else /* relative */
SAFEPRINTF2(fname,"%s%s",local_dir,p);
if(!fexist(fname)) {
lprintf(LOG_WARNING,"%04d <%s> !File not found: %s",sock,user.alias,fname);
sockprintf(sock,sess,"550 File not found: %s",fname);
continue;
}
if(!strnicmp(cmd,"SIZE ",5)) {
sockprintf(sock,sess,"213 %"PRIuOFF,flength(fname));
continue;
}
if(!strnicmp(cmd,"MDTM ",5)) {
t=fdate(fname);
if(gmtime_r(&t,&tm)==NULL) /* specifically use GMT/UTC representation */
memset(&tm,0,sizeof(tm));
sockprintf(sock,sess,"213 %u%02u%02u%02u%02u%02u"
,1900+tm.tm_year,tm.tm_mon+1,tm.tm_mday
,tm.tm_hour,tm.tm_min,tm.tm_sec);
continue;
}
if(!strnicmp(cmd,"DELE ",5)) {
if(ftp_remove(sock, __LINE__, fname, user.alias) == 0) {
sockprintf(sock,sess,"250 \"%s\" removed successfully.",fname);
lprintf(LOG_NOTICE,"%04d <%s> deleted file: %s",sock,user.alias,fname);
} else {
sockprintf(sock,sess,"450 Error %d removing file: %s", errno, fname);
lprintf(LOG_WARNING,"%04d <%s> !ERROR %d (%s) deleting file: %s"
,sock, user.alias, errno, strerror(errno), fname);
}
continue;
}
/* RETR */
lprintf(LOG_INFO,"%04d <%s> downloading: %s (%"PRIuOFF" bytes) in %s mode"
,sock,user.alias,fname,flength(fname)
,mode);
sockprintf(sock,sess,"150 Opening BINARY mode data connection for file transfer.");
filexfer(&data_addr,sock,sess,pasv_sock,pasv_sess,&data_sock,&data_sess,fname,filepos
,&transfer_inprogress,&transfer_aborted,FALSE,FALSE
,&lastactive,&user,&client,-1,FALSE,FALSE,FALSE,NULL,protection);
continue;
} /* Local RETR/SIZE/MDTM */
if(!strnicmp(cmd, "STOR ", 5) || !strnicmp(cmd, "APPE ", 5)) {
p=cmd+5;
SKIP_WHITESPACE(p);
if(!strnicmp(p,LOCAL_FSYS_DIR,strlen(LOCAL_FSYS_DIR)))
p+=strlen(LOCAL_FSYS_DIR); /* already mounted */
if(p[1]==':') /* drive specified */
SAFECOPY(fname,p);
else if(*p=='/') /* absolute, current drive */
SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
else /* relative */
SAFEPRINTF2(fname,"%s%s",local_dir,p);
lprintf(LOG_INFO,"%04d <%s> uploading: %s in %s mode", sock,user.alias,fname
,mode);
sockprintf(sock,sess,"150 Opening BINARY mode data connection for file transfer.");
filexfer(&data_addr,sock,sess,pasv_sock,pasv_sess,&data_sock,&data_sess,fname,filepos
,&transfer_inprogress,&transfer_aborted,FALSE,FALSE
,&lastactive
,&user
,&client
,-1 /* dir */
,TRUE /* uploading */
,FALSE /* credits */
,!strnicmp(cmd,"APPE",4) ? TRUE : FALSE /* append */
,NULL /* desc */
,protection
);
filepos=0;
continue;
} /* Local STOR */
}
if (!strnicmp(cmd, "MLS", 3)) {
if (cmd[3] == 'D' || cmd[3] == 'T') {
dir=curdir;
lib=curlib;
l = 0;
if(cmd[4]!=0)
lprintf(LOG_DEBUG,"%04d <%s> MLSx: %s",sock, user.alias, cmd);
/* path specified? */
p=cmd+4;
if (*p == ' ')
p++;
if (parsepath(&p,&user,&client,&lib,&dir) == -1) {
sockprintf(sock,sess, "550 No such path");
continue;
}
if (strchr(p, '/')) {
sockprintf(sock,sess, "550 No such path");
continue;
}
if (cmd[3] == 'T') {
if (cmd[4])
strcpy(mls_path, cmd+5);
else
strcpy(mls_path, p);
}
else {
if (*p) {
sockprintf(sock,sess, "501 Not a directory");
continue;
}
strcpy(mls_path, p);
}
mls_fname = p;
fp = NULL;
if (cmd[3] == 'D') {
if((fp=fopen(ftp_tmpfname(fname,"lst",sock),"w+b"))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) line %d opening %s"
,sock, user.alias, errno, strerror(errno), __LINE__, fname);
sockprintf(sock,sess, "451 Insufficient system storage");
continue;
}
sockprintf(sock,sess,"150 Opening ASCII mode data connection for MLSD.");
}
now=time(NULL);
if(localtime_r(&now,&cur_tm)==NULL)
memset(&cur_tm,0,sizeof(cur_tm));
/* ASCII Index File */
if(startup->options&FTP_OPT_INDEX_FILE && startup->index_file_name[0]
&& (cmd[3] == 'D' || strcmp(startup->index_file_name, mls_fname) == 0)) {
if (cmd[3] == 'T')
sockprintf(sock,sess, "250- Listing %s", startup->index_file_name);
get_owner_name(NULL, str);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", "r", UINT64_MAX, 0, str, NULL, 0, cmd[3] == 'T' ? mls_path : startup->index_file_name);
l++;
}
if(lib<0) { /* Root dir */
if (cmd[3] == 'T' && !*mls_fname) {
sockprintf(sock,sess, "250- Listing root");
get_owner_name(NULL, str);
strcpy(aliaspath, "/");
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", (startup->options&FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, aliaspath);
l++;
}
else {
send_mlsx_entry(fp, sock, sess, mlsx_feats, "cdir", (startup->options&FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, "/");
}
lprintf(LOG_INFO,"%04d <%s> %s listing: root in %s mode", sock, user.alias, cmd, mode);
/* QWK Packet */
if(startup->options&FTP_OPT_ALLOW_QWK) {
SAFEPRINTF(str,"%s.qwk",scfg.sys_id);
if (cmd[3] == 'D' || strcmp(str, mls_fname) == 0) {
if (cmd[3] == 'T')
sockprintf(sock,sess, "250- Listing %s", str);
get_owner_name(NULL, str);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", "r", UINT64_MAX, 0, str, NULL, 0, cmd[3] == 'T' ? mls_path : str);
l++;
}
}
/* File Aliases */
sprintf(aliasfile,"%sftpalias.cfg",scfg.ctrl_dir);
if((alias_fp=fopen(aliasfile,"r"))!=NULL) {
while(!feof(alias_fp)) {
if(!fgets(aliasline,sizeof(aliasline),alias_fp))
break;
alias_dir=FALSE;
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;
/* Virtual Path? */
aliaspath[0]=0;
if(!strnicmp(np,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
if((dir=getdir_from_vpath(&scfg, np+strlen(BBS_VIRTUAL_PATH), &user, &client, true))<0) {
lprintf(LOG_WARNING,"%04d <%s> !Invalid virtual path:%s",sock,user.alias,np);
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;
SAFEPRINTF3(aliaspath,"/%s/%s/%s", scfg.lib[scfg.dir[dir]->lib]->vdir, scfg.dir[dir]->vdir, tp);
}
else {
alias_dir=TRUE;
SAFEPRINTF2(aliaspath,"/%s/%s", scfg.lib[scfg.dir[dir]->lib]->vdir, scfg.dir[dir]->vdir);
}
}
if(!alias_dir && !fexist(np)) {
lprintf(LOG_WARNING,"%04d <%s> !Missing aliased file: %s",sock, user.alias, np);
continue;
}
}
fclose(alias_fp);
}
/* Library folders */
for(i=0;i<scfg.total_libs;i++) {
if(!chk_ar(&scfg,scfg.lib[i]->ar,&user,&client))
continue;
if (cmd[3] != 'D' && strcmp(scfg.lib[i]->vdir, mls_fname) != 0)
continue;
if (cmd[3] == 'T')
sockprintf(sock,sess, "250- Listing %s", scfg.lib[i]->vdir);
get_libperm(scfg.lib[i], &user, &client, permstr);
get_owner_name(NULL, str);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", permstr, UINT64_MAX, 0, str, NULL, 0, cmd[3] == 'T' ? mls_path : scfg.lib[i]->vdir);
l++;
}
} else if(dir<0) {
if (cmd[3] == 'T' && !*mls_fname) {
sockprintf(sock,sess, "250- Listing %s", scfg.lib[lib]->vdir);
get_owner_name(NULL, str);
SAFEPRINTF(aliaspath, "/%s", scfg.lib[lib]->vdir);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", "el", UINT64_MAX, 0, str, NULL, 0, aliaspath);
l++;
}
if (cmd[3] == 'D') {
get_owner_name(NULL, str);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "pdir", (startup->options&FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, "/");
SAFEPRINTF(aliaspath, "/%s", scfg.lib[lib]->vdir);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "cdir", (startup->options&FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, aliaspath);
}
lprintf(LOG_INFO,"%04d <%s> %s listing: %s library in %s mode"
,sock, user.alias, cmd, scfg.lib[lib]->vdir, mode);
for(i=0;i<scfg.total_dirs;i++) {
if(scfg.dir[i]->lib!=lib)
continue;
if(i!=(int)scfg.sysop_dir && i!=(int)scfg.upload_dir
&& !chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
continue;
if (cmd[3] != 'D' && strcmp(scfg.dir[i]->vdir, mls_fname) != 0)
continue;
if (cmd[3] == 'T')
sockprintf(sock,sess, "250- Listing %s", scfg.dir[i]->vdir);
get_dirperm(scfg.lib[lib], scfg.dir[i], &user, &client, permstr);
get_owner_name(NULL, str);
SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->vdir, scfg.dir[i]->vdir);
get_unique(aliaspath, uniq);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", permstr, UINT64_MAX, 0, str, uniq, 0, cmd[3] == 'T' ? mls_path : scfg.dir[i]->vdir);
l++;
}
} else if(chk_ar(&scfg,scfg.dir[dir]->ar,&user,&client)) {
lprintf(LOG_INFO,"%04d <%s> %s listing: /%s/%s directory in %s mode"
,sock, user.alias, cmd, scfg.lib[lib]->vdir, scfg.dir[dir]->vdir,mode);
if (cmd[3] == 'T' && !*mls_fname) {
sockprintf(sock,sess, "250- Listing %s/%s",scfg.lib[lib]->vdir,scfg.dir[dir]->vdir);
get_owner_name(NULL, str);
SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->vdir, scfg.dir[dir]->vdir);
get_unique(aliaspath, uniq);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", (startup->options&FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, uniq, 0, aliaspath);
l++;
}
if (cmd[3] == 'D') {
get_libperm(scfg.lib[lib], &user, &client, permstr);
get_owner_name(NULL, str);
SAFEPRINTF(aliaspath, "/%s", scfg.lib[lib]->vdir);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "pdir", permstr, UINT64_MAX, 0, str, NULL, 0, aliaspath);
SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->vdir, scfg.dir[dir]->vdir);
get_unique(aliaspath, uniq);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "cdir", permstr, UINT64_MAX, 0, str, NULL, 0, aliaspath);
}
smb_t smb;
if((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) {
lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file);
continue;
}
time_t start = time(NULL);
size_t file_count = 0;
file_t* file_list = loadfiles(&smb
,/* filespec */NULL, /* time: */0, file_detail_normal, scfg.dir[dir]->sort, &file_count);
for(size_t i = 0; i < file_count; i++) {
file_t* f = &file_list[i];
if (cmd[3] != 'D' && strcmp(f->name, mls_fname) != 0)
continue;
if (cmd[3] == 'T')
sockprintf(sock,sess, "250- Listing %s", p);
get_fileperm(scfg.lib[lib], scfg.dir[dir], &user, &client, f, permstr);
get_owner_name(f, str);
SAFEPRINTF3(aliaspath, "/%s/%s/%s", scfg.lib[lib]->vdir, scfg.dir[dir]->vdir, f->name);
get_unique(aliaspath, uniq);
f->size = f->cost;
f->time = f->hdr.when_imported.time;
if(scfg.dir[dir]->misc&DIR_FCHK) {
struct stat st;
if(stat(getfilepath(&scfg, f, path), &st) != 0)
continue;
f->size = st.st_size;
f->time = st.st_mtime;
f->hdr.when_imported.time = (uint32_t)st.st_ctime;
}
send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", permstr, f->size, f->time, str, uniq, f->hdr.when_imported.time, cmd[3] == 'T' ? mls_path : f->name);
l++;
}
if (cmd[3] == 'D') {
lprintf(LOG_INFO, "%04d <%s> %s listing (%ld bytes) of /%s/%s (%lu files) created in %ld seconds"
,sock, user.alias, cmd, ftell(fp), scfg.lib[lib]->vdir, scfg.dir[dir]->vdir
,(ulong)file_count, (long)time(NULL) - start);
}
freefiles(file_list, file_count);
smb_close(&smb);
} else
lprintf(LOG_INFO,"%04d <%s> %s listing: /%s/%s directory in %s mode (empty - no access)"
,sock, user.alias, cmd, scfg.lib[lib]->vdir, scfg.dir[dir]->vdir, mode);
if (cmd[3] == 'D') {
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,dir,FALSE,FALSE,FALSE,NULL,protection);
}
else {
if (l==0)
sockprintf(sock,sess, "550 No such path");
else
sockprintf(sock, sess, "250 End");
}
continue;
}
}
if(!strnicmp(cmd, "LIST", 4) || !strnicmp(cmd, "NLST", 4)) {
dir=curdir;
lib=curlib;
if(cmd[4]!=0)
lprintf(LOG_DEBUG,"%04d <%s> LIST/NLST: %s", sock, user.alias, cmd);
/* path specified? */
p=cmd+4;
SKIP_WHITESPACE(p);
if(*p=='-') { /* -Letc */
FIND_WHITESPACE(p);
SKIP_WHITESPACE(p);
}
if((fp=fopen(ftp_tmpfname(fname,"lst",sock),"w+b"))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) line %d opening %s"
,sock, user.alias, errno, strerror(errno), __LINE__, fname);
sockprintf(sock,sess, "451 Insufficient system storage");
continue;
}
sockprintf(sock,sess,"150 Opening ASCII mode data connection for /bin/ls.");
if (parsepath(&p,&user,&client,&lib,&dir) == -1) {
/* Empty list */
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,dir,FALSE,FALSE,FALSE,NULL,protection);
continue;
}
filespec=p;
if(*filespec==0)
filespec="*";
if(!strnicmp(cmd, "LIST", 4))
detail=TRUE;
else
detail=FALSE;
now=time(NULL);
if(localtime_r(&now,&cur_tm)==NULL)
memset(&cur_tm,0,sizeof(cur_tm));
/* ASCII Index File */
if(startup->options&FTP_OPT_INDEX_FILE && startup->index_file_name[0]
&& wildmatchi(startup->index_file_name, filespec, FALSE)) {
if(detail)
fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
,NAME_LEN
,scfg.sys_id
,lib<0 ? scfg.sys_id : dir<0
? scfg.lib[lib]->vdir : scfg.dir[dir]->vdir
,512L
,ftp_mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min
,startup->index_file_name);
else
fprintf(fp,"%s\r\n",startup->index_file_name);
}
if(lib<0) { /* Root dir */
lprintf(LOG_INFO,"%04d <%s> %slisting: root in %s mode", sock, user.alias, detail ? "detailed ":"", mode);
/* QWK Packet */
if(startup->options&FTP_OPT_ALLOW_QWK) {
SAFEPRINTF(str,"%s.qwk",scfg.sys_id);
if(wildmatchi(str, filespec, FALSE)) {
if(detail) {
if(fexistcase(qwkfile)) {
t=fdate(qwkfile);
l=(ulong)flength(qwkfile);
} else {
t=time(NULL);
l=10240;
};
if(localtime_r(&t,&tm)==NULL)
memset(&tm,0,sizeof(tm));
fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
,NAME_LEN
,scfg.sys_id
,scfg.sys_id
,l
,ftp_mon[tm.tm_mon],tm.tm_mday,tm.tm_hour,tm.tm_min
,str);
} else
fprintf(fp,"%s\r\n",str);
}
}
/* File Aliases */
sprintf(aliasfile,"%sftpalias.cfg",scfg.ctrl_dir);
if((alias_fp=fopen(aliasfile,"r"))!=NULL) {
while(!feof(alias_fp)) {
if(!fgets(aliasline,sizeof(aliasline),alias_fp))
break;
alias_dir=FALSE;
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;
if(!wildmatchi(p, filespec, FALSE))
continue;
/* Virtual Path? */
if(!strnicmp(np,BBS_VIRTUAL_PATH,strlen(BBS_VIRTUAL_PATH))) {
if((dir=getdir_from_vpath(&scfg, np+strlen(BBS_VIRTUAL_PATH), &user, &client, true))<0) {
lprintf(LOG_WARNING,"%04d <%s> !Invalid virtual path: %s", sock, user.alias, np);
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)) {
lprintf(LOG_WARNING,"%04d <%s> !Missing aliased file: %s", sock, user.alias, np);
continue;
}
if(detail) {
if(alias_dir==TRUE) {
fprintf(fp,"drwxrwxrwx 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
,NAME_LEN
,scfg.sys_id
,scfg.lib[scfg.dir[dir]->lib]->vdir
,512L
,ftp_mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min
,p);
}
else {
t=fdate(np);
if(localtime_r(&t,&tm)==NULL)
memset(&tm,0,sizeof(tm));
fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9"PRIdOFF" %s %2d %02d:%02d %s\r\n"
,NAME_LEN
,scfg.sys_id
,scfg.sys_id
,flength(np)
,ftp_mon[tm.tm_mon],tm.tm_mday,tm.tm_hour,tm.tm_min
,p);
}
} else
fprintf(fp,"%s\r\n",p);
}
fclose(alias_fp);
}
/* Library folders */
for(i=0;i<scfg.total_libs;i++) {
if(!chk_ar(&scfg,scfg.lib[i]->ar,&user,&client))
continue;
if(!wildmatchi(scfg.lib[i]->vdir, filespec, FALSE))
continue;
if(detail)
fprintf(fp,"dr-xr-xr-x 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
,NAME_LEN
,scfg.sys_id
,scfg.sys_id
,512L
,ftp_mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min
,scfg.lib[i]->vdir);
else
fprintf(fp,"%s\r\n",scfg.lib[i]->vdir);
}
} else if(dir<0) {
lprintf(LOG_INFO,"%04d <%s> %slisting: %s library in %s mode"
,sock, user.alias, detail ? "detailed ":"", scfg.lib[lib]->vdir, mode);
for(i=0;i<scfg.total_dirs;i++) {
if(scfg.dir[i]->lib!=lib)
continue;
if(i!=(int)scfg.sysop_dir && i!=(int)scfg.upload_dir
&& !chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
continue;
if(!wildmatchi(scfg.dir[i]->vdir, filespec, FALSE))
continue;
if(detail)
fprintf(fp,"drwxrwxrwx 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
,NAME_LEN
,scfg.sys_id
,scfg.lib[lib]->vdir
,512L
,ftp_mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min
,scfg.dir[i]->vdir);
else
fprintf(fp,"%s\r\n",scfg.dir[i]->vdir);
}
} else if(chk_ar(&scfg,scfg.dir[dir]->ar,&user,&client)) {
lprintf(LOG_INFO,"%04d <%s> %slisting: /%s/%s directory in %s mode"
,sock, user.alias, detail ? "detailed ":""
,scfg.lib[lib]->vdir, scfg.dir[dir]->vdir, mode);
smb_t smb;
if((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) {
lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file);
continue;
}
time_t start = time(NULL);
size_t file_count = 0;
file_t* file_list = loadfiles(&smb
,filespec, /* time: */0, file_detail_normal, scfg.dir[dir]->sort, &file_count);
for(size_t i = 0; i < file_count; i++) {
file_t* f = &file_list[i];
if(detail) {
f->size = f->cost;
t = f->hdr.when_imported.time;
if(scfg.dir[dir]->misc&DIR_FCHK) {
struct stat st;
if(stat(getfilepath(&scfg, f, path), &st) != 0)
continue;
f->size = st.st_size;
t = st.st_mtime;
}
if(localtime_r(&t,&tm)==NULL)
memset(&tm,0,sizeof(tm));
if(f->hdr.attr & MSG_ANONYMOUS)
SAFECOPY(str,ANONYMOUS);
else
dotname(f->from,str);
fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9"PRId64" %s %2d "
,NAME_LEN
,str
,scfg.dir[dir]->vdir
,(int64_t)f->size
,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
,f->name);
else
fprintf(fp,"%5d %s\r\n"
,1900+tm.tm_year
,f->name);
} else
fprintf(fp,"%s\r\n", f->name);
}
lprintf(LOG_INFO, "%04d <%s> %slisting (%ld bytes) of /%s/%s (%lu files) created in %ld seconds"
,sock, user.alias, detail ? "detailed ":"", ftell(fp), scfg.lib[lib]->vdir, scfg.dir[dir]->vdir
,(ulong)file_count, (long)time(NULL) - start);
freefiles(file_list, file_count);
smb_close(&smb);
} else
lprintf(LOG_INFO,"%04d <%s> %slisting: /%s/%s directory in %s mode (empty - no access)"
,sock, user.alias, detail ? "detailed ":"", scfg.lib[lib]->vdir, scfg.dir[dir]->vdir, mode);
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,dir,FALSE,FALSE,FALSE,NULL,protection);
continue;
}
if(!strnicmp(cmd, "RETR ", 5)
|| !strnicmp(cmd, "SIZE ",5)
|| !strnicmp(cmd, "MDTM ",5)
|| !strnicmp(cmd, "DELE ",5)) {
getdate=FALSE;
getsize=FALSE;
delecmd=FALSE;
file_date=0;
file_size=-1;
if(!strnicmp(cmd,"SIZE ",5))
getsize=TRUE;
else if(!strnicmp(cmd,"MDTM ",5))
getdate=TRUE;
else if(!strnicmp(cmd,"DELE ",5))
delecmd=TRUE;
if(!getsize && !getdate && user.rest&FLAG('D')) {
sockprintf(sock,sess,"550 Insufficient access.");
filepos=0;
continue;
}
credits=TRUE;
success=FALSE;
delfile=FALSE;
tmpfile=FALSE;
lib=curlib;
dir=curdir;
p=cmd+5;
SKIP_WHITESPACE(p);
if(!strnicmp(p,BBS_FSYS_DIR,strlen(BBS_FSYS_DIR)))
p+=strlen(BBS_FSYS_DIR); /* already mounted */
if(*p=='/') {
lib=-1;
p++;
}
if(!strncmp(p,"./",2))
p+=2;
if(lib<0 && ftpalias(p, fname, &user, &client, &dir)==TRUE) {
success=TRUE;
credits=TRUE; /* include in d/l stats */
tmpfile=FALSE;
delfile=FALSE;
lprintf(LOG_INFO,"%04d <%s> %.4s by alias: %s"
,sock,user.alias,cmd,p);
p=getfname(fname);
if(dir>=0)
lib=scfg.dir[dir]->lib;
}
if(!success && lib<0 && (tp=strchr(p,'/'))!=NULL) {
dir=-1;
*tp=0;
for(i=0;i<scfg.total_libs;i++) {
if(!chk_ar(&scfg,scfg.lib[i]->ar,&user,&client))
continue;
if(!stricmp(scfg.lib[i]->vdir,p))
break;
}
if(i<scfg.total_libs)
lib=i;
p=tp+1;
}
if(!success && dir<0 && (tp=strchr(p,'/'))!=NULL) {
*tp=0;
for(i=0;i<scfg.total_dirs;i++) {
if(scfg.dir[i]->lib!=lib)
continue;
if(!chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
continue;
if(!stricmp(scfg.dir[i]->vdir,p))
break;
}
if(i<scfg.total_dirs)
dir=i;
p=tp+1;
}
sprintf(str,"%s.qwk",scfg.sys_id);
if(lib<0 && startup->options&FTP_OPT_ALLOW_QWK
&& !stricmp(p,str) && !delecmd) {
if(!fexistcase(qwkfile)) {
lprintf(LOG_INFO,"%04d <%s> creating QWK packet...",sock,user.alias);
sprintf(str,"%spack%04u.now",scfg.data_dir,user.number);
if(!fmutex(str, startup->host_name, /* max_age: */60 * 60)) {
lprintf(LOG_WARNING, "%04d <%s> !ERROR %d creating mutex-semaphore file: %s"
,sock, user.alias, errno, str);
sockprintf(sock,sess,"451 Packet creation already in progress (are you logged-in concurrently?)");
filepos=0;
continue;
}
t=time(NULL);
while(fexist(str) && !terminate_server) {
if(!socket_check(sock,NULL,NULL,0))
break;
if(time(NULL)-t>startup->qwk_timeout)
break;
mswait(1000);
}
if(!socket_check(sock,NULL,NULL,0)) {
lprintf(LOG_NOTICE,"%04d <%s> disconnected while waiting for QWK packet creation"
,sock, user.alias);
(void)ftp_remove(sock, __LINE__, str, user.alias);
continue;
}
if(fexist(str)) {
lprintf(LOG_WARNING,"%04d <%s> !TIMEOUT waiting for QWK packet creation", sock, user.alias);
sockprintf(sock,sess,"451 Time-out waiting for packet creation.");
(void)ftp_remove(sock, __LINE__, str, user.alias);
filepos=0;
continue;
}
if(!fexistcase(qwkfile)) {
lprintf(LOG_INFO,"%04d <%s> No QWK Packet created (no new messages)", sock, user.alias);
sockprintf(sock,sess,"550 No QWK packet created (no new messages)");
filepos=0;
continue;
}
}
SAFECOPY(fname,qwkfile);
file_size = flength(fname);
if(file_size < 1) {
lprintf(LOG_WARNING, "%04d <%s> Invalid QWK packet file size (%"PRIuOFF" bytes): %s"
,sock, user.alias, file_size, fname);
sockprintf(sock,sess,"550 Invalid QWK packet file size: %"PRIuOFF" bytes", file_size);
filepos=0;
continue;
}
success=TRUE;
delfile=TRUE;
credits=FALSE;
if(!getsize && !getdate)
lprintf(LOG_INFO,"%04d <%s> downloading QWK packet (%"PRIuOFF" bytes) in %s mode"
,sock,user.alias,file_size
,mode);
/* ASCII Index File */
} else if(startup->options&FTP_OPT_INDEX_FILE
&& !stricmp(p,startup->index_file_name)
&& !delecmd) {
if(getsize) {
sockprintf(sock,sess, "550 Size not available for dynamically generated files");
continue;
}
if((fp=fopen(ftp_tmpfname(fname,"ndx",sock),"wb"))==NULL) {
lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) line %d opening %s"
,sock, user.alias, errno, strerror(errno), __LINE__, fname);
sockprintf(sock,sess, "451 Insufficient system storage");
filepos=0;
continue;
}
success=TRUE;
if(getdate)
file_date=time(NULL);
else {
lprintf(LOG_INFO,"%04d <%s> downloading %s for %s in %s mode"
,sock, user.alias, startup->index_file_name, genvpath(lib,dir,str)
,mode);
credits=FALSE;
tmpfile=TRUE;
delfile=TRUE;
fprintf(fp,"%-*s File/Folder Descriptions\r\n"
,INDEX_FNAME_LEN,startup->index_file_name);
if(lib<0) {
/* File Aliases */
sprintf(aliasfile,"%sftpalias.cfg",scfg.ctrl_dir);
if((alias_fp=fopen(aliasfile,"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);
np++; /* description pointer */
FIND_WHITESPACE(np);
while(*np && *np<' ') np++;
truncsp(np);
fprintf(fp,"%-*s %s\r\n",INDEX_FNAME_LEN,p,np);
}
fclose(alias_fp);
}
/* QWK Packet */
if(startup->options&FTP_OPT_ALLOW_QWK /* && fexist(qwkfile) */) {
sprintf(str,"%s.qwk",scfg.sys_id);
fprintf(fp,"%-*s QWK Message Packet\r\n"
,INDEX_FNAME_LEN,str);
}
/* Library Folders */
for(i=0;i<scfg.total_libs;i++) {
if(!chk_ar(&scfg,scfg.lib[i]->ar,&user,&client))
continue;
fprintf(fp,"%-*s %s\r\n"
,INDEX_FNAME_LEN,scfg.lib[i]->vdir,scfg.lib[i]->lname);
}
} else if(dir<0) {
for(i=0;i<scfg.total_dirs;i++) {
if(scfg.dir[i]->lib!=lib)
continue;
if(i!=(int)scfg.sysop_dir && i!=(int)scfg.upload_dir
&& !chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
continue;
fprintf(fp,"%-*s %s\r\n"
,INDEX_FNAME_LEN,scfg.dir[i]->vdir,scfg.dir[i]->lname);
}
} else if(chk_ar(&scfg,scfg.dir[dir]->ar,&user,&client)){
smb_t smb;
if((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) {
lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file);
continue;
}
time_t start = time(NULL);
size_t file_count = 0;
file_t* file_list = loadfiles(&smb
,/* filespec */NULL, /* time: */0, file_detail_normal, scfg.dir[dir]->sort, &file_count);
for(size_t i = 0; i < file_count; i++) {
file_t* f = &file_list[i];
fprintf(fp,"%-*s %s\r\n",INDEX_FNAME_LEN
,f->name, f->desc);
}
lprintf(LOG_INFO, "%04d <%s> index (%ld bytes) of /%s/%s (%lu files) created in %ld seconds"
,sock, user.alias, ftell(fp), scfg.lib[lib]->vdir, scfg.dir[dir]->vdir
,(ulong)file_count, (long)time(NULL) - start);
freefiles(file_list, file_count);
smb_close(&smb);
}
fclose(fp);
}
} else if(dir>=0) {
if(!chk_ar(&scfg,scfg.dir[dir]->ar,&user,&client)) {
lprintf(LOG_WARNING,"%04d <%s> has insufficient access to /%s/%s"
,sock,user.alias
,scfg.lib[scfg.dir[dir]->lib]->vdir
,scfg.dir[dir]->vdir);
sockprintf(sock,sess,"550 Insufficient access.");
filepos=0;
continue;
}
if(!getsize && !getdate && !delecmd
&& !can_user_download(&scfg, dir, &user, &client, /* reason */NULL)) {
lprintf(LOG_WARNING,"%04d <%s> has insufficient access to download from /%s/%s"
,sock,user.alias
,scfg.lib[scfg.dir[dir]->lib]->vdir
,scfg.dir[dir]->vdir);
sockprintf(sock,sess,"550 Insufficient access.");
filepos=0;
continue;
}
if(delecmd && !dir_op(&scfg,&user,&client,dir) && !(user.exempt&FLAG('R'))) {
lprintf(LOG_WARNING,"%04d <%s> has insufficient access to delete files in /%s/%s"
,sock,user.alias
,scfg.lib[scfg.dir[dir]->lib]->vdir
,scfg.dir[dir]->vdir);
sockprintf(sock,sess,"550 Insufficient access.");
filepos=0;
continue;
}
SAFEPRINTF2(fname,"%s%s",scfg.dir[dir]->path,p);
filedat=findfile(&scfg, dir, p, NULL);
if(!filedat) {
sockprintf(sock,sess,"550 File not found: %s",p);
lprintf(LOG_WARNING,"%04d <%s> file (%s%s) not in database for %.4s command"
,sock,user.alias,genvpath(lib,dir,str),p,cmd);
filepos=0;
continue;
}
/* Verify credits */
if(!getsize && !getdate && !delecmd
&& !is_download_free(&scfg,dir,&user,&client)) {
file_t f;
if(filedat)
loadfile(&scfg, dir, p, &f, file_detail_normal);
else
f.cost=(uint32_t)flength(fname);
if(f.cost>user_available_credits(&user)) {
lprintf(LOG_WARNING,"%04d <%s> has insufficient credit to download /%s/%s/%s (%lu credits)"
,sock,user.alias,scfg.lib[scfg.dir[dir]->lib]->vdir
,scfg.dir[dir]->vdir
,p
,(ulong)f.cost);
sockprintf(sock,sess,"550 Insufficient credit (%lu required).", (ulong)f.cost);
filepos=0;
smb_freefilemem(&f);
continue;
}
smb_freefilemem(&f);
}
if(strcspn(p,ILLEGAL_FILENAME_CHARS)!=strlen(p)) {
success=FALSE;
lprintf(LOG_WARNING,"%04d <%s> !ILLEGAL FILENAME ATTEMPT by %s [%s]: '%s'"
,sock, user.alias, host_name, host_ip, p);
ftp_hacklog("FTP FILENAME", user.alias, cmd, host_name, &ftp.client_addr);
} else {
if(fexistcase(fname)) {
success=TRUE;
if(!getsize && !getdate && !delecmd)
lprintf(LOG_INFO,"%04d <%s> downloading: %s (%"PRIuOFF" bytes) in %s mode"
,sock,user.alias,fname,flength(fname)
,mode);
}
}
}
#if defined(SOCKET_DEBUG_DOWNLOAD)
socket_debug[sock]|=SOCKET_DEBUG_DOWNLOAD;
#endif
if(getsize && success)
sockprintf(sock,sess,"213 %"PRIuOFF, flength(fname));
else if(getdate && success) {
if(file_date==0)
file_date = fdate(fname);
if(gmtime_r(&file_date,&tm)==NULL) /* specifically use GMT/UTC representation */
memset(&tm,0,sizeof(tm));
sockprintf(sock,sess,"213 %u%02u%02u%02u%02u%02u"
,1900+tm.tm_year,tm.tm_mon+1,tm.tm_mday
,tm.tm_hour,tm.tm_min,tm.tm_sec);
} else if(delecmd && success) {
if(removecase(fname)!=0) {
lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) deleting %s", sock, user.alias, errno, strerror(errno), fname);
sockprintf(sock,sess,"450 %s could not be deleted (error: %d)"
,fname,errno);
} else {
lprintf(LOG_NOTICE,"%04d <%s> deleted %s",sock,user.alias,fname);
if(filedat)
removefile(&scfg, dir, getfname(fname));
sockprintf(sock,sess,"250 %s deleted.",fname);
}
} else if(success) {
sockprintf(sock,sess,"150 Opening BINARY mode data connection for file transfer.");
filexfer(&data_addr,sock,sess,pasv_sock,pasv_sess,&data_sock,&data_sess,fname,filepos
,&transfer_inprogress,&transfer_aborted,delfile,tmpfile
,&lastactive,&user,&client,dir,FALSE,credits,FALSE,NULL,protection);
}
else {
sockprintf(sock,sess,"550 File not found: %s",p);
lprintf(LOG_WARNING,"%04d <%s> file (%s%s) not found for %.4s command"
,sock,user.alias,genvpath(lib,dir,str),p,cmd);
}
filepos=0;
#if defined(SOCKET_DEBUG_DOWNLOAD)
socket_debug[sock]&=~SOCKET_DEBUG_DOWNLOAD;
#endif
continue;
}
if(!strnicmp(cmd, "DESC", 4)) {
if(user.rest&FLAG('U')) {
sockprintf(sock,sess,"553 Insufficient access.");
continue;
}
p=cmd+4;
SKIP_WHITESPACE(p);
if(*p==0)
sockprintf(sock,sess,"501 No file description given.");
else {
SAFECOPY(desc,p);
sockprintf(sock,sess,"200 File description set. Ready to STOR file.");
}
continue;
}
if(!strnicmp(cmd, "STOR ", 5) || !strnicmp(cmd, "APPE ", 5)) {
if(user.rest&FLAG('U')) {
sockprintf(sock,sess,"553 Insufficient access.");
continue;
}
if(transfer_inprogress==TRUE) {
lprintf(LOG_WARNING,"%04d <%s> !TRANSFER already in progress (%s)",sock, user.alias, cmd);
sockprintf(sock,sess,"425 Transfer already in progress.");
continue;
}
append=FALSE;
lib=curlib;
dir=curdir;
p=cmd+5;
SKIP_WHITESPACE(p);
if(!strnicmp(p,BBS_FSYS_DIR,strlen(BBS_FSYS_DIR)))
p+=strlen(BBS_FSYS_DIR); /* already mounted */
if(*p=='/') {
lib=-1;
p++;
}
if(!strncmp(p,"./",2))
p+=2;
/* Need to add support for uploading to aliased directories */
if(lib<0 && (tp=strchr(p,'/'))!=NULL) {
dir=-1;
*tp=0;
for(i=0;i<scfg.total_libs;i++) {
if(!chk_ar(&scfg,scfg.lib[i]->ar,&user,&client))
continue;
if(!stricmp(scfg.lib[i]->vdir,p))
break;
}
if(i<scfg.total_libs)
lib=i;
p=tp+1;
}
if(dir<0 && (tp=strchr(p,'/'))!=NULL) {
*tp=0;
for(i=0;i<scfg.total_dirs;i++) {
if(scfg.dir[i]->lib!=lib)
continue;
if(i!=(int)scfg.sysop_dir && i!=(int)scfg.upload_dir
&& !chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
continue;
if(!stricmp(scfg.dir[i]->vdir,p))
break;
}
if(i<scfg.total_dirs)
dir=i;
p=tp+1;
}
uint64_t freespace;
if(dir<0) {
sprintf(str,"%s.rep",scfg.sys_id);
if(!(startup->options&FTP_OPT_ALLOW_QWK)
|| stricmp(p,str)) {
lprintf(LOG_WARNING,"%04d <%s> !attempted to upload invalid path/filename: '%s'"
,sock, user.alias, p);
sockprintf(sock,sess,"553 Invalid directory.");
continue;
}
sprintf(fname,"%sfile/%04d.rep",scfg.data_dir,user.number);
lprintf(LOG_INFO,"%04d <%s> uploading: %s in %s mode"
,sock,user.alias,fname
,mode);
freespace = getfreediskspace(scfg.data_dir, 1);
} else {
append=(strnicmp(cmd,"APPE",4)==0);
if(!dir_op(&scfg,&user,&client,dir) && !(user.exempt&FLAG('U'))) {
if(!chk_ar(&scfg,scfg.dir[dir]->ul_ar,&user,&client)
|| !chk_ar(&scfg, scfg.lib[scfg.dir[dir]->lib]->ul_ar, &user, &client)) {
lprintf(LOG_WARNING,"%04d <%s> cannot upload to /%s/%s (insufficient access)"
,sock,user.alias
,scfg.lib[scfg.dir[dir]->lib]->vdir
,scfg.dir[dir]->vdir);
sockprintf(sock,sess,"553 Insufficient access.");
continue;
}
if(!append && scfg.dir[dir]->maxfiles && getfiles(&scfg,dir)>=scfg.dir[dir]->maxfiles) {
lprintf(LOG_WARNING,"%04d <%s> cannot upload to /%s/%s (directory full: %d files)"
,sock,user.alias
,scfg.lib[scfg.dir[dir]->lib]->vdir
,scfg.dir[dir]->vdir
,getfiles(&scfg,dir));
sockprintf(sock,sess,"553 Directory full.");
continue;
}
}
if(illegal_filename(p)
|| trashcan(&scfg,p,"file")) {
lprintf(LOG_WARNING,"%04d <%s> !ILLEGAL FILENAME ATTEMPT by %s [%s]: '%s'"
,sock, user.alias, host_name, host_ip, p);
sockprintf(sock,sess,"553 Illegal filename attempt");
ftp_hacklog("FTP FILENAME", user.alias, cmd, host_name, &ftp.client_addr);
continue;
}
if(!allowed_filename(&scfg, p)) {
lprintf(LOG_WARNING,"%04d <%s> !UNALLOWED FILENAME ATTEMPT by %s [%s]: '%s'"
,sock, user.alias, host_name, host_ip, p);
sockprintf(sock,sess,"553 Unallowed filename attempt");
continue;
}
SAFEPRINTF2(fname,"%s%s",scfg.dir[dir]->path,p);
if((!append && filepos==0 && fexist(fname))
|| (startup->options&FTP_OPT_INDEX_FILE
&& !stricmp(p,startup->index_file_name))
) {
lprintf(LOG_WARNING,"%04d <%s> attempted to overwrite existing file: '%s'"
,sock,user.alias,fname);
sockprintf(sock,sess,"553 File already exists.");
continue;
}
if(append || filepos) { /* RESUME */
file_t f;
if(!loadfile(&scfg, dir, p, &f, file_detail_normal)) {
if(filepos) {
lprintf(LOG_WARNING,"%04d <%s> file (%s) not in database for %.4s command"
,sock,user.alias,fname,cmd);
sockprintf(sock,sess,"550 File not found: %s",p);
continue;
}
append=FALSE;
}
/* Verify user is original uploader */
if((append || filepos) && stricmp(f.from, user.alias)) {
lprintf(LOG_WARNING,"%04d <%s> !cannot resume upload of %s, uploaded by %s"
,sock,user.alias,fname,f.from);
sockprintf(sock,sess,"553 Insufficient access (can't resume upload from different user).");
smb_freefilemem(&f);
continue;
}
smb_freefilemem(&f);
}
lprintf(LOG_INFO,"%04d <%s> uploading: %s to %s (%s) in %s mode"
,sock,user.alias
,p /* filename */
,genvpath(lib,dir,str) /* virtual path */
,scfg.dir[dir]->path /* actual path */
,mode);
freespace = getfreediskspace(scfg.dir[dir]->path, 1);
}
if(freespace < scfg.min_dspace) {
lprintf(LOG_ERR, "%04d <%s> !Insufficient free disk space (%s bytes) to allow upload"
,sock, user.alias, byte_estimate_to_str(freespace, str, sizeof(str), 1,1));
sockprintf(sock, sess, "452 Insufficient free disk space, try again later");
continue;
}
sockprintf(sock,sess,"150 Opening BINARY mode data connection for file transfer.");
filexfer(&data_addr,sock,sess,pasv_sock,pasv_sess,&data_sock,&data_sess,fname,filepos
,&transfer_inprogress,&transfer_aborted,FALSE,FALSE
,&lastactive
,&user
,&client
,dir
,TRUE /* uploading */
,TRUE /* credits */
,append
,desc
,protection
);
filepos=0;
continue;
}
if(!stricmp(cmd,"CDUP") || !stricmp(cmd,"XCUP")) {
if(curdir<0)
curlib=-1;
else
curdir=-1;
sockprintf(sock,sess,"200 CDUP command successful.");
continue;
}
if(!strnicmp(cmd, "CWD ", 4) || !strnicmp(cmd,"XCWD ",5)) {
p=cmd+4;
SKIP_WHITESPACE(p);
if(!strnicmp(p,BBS_FSYS_DIR,strlen(BBS_FSYS_DIR)))
p+=strlen(BBS_FSYS_DIR); /* already mounted */
if(*p=='/') {
curlib=-1;
curdir=-1;
p++;
}
/* Local File System? */
if(sysop && !(startup->options&FTP_OPT_NO_LOCAL_FSYS)
&& !strnicmp(p,LOCAL_FSYS_DIR,strlen(LOCAL_FSYS_DIR))) {
p+=strlen(LOCAL_FSYS_DIR);
if(!direxist(p)) {
sockprintf(sock,sess,"550 Directory does not exist.");
lprintf(LOG_WARNING,"%04d <%s> attempted to mount invalid directory: '%s'"
,sock, user.alias, p);
continue;
}
SAFECOPY(local_dir,p);
local_fsys=TRUE;
sockprintf(sock,sess,"250 CWD command successful (local file system mounted).");
lprintf(LOG_INFO,"%04d <%s> mounted local file system", sock, user.alias);
continue;
}
success=FALSE;
/* Directory Alias? */
if(curlib<0 && ftpalias(p,NULL,&user,&client,&curdir)==TRUE) {
if(curdir>=0)
curlib=scfg.dir[curdir]->lib;
success=TRUE;
}
orglib=curlib;
orgdir=curdir;
tp=0;
if(!strncmp(p,"...",3)) {
curlib=-1;
curdir=-1;
p+=3;
}
if(!strncmp(p,"./",2))
p+=2;
else if(!strncmp(p,"..",2)) {
if(curdir<0)
curlib=-1;
else
curdir=-1;
p+=2;
}
if(*p==0)
success=TRUE;
else if(!strcmp(p,"."))
success=TRUE;
if(!success && (curlib<0 || *p=='/')) { /* Root dir */
if(*p=='/') p++;
tp=strchr(p,'/');
if(tp) *tp=0;
for(i=0;i<scfg.total_libs;i++) {
if(!chk_ar(&scfg,scfg.lib[i]->ar,&user,&client))
continue;
if(!stricmp(scfg.lib[i]->vdir,p))
break;
}
if(i<scfg.total_libs) {
curlib=i;
success=TRUE;
}
}
if((!success && curdir<0) || (success && tp && *(tp+1))) {
if(tp)
p=tp+1;
tp=lastchar(p);
if(tp && *tp=='/') *tp=0;
for(i=0;i<scfg.total_dirs;i++) {
if(scfg.dir[i]->lib!=curlib)
continue;
if(i!=(int)scfg.sysop_dir && i!=(int)scfg.upload_dir
&& !chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
continue;
if(!stricmp(scfg.dir[i]->vdir,p))
break;
}
if(i<scfg.total_dirs) {
curdir=i;
success=TRUE;
} else
success=FALSE;
}
if(success)
sockprintf(sock,sess,"250 CWD command successful.");
else {
sockprintf(sock,sess,"550 %s: No such directory.",p);
curlib=orglib;
curdir=orgdir;
}
continue;
}
if(!stricmp(cmd, "PWD") || !stricmp(cmd,"XPWD")) {
if(curlib<0)
sockprintf(sock,sess,"257 \"/\" is current directory.");
else if(curdir<0)
sockprintf(sock,sess,"257 \"/%s\" is current directory."
,scfg.lib[curlib]->vdir);
else
sockprintf(sock,sess,"257 \"/%s/%s\" is current directory."
,scfg.lib[curlib]->vdir
,scfg.dir[curdir]->vdir);
continue;
}
if(!strnicmp(cmd, "MKD", 3) ||
!strnicmp(cmd,"XMKD",4) ||
!strnicmp(cmd,"SITE EXEC",9)) {
lprintf(LOG_WARNING,"%04d <%s> !SUSPECTED HACK ATTEMPT: %s"
,sock,user.alias,cmd);
ftp_hacklog("FTP", user.alias, cmd, host_name, &ftp.client_addr);
}
// TODO: STAT is mandatory
sockprintf(sock,sess,"500 Syntax error: '%s'",cmd);
lprintf(LOG_WARNING,"%04d <%s> !UNSUPPORTED COMMAND: %s"
,sock,user.alias,cmd);
} /* while(1) */
#if defined(SOCKET_DEBUG_TERMINATE)
socket_debug[sock]|=SOCKET_DEBUG_TERMINATE;
#endif
if(transfer_inprogress==TRUE) {
lprintf(LOG_DEBUG,"%04d Waiting for transfer to complete...",sock);
count=0;
while(transfer_inprogress==TRUE) {
if(ftp_set==NULL || terminate_server) {
mswait(2000); /* allow xfer threads to terminate */
break;
}
if(!transfer_aborted) {
if(gettimeleft(&scfg,&user,logintime)<1) {
lprintf(LOG_WARNING,"%04d Out of time, disconnecting",sock);
sockprintf(sock,sess,"421 Sorry, you've run out of time.");
ftp_close_socket(&data_sock,&data_sess,__LINE__);
transfer_aborted=TRUE;
}
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);
ftp_close_socket(&data_sock,&data_sess,__LINE__);
transfer_aborted=TRUE;
}
}
if(count && (count%60)==0)
lprintf(LOG_WARNING,"%04d Still waiting for transfer to complete "
"(count=%lu, aborted=%d, lastactive=%lX) ..."
,sock,count,transfer_aborted,lastactive);
count++;
mswait(1000);
}
lprintf(LOG_DEBUG,"%04d Done waiting for transfer to complete",sock);
}
if(user.number) {
/* Update User Statistics */
if(!logoutuserdat(&scfg, &user, time(NULL), logintime))
lprintf(LOG_ERR,"%04d <%s> !ERROR in logoutuserdat", sock, user.alias);
mqtt_user_logout(&mqtt, &client, logintime);
lprintf(LOG_INFO,"%04d <%s> logged off", sock, user.alias);
#ifdef _WIN32
if(startup->sound.logout[0] && !sound_muted(&scfg))
PlaySound(startup->sound.logout, NULL, SND_ASYNC|SND_FILENAME);
#endif
}
#ifdef _WIN32
if(startup->sound.hangup[0] && !sound_muted(&scfg))
PlaySound(startup->sound.hangup, NULL, SND_ASYNC|SND_FILENAME);
#endif
if(pasv_sock!=INVALID_SOCKET)
ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
if(data_sock!=INVALID_SOCKET)
ftp_close_socket(&data_sock,&data_sess,__LINE__);
client_off(sock);
#ifdef SOCKET_DEBUG_CTRL
socket_debug[sock]&=~SOCKET_DEBUG_CTRL;
#endif
#if defined(SOCKET_DEBUG_TERMINATE)
socket_debug[sock]&=~SOCKET_DEBUG_TERMINATE;
#endif
tmp_sock=sock;
ftp_close_socket(&tmp_sock,&sess,__LINE__);
{
int32_t clients = protected_uint32_adjust_fetch(&active_clients, -1);
int32_t threads = thread_down();
update_clients();
lprintf(LOG_INFO,"%04d CTRL thread terminated (%d clients and %d threads remain, %lu served)"
,sock, clients, threads, served);
}
}
static void cleanup(int code, int line)
{
#ifdef _DEBUG
lprintf(LOG_DEBUG,"0000 cleanup called from line %d",line);
#endif
if(protected_uint32_value(thread_count) > 1) {
lprintf(LOG_INFO, "0000 Waiting for %d child threads to terminate", protected_uint32_value(thread_count)-1);
while(protected_uint32_value(thread_count) > 1) {
mswait(100);
}
lprintf(LOG_INFO, "0000 Done waiting for child threads to terminate");
}
free_cfg(&scfg);
free_text(text);
semfile_list_free(&recycle_semfiles);
semfile_list_free(&shutdown_semfiles);
if(ftp_set != NULL) {
xpms_destroy(ftp_set, ftp_close_socket_cb, NULL);
ftp_set = NULL;
}
update_clients(); /* active_clients is destroyed below */
listFree(¤t_connections);
if(protected_uint32_value(active_clients))
lprintf(LOG_WARNING,"!!!! Terminating with %d active clients", protected_uint32_value(active_clients));
else
protected_uint32_destroy(active_clients);
#ifdef _WINSOCKAPI_
if(WSAInitialized && WSACleanup()!=0)
lprintf(LOG_ERR,"0000 !WSACleanup ERROR %d",ERROR_VALUE);
#endif
thread_down();
if(terminate_server || code)
lprintf(LOG_INFO,"#### FTP Server thread terminated (%lu clients served)", served);
set_state(SERVER_STOPPED);
mqtt_shutdown(&mqtt);
if(startup!=NULL && startup->terminated!=NULL)
startup->terminated(startup->cbdata,code);
}
const char* ftp_ver(void)
{
static char ver[256];
char compiler[32];
DESCRIBE_COMPILER(compiler);
safe_snprintf(ver, sizeof(ver), "%s %s%c%s "
"Compiled %s/%s %s %s with %s"
,FTP_SERVER
,VERSION, REVISION
#ifdef _DEBUG
," Debug"
#else
,""
#endif
,GIT_BRANCH, GIT_HASH
,__DATE__, __TIME__, compiler);
return(ver);
}
void ftp_server(void* arg)
{
char* p;
char path[MAX_PATH+1];
char error[256];
char compiler[32];
char str[256];
union xp_sockaddr client_addr;
socklen_t client_addr_len;
SOCKET client_socket;
int i;
time_t t;
time_t start;
time_t initialized=0;
ftp_t* ftp;
char client_ip[INET6_ADDRSTRLEN];
CRYPT_SESSION none = -1;
startup=(ftp_startup_t*)arg;
SetThreadName("sbbs/ftpServer");
#ifdef _THREAD_SUID_BROKEN
if(thread_suid_broken)
startup->seteuid(TRUE);
#endif
if(startup==NULL) {
sbbs_beep(100,500);
fprintf(stderr, "No startup structure passed!\n");
return;
}
if(startup->size!=sizeof(ftp_startup_t)) { /* verify size */
sbbs_beep(100,500);
sbbs_beep(300,500);
sbbs_beep(100,500);
fprintf(stderr, "Invalid startup structure!\n");
return;
}
set_state(SERVER_INIT);
uptime=0;
served=0;
startup->recycle_now=FALSE;
startup->shutdown_now=FALSE;
terminate_server=FALSE;
protected_uint32_init(&thread_count, 0);
do {
listInit(¤t_connections, LINK_LIST_MUTEX);
protected_uint32_init(&active_clients, 0);
/* Setup intelligent defaults */
if(startup->port==0) startup->port=IPPORT_FTP;
if(startup->qwk_timeout==0) startup->qwk_timeout=FTP_DEFAULT_QWK_TIMEOUT; /* seconds */
if(startup->max_inactivity==0) startup->max_inactivity=FTP_DEFAULT_MAX_INACTIVITY; /* seconds */
if(startup->sem_chk_freq==0) startup->sem_chk_freq=DEFAULT_SEM_CHK_FREQ; /* seconds */
if(startup->index_file_name[0]==0) SAFECOPY(startup->index_file_name,"00index");
(void)protected_uint32_adjust(&thread_count,1);
thread_up(FALSE /* setuid */);
memset(&scfg, 0, sizeof(scfg));
lprintf(LOG_INFO,"Synchronet FTP Server Version %s%c%s"
,VERSION, REVISION
#ifdef _DEBUG
," Debug"
#else
,""
#endif
);
DESCRIBE_COMPILER(compiler);
lprintf(LOG_INFO,"Compiled %s/%s %s %s with %s", GIT_BRANCH, GIT_HASH, __DATE__, __TIME__, compiler);
sbbs_srand(); /* Seed random number generator */
if(!winsock_startup()) {
cleanup(1,__LINE__);
break;
}
t=time(NULL);
lprintf(LOG_INFO,"Initializing on %.24s with options: %x"
,ctime_r(&t,str),startup->options);
if(chdir(startup->ctrl_dir)!=0)
lprintf(LOG_ERR,"!ERROR %d (%s) changing directory to: %s", errno, strerror(errno), startup->ctrl_dir);
/* Initial configuration and load from CNF files */
SAFECOPY(scfg.ctrl_dir, startup->ctrl_dir);
lprintf(LOG_INFO,"Loading configuration files from %s", scfg.ctrl_dir);
scfg.size=sizeof(scfg);
SAFECOPY(error,UNKNOWN_LOAD_ERROR);
if(!load_cfg(&scfg, text, /* prep: */TRUE, /* node: */FALSE, error, sizeof(error))) {
lprintf(LOG_CRIT,"!ERROR %s",error);
lprintf(LOG_CRIT,"!Failed to load configuration files");
cleanup(1,__LINE__);
break;
}
mqtt_startup(&mqtt, &scfg, (struct startup*)startup, ftp_ver(), lputs);
if((t=checktime())!=0) { /* Check binary time */
lprintf(LOG_ERR,"!TIME PROBLEM (%ld)",t);
}
if(uptime==0)
uptime=time(NULL); /* this must be done *after* setting the timezone */
if(startup->temp_dir[0])
SAFECOPY(scfg.temp_dir,startup->temp_dir);
else
SAFECOPY(scfg.temp_dir,"../temp");
prep_dir(scfg.ctrl_dir, scfg.temp_dir, sizeof(scfg.temp_dir));
if((i = md(scfg.temp_dir)) != 0) {
lprintf(LOG_CRIT,"!ERROR %d (%s) creating directory: %s", i, strerror(i), scfg.temp_dir);
cleanup(1,__LINE__);
break;
}
lprintf(LOG_DEBUG,"Temporary file directory: %s", scfg.temp_dir);
if(!startup->max_clients) {
startup->max_clients=scfg.sys_nodes;
if(startup->max_clients<10)
startup->max_clients=10;
}
lprintf(LOG_DEBUG,"Maximum clients: %d",startup->max_clients);
/* Sanity-check the passive port range */
if(startup->pasv_port_low || startup->pasv_port_high) {
if(startup->pasv_port_low > startup->pasv_port_high
|| startup->pasv_port_high-startup->pasv_port_low < (startup->max_clients-1)) {
lprintf(LOG_WARNING,"!Correcting Passive Port Range (Low: %u, High: %u)"
,startup->pasv_port_low,startup->pasv_port_high);
if(startup->pasv_port_low)
startup->pasv_port_high = startup->pasv_port_low+(startup->max_clients-1);
else
startup->pasv_port_low = startup->pasv_port_high-(startup->max_clients-1);
}
lprintf(LOG_DEBUG,"Passive Port Low: %u",startup->pasv_port_low);
lprintf(LOG_DEBUG,"Passive Port High: %u",startup->pasv_port_high);
}
lprintf(LOG_DEBUG,"Maximum inactivity: %d seconds",startup->max_inactivity);
update_clients();
/* open a socket and wait for a client */
ftp_set = xpms_create(startup->bind_retry_count, startup->bind_retry_delay, lprintf);
if(ftp_set == NULL) {
lprintf(LOG_CRIT,"!ERROR %d creating FTP socket set", ERROR_VALUE);
cleanup(1, __LINE__);
return;
}
lprintf(LOG_DEBUG,"FTP Server socket set created");
/*
* Add interfaces
*/
xpms_add_list(ftp_set, PF_UNSPEC, SOCK_STREAM, 0, startup->interfaces, startup->port, "FTP Server", ftp_open_socket_cb, startup->seteuid, NULL);
/* Setup recycle/shutdown semaphore file lists */
shutdown_semfiles=semfile_list_init(scfg.ctrl_dir,"shutdown", server_abbrev);
recycle_semfiles=semfile_list_init(scfg.ctrl_dir,"recycle", server_abbrev);
semfile_list_add(&recycle_semfiles,startup->ini_fname);
SAFEPRINTF(path,"%sftpsrvr.rec",scfg.ctrl_dir); /* legacy */
semfile_list_add(&recycle_semfiles,path);
if(!initialized) {
semfile_list_check(&initialized,recycle_semfiles);
semfile_list_check(&initialized,shutdown_semfiles);
}
/* signal caller that we've started up successfully */
set_state(SERVER_READY);
lprintf(LOG_INFO,"FTP Server thread started");
mqtt_client_max(&mqtt, startup->max_clients);
while(ftp_set!=NULL && !terminate_server) {
YIELD();
if(protected_uint32_value(thread_count) <= 1) {
if(!(startup->options&FTP_OPT_NO_RECYCLE)) {
if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) {
lprintf(LOG_INFO,"0000 Recycle semaphore file (%s) detected",p);
break;
}
if(startup->recycle_now==TRUE) {
lprintf(LOG_NOTICE,"0000 Recycle semaphore signaled");
startup->recycle_now=FALSE;
break;
}
}
if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL
&& lprintf(LOG_INFO,"0000 Shutdown semaphore file (%s) detected",p))
|| (startup->shutdown_now==TRUE
&& lprintf(LOG_INFO,"0000 Shutdown semaphore signaled"))) {
startup->shutdown_now=FALSE;
terminate_server=TRUE;
break;
}
}
if(ftp_set==NULL || terminate_server) /* terminated */
break;
/* now wait for connection */
client_addr_len = sizeof(client_addr);
client_socket = xpms_accept(ftp_set, &client_addr, &client_addr_len, startup->sem_chk_freq*1000, XPMS_FLAGS_NONE, NULL);
if(client_socket == INVALID_SOCKET)
continue;
if(startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
inet_addrtop(&client_addr, client_ip, sizeof(client_ip));
if(startup->max_concurrent_connections > 0) {
int ip_len = strlen(client_ip) + 1;
uint connections = listCountMatches(¤t_connections, client_ip, ip_len);
if(connections >= startup->max_concurrent_connections
&& !is_host_exempt(&scfg, client_ip, /* host_name */NULL)) {
lprintf(LOG_NOTICE, "%04d [%s] !Maximum concurrent connections (%u) exceeded"
,client_socket, client_ip, startup->max_concurrent_connections);
sockprintf(client_socket, -1, "421 Maximum connections (%u) exceeded", startup->max_concurrent_connections);
ftp_close_socket(&client_socket,&none,__LINE__);
continue;
}
}
if(trashcan(&scfg,client_ip,"ip-silent")) {
ftp_close_socket(&client_socket,&none,__LINE__);
continue;
}
if(protected_uint32_value(active_clients)>=startup->max_clients) {
lprintf(LOG_WARNING,"%04d [%s] !MAXIMUM CLIENTS (%d) reached, access denied"
,client_socket, client_ip, startup->max_clients);
sockprintf(client_socket,-1,"421 Maximum active clients reached, please try again later.");
ftp_close_socket(&client_socket,&none,__LINE__);
continue;
}
if((ftp=malloc(sizeof(ftp_t)))==NULL) {
lprintf(LOG_CRIT,"%04d !ERROR allocating %d bytes of memory for ftp_t"
,client_socket,(int)sizeof(ftp_t));
sockprintf(client_socket,-1,"421 System error, please try again later.");
ftp_close_socket(&client_socket,&none,__LINE__);
continue;
}
ftp->socket=client_socket;
memcpy(&ftp->client_addr, &client_addr, client_addr_len);
ftp->client_addr_len = client_addr_len;
(void)protected_uint32_adjust(&thread_count,1);
_beginthread(ctrl_thread, 0, ftp);
served++;
}
set_state(terminate_server ? SERVER_STOPPING : SERVER_RELOADING);
#if 0 /* def _DEBUG */
lprintf(LOG_DEBUG,"0000 terminate_server: %d",terminate_server);
#endif
if(protected_uint32_value(active_clients)) {
lprintf(LOG_INFO,"0000 Waiting for %d active clients to disconnect..."
, protected_uint32_value(active_clients));
start=time(NULL);
while(protected_uint32_value(active_clients)) {
if(time(NULL)-start > startup->max_inactivity * 2) {
lprintf(LOG_WARNING,"0000 !TIMEOUT waiting for %d active clients"
, protected_uint32_value(active_clients));
break;
}
mswait(100);
}
lprintf(LOG_INFO, "0000 Done waiting for active clients to disconnect");
}
cleanup(0,__LINE__);
if(!terminate_server) {
lprintf(LOG_INFO,"Recycling server...");
mswait(2000);
if(startup->recycle!=NULL)
startup->recycle(startup->cbdata);
}
} while(!terminate_server);
protected_uint32_destroy(thread_count);
}