/* 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 "telnet.h" #include "multisock.h" #include "ssl.h" #include "cryptlib.h" #include "xpprintf.h" // vasprintf #include "md5.h" #include "ver.h" /* Constants */ #define FTP_SERVER "Synchronet FTP Server" #define STATUS_WFC "Listening" #define ANONYMOUS "anonymous" #define BBS_VIRTUAL_PATH "bbs:/""/" /* this is actually bbs:<slash><slash> */ #define LOCAL_FSYS_DIR "local:" #define BBS_FSYS_DIR "bbs:" #define BBS_HIDDEN_ALIAS "hidden" #define TIMEOUT_THREAD_WAIT 60 /* Seconds */ #define TIMEOUT_SOCKET_LISTEN 30 /* Seconds */ #define XFER_REPORT_INTERVAL 60 /* Seconds */ #define INDEX_FNAME_LEN 15 #define NAME_LEN 15 /* User name length for listings */ #define MLSX_TYPE (1<<0) #define MLSX_PERM (1<<1) #define MLSX_SIZE (1<<2) #define MLSX_MODIFY (1<<3) #define MLSX_OWNER (1<<4) #define MLSX_UNIQUE (1<<5) #define MLSX_CREATE (1<<6) static ftp_startup_t* startup=NULL; static scfg_t scfg; 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(user->level>=SYSOP_LEVEL || (cfg->dir[dirnum]->op_ar!=NULL && cfg->dir[dirnum]->op_ar[0] && chk_ar(cfg,cfg->dir[dirnum]->op_ar,user,client))); } #if defined(__GNUC__) // Catch printf-format errors with lprintf static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); #endif static int lprintf(int level, const char *fmt, ...) { va_list argptr; char sbuf[1024]; va_start(argptr,fmt); vsnprintf(sbuf,sizeof(sbuf),fmt,argptr); sbuf[sizeof(sbuf)-1]=0; va_end(argptr); if(level <= LOG_ERR) { char errmsg[sizeof(sbuf)+16]; SAFEPRINTF(errmsg, "ftp %s", sbuf); errorlog(&scfg, 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 || level > startup->log_level) return(0); #if defined(_WIN32) if(IsBadCodePtr((FARPROC)startup->lputs)) return(0); #endif return startup->lputs(startup->cbdata,level,sbuf); } #ifdef _WINSOCKAPI_ static WSADATA WSAData; #define SOCKLIB_DESC WSAData.szDescription static BOOL WSAInitialized=FALSE; static BOOL winsock_startup(void) { int status; /* Status Code */ if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) { lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus); WSAInitialized=TRUE; 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 status(char* str) { if(startup!=NULL && startup->status!=NULL) startup->status(startup->cbdata,str); } static void update_clients(void) { if(startup!=NULL && startup->clients!=NULL) startup->clients(startup->cbdata,protected_uint32_value(active_clients)); } static void client_on(SOCKET sock, client_t* client, BOOL update) { if(!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); } 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); } static void thread_up(BOOL setuid) { if(startup!=NULL && startup->thread_up!=NULL) startup->thread_up(startup->cbdata,TRUE, setuid); } static int32_t thread_down(void) { int32_t count = protected_uint32_adjust(&thread_count,-1); if(startup!=NULL && startup->thread_up!=NULL) startup->thread_up(startup->cbdata,FALSE, FALSE); return count; } 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]; fd_set socket_set; struct timeval tv; 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 (using select) */ tv.tv_sec=300; tv.tv_usec=0; FD_ZERO(&socket_set); FD_SET(sock,&socket_set); if((result=select(sock+1,NULL,&socket_set,NULL,&tv))<1) { if(result==0) lprintf(LOG_WARNING,"%04d !TIMEOUT selecting socket for send" ,sock); else lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for send" ,sock, ERROR_VALUE); return(0); } 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); } /* Returns the directory index of a virtual lib/dir path (e.g. main/games/filename) */ int getdir(char* p, user_t* user, client_t* client) { char* tp; char path[MAX_PATH+1]; uint dir; uint lib; SAFECOPY(path,p); p=path; if(*p=='/') p++; else if(!strncmp(p,"./",2)) p+=2; tp=strchr(p,'/'); if(tp) *tp=0; for(lib=0;lib<scfg.total_libs;lib++) { if(!chk_ar(&scfg,scfg.lib[lib]->ar,user,client)) continue; if(!stricmp(scfg.lib[lib]->sname,p)) break; } if(lib>=scfg.total_libs) return(-1); if(tp!=NULL) p=tp+1; tp=strchr(p,'/'); if(tp) *tp=0; for(dir=0;dir<scfg.total_dirs;dir++) { if(scfg.dir[dir]->lib!=lib) continue; if(dir!=scfg.sysop_dir && dir!=scfg.upload_dir && !chk_ar(&scfg,scfg.dir[dir]->ar,user,client)) continue; if(!stricmp(scfg.dir[dir]->code_suffix,p)) break; } if(dir>=scfg.total_dirs) return(-1); return(dir); } 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; fd_set socket_set; struct timeval tv; 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 select() */ cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity); switch(ret) { case CRYPT_OK: break; case CRYPT_ERROR_TIMEOUT: if (!first) { GCES(ret, sock, sess, estr, "popping data"); return -1; } break; 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); } tv.tv_sec=startup->max_inactivity; tv.tv_usec=0; FD_ZERO(&socket_set); FD_SET(sock,&socket_set); i=select(sock+1,&socket_set,NULL,NULL,&tv); if(i<1) { if(i==0) { if((time(NULL)-(*lastactive))>startup->max_inactivity) { lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock); sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)." ,startup->max_inactivity); return(0); } continue; } recverror(sock,i,__LINE__); return(i); } } } else { while (1) { tv.tv_sec=startup->max_inactivity; tv.tv_usec=0; FD_ZERO(&socket_set); FD_SET(sock,&socket_set); i=select(sock+1,&socket_set,NULL,NULL,&tv); if(i<1) { if(i==0) { if((time(NULL)-(*lastactive))>startup->max_inactivity) { lprintf(LOG_WARNING,"%04d Disconnecting due to to inactivity",sock); sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)." ,startup->max_inactivity); return(0); } continue; } return(i); } #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 DLLCALL 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 fname[MAX_PATH+1]; char str[256]; char tmp[128]; char username[128]; char host_ip[INET6_ADDRSTRLEN]; int i; int rd; int wr; long mod; ulong 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; fd_set socket_set; struct timeval tv; char *estr; xfer=*(xfer_t*)arg; free(arg); SetThreadName("sbbs/ftpSend"); thread_up(TRUE /* setuid */); length=flength(xfer.filename); if(length < 1) { 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); if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES)) 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; } if((fp=fnopen(NULL,xfer.filename,O_RDONLY|O_BINARY))==NULL /* non-shareable open failed */ && (fp=fopen(xfer.filename,"rb"))==NULL) { /* shareable open failed */ lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%s) line %d opening %s" ,xfer.ctrl_sock, xfer.user->alias, errno, strerror(errno), __LINE__, xfer.filename); sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 ERROR %d (%s) opening %s", errno, strerror(errno), xfer.filename); if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES)) ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias); 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); fseek(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 (using select) */ tv.tv_sec=1; tv.tv_usec=0; FD_ZERO(&socket_set); FD_SET(*xfer.data_sock,&socket_set); i=select((*xfer.data_sock)+1,NULL,&socket_set,NULL,&tv); if(i==SOCKET_ERROR) { lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d selecting socket %d for send" ,xfer.ctrl_sock, xfer.user->alias, ERROR_VALUE, *xfer.data_sock); sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer error."); error=TRUE; break; } if(i<1) continue; fseek(fp,xfer.filepos+total,SEEK_SET); rd=fread(buf,sizeof(char),sizeof(buf),fp); if(rd<1) /* EOF or READ error */ break; #ifdef SOCKET_DEBUG_SEND socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SEND; #endif 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=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) { memset(&f,0,sizeof(f)); #ifdef _WIN32 GetShortPathName(xfer.filename,fname,sizeof(fname)); #else SAFECOPY(fname,xfer.filename); #endif padfname(getfname(fname),f.name); f.dir=xfer.dir; f.size=total; if(getfileixb(&scfg,&f)==TRUE && getfiledat(&scfg,&f)==TRUE) { f.timesdled++; putfiledat(&scfg,&f); f.datedled=time32(NULL); putfileixb(&scfg,&f); lprintf(LOG_INFO,"%04d <%s> DATA downloaded: %s (%u times total)" ,xfer.ctrl_sock ,xfer.user->alias ,xfer.filename ,f.timesdled); /**************************/ /* Update Uploader's Info */ /**************************/ uploader.number=matchuser(&scfg,f.uler,TRUE /*sysop_alias*/); if(uploader.number && uploader.number!=xfer.user->number && getuserdat(&scfg,&uploader)==0 && uploader.firston<f.dateuled) { l=f.cdt; if(!(scfg.dir[f.dir]->misc&DIR_CDTDL)) /* Don't give credits on d/l */ l=0; if(scfg.dir[f.dir]->misc&DIR_CDTMIN && cps) { /* Give min instead of cdt */ mod=((ulong)(l*(scfg.dir[f.dir]->dn_pct/100.0))/cps)/60; adjustuserrec(&scfg,uploader.number,U_MIN,10,mod); sprintf(tmp,"%lu minute",mod); } else { mod=(ulong)(l*(scfg.dir[f.dir]->dn_pct/100.0)); adjustuserrec(&scfg,uploader.number,U_CDT,10,mod); ultoac(mod,tmp); } if(!(scfg.dir[f.dir]->misc&DIR_QUIET)) { addr_len = sizeof(addr); if(uploader.level>=SYSOP_LEVEL && getpeername(xfer.ctrl_sock,&addr.addr,&addr_len)==0 && inet_addrtop(&addr, host_ip, sizeof(host_ip))!=NULL) SAFEPRINTF2(username,"%s [%s]",xfer.user->alias,host_ip); else SAFECOPY(username,xfer.user->alias); /* Inform uploader of downloaded file */ safe_snprintf(str,sizeof(str),text[DownloadUserMsg] ,getfname(xfer.filename) ,xfer.filepos ? "partially FTP-" : "FTP-" ,username,tmp); putsmsg(&scfg,uploader.number,str); } } } if(!xfer.tmpfile && !xfer.delfile && !(scfg.dir[f.dir]->misc&DIR_NOSTAT)) inc_sys_download_stats(&scfg, 1, 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)) ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias); } else if(xfer.delfile && !error) ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias); #if defined(SOCKET_DEBUG_SENDTHREAD) socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SENDTHREAD; #endif thread_down(); } static void receive_thread(void* arg) { char* p; char str[128]; char buf[8192]; char ext[F_EXBSIZE+1]; char desc[F_EXBSIZE+1]; char cmd[MAX_PATH*2]; char tmp[MAX_PATH+1]; char fname[MAX_PATH+1]; int i; int rd; int file; 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; fd_set socket_set; struct timeval tv; 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); fseek(fp,xfer.filepos,SEEK_SET); last_report=start=time(NULL); while(1) { now=time(NULL); /* Periodic progress report */ if(total && now>=last_report+XFER_REPORT_INTERVAL) { if(xfer.filepos) sprintf(str," from offset %"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(startup->max_fsize && (xfer.filepos+total) > startup->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 (using select) */ tv.tv_sec=1; tv.tv_usec=0; FD_ZERO(&socket_set); FD_SET(*xfer.data_sock,&socket_set); i=select((*xfer.data_sock)+1,&socket_set,NULL,NULL,&tv); if(i==SOCKET_ERROR) { lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d selecting socket %d for receive" ,xfer.ctrl_sock, xfer.user->alias, ERROR_VALUE, *xfer.data_sock); sockprintf(xfer.ctrl_sock,sess,"426 Transfer error."); error=TRUE; break; } if(i<1) continue; #if defined(SOCKET_DEBUG_RECV_BUF) socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_RECV_BUF; #endif 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"); rd = -1; } } 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) ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias); } else { dur=(long)(time(NULL)-start); cps=dur ? total/dur : total*2; lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %"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)); #ifdef _WIN32 GetShortPathName(xfer.filename,fname,sizeof(fname)); #else SAFECOPY(fname,xfer.filename); #endif padfname(getfname(fname),f.name); f.dir=xfer.dir; filedat=getfileixb(&scfg,&f); if(scfg.dir[f.dir]->misc&DIR_AONLY) /* Forced anonymous */ f.misc|=FM_ANON; f.cdt=flength(xfer.filename); f.dateuled=time32(NULL); /* Description specified with DESC command? */ if(xfer.desc!=NULL && *xfer.desc!=0) SAFECOPY(f.desc,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 */ p=strrchr(f.name,'.'); if(p!=NULL && scfg.dir[f.dir]->misc&DIR_DIZ) { for(i=0;i<scfg.total_fextrs;i++) if(!stricmp(scfg.fextr[i]->ext,p+1) && chk_ar(&scfg,scfg.fextr[i]->ar,xfer.user,xfer.client)) break; if(i<scfg.total_fextrs) { sprintf(tmp,"%sFILE_ID.DIZ",scfg.temp_dir); if(fexistcase(tmp)) ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias); cmdstr(&scfg,xfer.user,scfg.fextr[i]->cmd,fname,"FILE_ID.DIZ",cmd); lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ: %s",xfer.ctrl_sock, xfer.user->alias,cmd); system(cmd); if(!fexistcase(tmp)) { sprintf(tmp,"%sDESC.SDI",scfg.temp_dir); if(fexistcase(tmp)) ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias); cmdstr(&scfg,xfer.user,scfg.fextr[i]->cmd,fname,"DESC.SDI",cmd); lprintf(LOG_DEBUG,"%04d <%s> DATA Extracting DIZ: %s",xfer.ctrl_sock, xfer.user->alias,cmd); system(cmd); fexistcase(tmp); /* fixes filename case */ } if((file=nopen(tmp,O_RDONLY))!=-1) { lprintf(LOG_DEBUG,"%04d <%s> DATA Parsing DIZ: %s",xfer.ctrl_sock, xfer.user->alias,tmp); memset(ext,0,sizeof(ext)); read(file,ext,sizeof(ext)-1); for(i=sizeof(ext)-1;i;i--) /* trim trailing spaces */ if(ext[i-1]>' ') break; ext[i]=0; if(!f.desc[0]) { /* use for normal description */ SAFECOPY(desc,ext); strip_exascii(desc, desc); /* strip extended ASCII chars */ prep_file_desc(desc, desc); /* strip control chars and dupe chars */ for(i=0;desc[i];i++) /* find appropriate first char */ if(IS_ALPHANUMERIC(desc[i])) break; SAFECOPY(f.desc,desc+i); } close(file); ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias); f.misc|=FM_EXTDESC; } else lprintf(LOG_DEBUG,"%04d <%s> DATA DIZ Does not exist: %s",xfer.ctrl_sock, xfer.user->alias,tmp); } } /* FILE_ID.DIZ support */ if(f.desc[0]==0) /* no description given, use (long) filename */ SAFECOPY(f.desc,getfname(xfer.filename)); SAFECOPY(f.uler,xfer.user->alias); /* exception here, Aug-27-2002 */ if(filedat) { if(!putfiledat(&scfg,&f)) lprintf(LOG_ERR,"%04d <%s> !DATA ERROR updating file (%s) in database" ,xfer.ctrl_sock, xfer.user->alias,f.name); /* need to update the index here */ } else { if(!addfiledat(&scfg,&f)) lprintf(LOG_ERR,"%04d <%s> !DATA ERROR adding file (%s) to database" ,xfer.ctrl_sock, xfer.user->alias,f.name); } if(f.misc&FM_EXTDESC) putextdesc(&scfg,f.dir,f.datoffset,ext); if(scfg.dir[f.dir]->upload_sem[0]) ftouch(scfg.dir[f.dir]->upload_sem); /**************************/ /* Update Uploader's Info */ /**************************/ user_uploaded(&scfg, xfer.user, (!xfer.append && xfer.filepos==0) ? 1:0, total); if(scfg.dir[f.dir]->up_pct && scfg.dir[f.dir]->misc&DIR_CDTUL) { /* credit for upload */ if(scfg.dir[f.dir]->misc&DIR_CDTMIN && cps) /* Give min instead of cdt */ xfer.user->min=adjustuserrec(&scfg,xfer.user->number,U_MIN,10 ,((ulong)(total*(scfg.dir[f.dir]->up_pct/100.0))/cps)/60); else xfer.user->cdt=adjustuserrec(&scfg,xfer.user->number,U_CDT,10 ,(ulong)(f.cdt*(scfg.dir[f.dir]->up_pct/100.0))); } if(!(scfg.dir[f.dir]->misc&DIR_NOSTAT)) inc_sys_upload_stats(&scfg, 1, total); } /* 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; 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; struct timeval tv; fd_set socket_set; 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)) 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)) ftp_remove(ctrl_sock, __LINE__, filename, user->alias); *inprogress=FALSE; return; } if(startup->socket_open!=NULL) startup->socket_open(startup->cbdata,TRUE); if(startup->options&FTP_OPT_DEBUG_DATA) lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d opened",ctrl_sock, user->alias,*data_sock); /* Use port-1 for all data connections */ reuseaddr=TRUE; setsockopt(*data_sock,SOL_SOCKET,SO_REUSEADDR,(char*)&reuseaddr,sizeof(reuseaddr)); addr_len = sizeof(server_addr); if((result=getsockname(ctrl_sock, &server_addr.addr,&addr_len))!=0) { lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) getting address/port of command socket (%u)" ,ctrl_sock, user->alias,result,ERROR_VALUE,pasv_sock); 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)) 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)) 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)) 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)); } /* Setup for select() */ tv.tv_sec=TIMEOUT_SOCKET_LISTEN; tv.tv_usec=0; FD_ZERO(&socket_set); FD_SET(pasv_sock,&socket_set); #if defined(SOCKET_DEBUG_SELECT) socket_debug[ctrl_sock]|=SOCKET_DEBUG_SELECT; #endif result=select(pasv_sock+1,&socket_set,NULL,NULL,&tv); #if defined(SOCKET_DEBUG_SELECT) socket_debug[ctrl_sock]&=~SOCKET_DEBUG_SELECT; #endif if(result<1) { lprintf(LOG_WARNING,"%04d <%s> PASV !DATA select returned %d (error: %d)" ,ctrl_sock, user->alias,result,ERROR_VALUE); sockprintf(ctrl_sock,ctrl_sess,"425 Error %d selecting socket for connection",ERROR_VALUE); if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES)) ftp_remove(ctrl_sock, __LINE__, filename, user->alias); *inprogress=FALSE; return; } 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)) 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)) 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); protected_uint32_adjust(&thread_count,1); if(receiving) result=_beginthread(receive_thread,0,(void*)xfer); else result=_beginthread(send_thread,0,(void*)xfer); if(result!=-1) return; /* success */ } while(0); /* failure */ if(tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES)) ftp_remove(ctrl_sock, __LINE__, filename, 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(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 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(p+strlen(BBS_VIRTUAL_PATH),user,client))<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]->sname); if (strlen(p) < len) continue; if (p[len] != 0 && p[len] != '/') continue; if(!strnicmp(scfg.lib[lib]->sname,p,len)) { p += len; if (*p) p++; break; } } if (lib == scfg.total_libs) { strcpy(filename, p); tmp = strchr(filename, '/'); if (tmp != NULL) *tmp = 0; if (ftpalias(filename, filename, user, client, &dir) == TRUE) { lib = scfg.dir[dir]->lib; if (strchr(p, '/') != NULL) { p = strchr(p, '/'); p++; } else p = strchr(p, 0); } else { ret = -1; lib = -1; if (strchr(p, '/') != NULL) { p = strchr(p, '/'); p++; } else p = strchr(p, 0); } } } else if (dir < 0) { for(dir=0;dir<scfg.total_dirs;dir++) { if(scfg.dir[dir]->lib!=lib) continue; if (!can_list(scfg.lib[lib], scfg.dir[dir], user, client)) continue; len = strlen(scfg.dir[dir]->code_suffix); if (strlen(p) < len) continue; if (p[len] != 0 && p[len] != '/') continue; if(!strnicmp(scfg.dir[dir]->code_suffix,p,len)) { p += len; if (*p) p++; break; } } if (dir == scfg.total_dirs) { ret = -1; dir = -1; if (strchr(p, '/') != NULL) { p = strchr(p, '/'); p++; } else p = strchr(p, 0); } } else { // Filename if (strchr(p, '/') != NULL) { ret = -1; p = strchr(p, '/'); p++; } else { fname = p; p += strlen(fname); } } } *curdir = dir; *curlib = lib; *pp = fname; return ret; } char* root_dir(char* path) { char* p; static char root[MAX_PATH+1]; 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]->sname); strcat(str,"/"); if(dir<0) return(str); strcat(str,scfg.dir[dir]->code_suffix); 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->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME); #endif return hacklog(&scfg, prot, user, text, host, addr); } /****************************************************************************/ /* Consecutive failed login (possible password hack) attempt tracking */ /****************************************************************************/ static BOOL badlogin(SOCKET sock, CRYPT_SESSION sess, ulong* login_attempts, char* user, char* passwd, char* host, union xp_sockaddr* addr) { ulong count; char host_ip[INET6_ADDRSTRLEN]; if(addr!=NULL) { count=loginFailure(startup->login_attempt_list, addr, "FTP", user, passwd); if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold) ftp_hacklog("FTP LOGIN", user, passwd, host, addr); if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold) { char reason[128]; SAFEPRINTF(reason, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS (%lu)", count); inet_addrtop(addr, host_ip, sizeof(host_ip)); filter_ip(&scfg, "FTP", reason, host, host_ip, 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; if (fp == NULL && sock == INVALID_SOCKET) return FALSE; va_start(va, format); if (vasprintf(&str, format, va) == -1) return FALSE; if (fp != NULL) fprintf(fp, "%s\r\n", str); else sockprintf(sock, sess, " %s", str); free(str); return TRUE; } static char *get_unique(const char *path, char *uniq) { BYTE digest[MD5_DIGEST_SIZE]; if (path == NULL) return NULL; MD5_calc(digest, path, strlen(path)); MD5_hex((BYTE*)uniq, digest); return uniq; } static BOOL send_mlsx_entry(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned feats, const char *type, const char *perm, uint64_t size, time_t modify, const char *owner, const char *unique, time_t ul, const char *fname) { char line[1024]; char *end; BOOL need_owner = FALSE; 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(&modify); end += sprintf(end, "Create=%04d%02d%02d%02d%02d%02d;", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); } // Owner can contain percents, so let send_mlsx() deal with it if (owner != NULL && (feats & MLSX_OWNER)) { strcat(end, "UNIX.ownername=%s;"); need_owner = TRUE; } strcat(end, " %s"); if (need_owner) return send_mlsx(fp, sock, sess, line, owner, fname==NULL ? "" : fname); return send_mlsx(fp, sock, sess, line, fname==NULL ? "" : fname); } static BOOL write_local_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned feats, const char *path, BOOL full_path) { 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, 0, 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, dir->ul_ar,user,client)) return TRUE; if ((user->exempt & FLAG('U'))) return TRUE; return FALSE; } static BOOL can_delete_files(lib_t *lib, dir_t *dir, user_t *user, client_t *client) { if (!chk_ar(&scfg,lib->ar,user,client)) return FALSE; if (user->rest&FLAG('D')) return FALSE; if (!chk_ar(&scfg,dir->ar,user,client)) return FALSE; if (dir_op(&scfg,user,client,dir->dirnum)) return TRUE; if (user->exempt&FLAG('R')) return TRUE; return FALSE; } static void get_dirperm(lib_t *lib, dir_t *dir, user_t *user, client_t *client, char *permstr) { char *p = permstr; //*(p++) = 'a'; // File may be appended to if (can_upload(lib, dir, user, client)) *(p++) = 'c'; // Files may be created in dir //*(p++) = 'd'; // Item may be depeted (dir or file) if (can_list(lib, dir, user, client)) { *(p++) = 'e'; // Can change to the dir //*(p++) = 'f'; // Item may be renamed *(p++) = 'l'; // Directory contents can be listed } //*(p++) = 'm'; // New subdirectories may be created if (can_delete_files(lib, dir, user, client)) *(p++) = 'p'; // Files/Dirs in directory may be deleted //*(p++) = 'r'; // File may be retrieved //*(p++) = 'w'; // File may be overwritten *p=0; } static BOOL can_append(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file) { if (!chk_ar(&scfg,lib->ar,user,client)) return FALSE; if (user->rest&FLAG('U')) return FALSE; if (dir->dirnum != scfg.sysop_dir && dir->dirnum != scfg.upload_dir && !chk_ar(&scfg,dir->ar,user,client)) return FALSE; if(!dir_op(&scfg,user,client,dir->dirnum) && !(user->exempt&FLAG('U'))) { if(!chk_ar(&scfg,dir->ul_ar,user,client)) return FALSE; } if(!getfileixb(&scfg,file) || !getfiledat(&scfg,file)) return FALSE; if (stricmp(file->uler,user->alias)) return FALSE; // Check credits? return TRUE; } static BOOL can_delete(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file) { if (user->rest&FLAG('D')) return FALSE; if (!chk_ar(&scfg,lib->ar,user,client)) return FALSE; if (!chk_ar(&scfg,dir->ar,user,client)) return FALSE; if (!dir_op(&scfg, user, client, dir->dirnum)) return FALSE; if (!(user->exempt&FLAG('R'))) return FALSE; if(!getfileixb(&scfg,file) && !(startup->options&FTP_OPT_DIR_FILES) && !(dir->misc&DIR_FILES)) return FALSE; return TRUE; } static BOOL can_download(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file) { if (user->rest&FLAG('D')) return FALSE; if (!chk_ar(&scfg,lib->ar,user,client)) return FALSE; if (!chk_ar(&scfg,dir->ar,user,client)) return FALSE; if (!chk_ar(&scfg,dir->dl_ar,user,client)) return FALSE; if(!getfileixb(&scfg,file) && !(startup->options&FTP_OPT_DIR_FILES) && !(dir->misc&DIR_FILES)) return FALSE; // TODO: Verify credits return TRUE; } static void get_fileperm(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file, char *permstr) { char *p = permstr; if (can_append(lib, dir, user, client, file)) *(p++) = 'a'; // File may be appended to //*(p++) = 'c'; // Files may be created in dir if (can_delete(lib, dir, user, client, file)) *(p++) = 'd'; // Item may be depeted (dir or file) //*(p++) = 'e'; // Can change to the dir //*(p++) = 'f'; // Item may be renamed //*(p++) = 'l'; // Directory contents can be listed //*(p++) = 'm'; // New subdirectories may be created //*(p++) = 'p'; // Files/Dirs in directory may be deleted if (can_download(lib, dir, user, client, file)) *(p++) = 'r'; // File may be retrieved //*(p++) = 'w'; // File may be overwritten *p = 0; } static void get_owner_name(file_t *file, char *namestr) { char *p; if (file) { if (file->misc & FM_ANON) strcpy(namestr, ANONYMOUS); else strcpy(namestr, file->uler); } else strcpy(namestr, scfg.sys_id); // Now ensure it's an RCHAR string. for (p=namestr; *p; p++) { if (*p >= '!' && *p <= ')') continue; else if (*p >= '+' && *p <= ':') continue; else if (*p >= '?' && *p <= 'Z') continue; else if (*p == '\\') continue; else if (*p == '^') continue; else if (*p == '_') continue; else if (*p >= 'a' && *p <= 'z') continue; else if (*p == ' ') *p = '.'; else *p = '_'; } } static void ctrl_thread(void* arg) { unsigned mlsx_feats = (MLSX_TYPE | MLSX_PERM | MLSX_SIZE | MLSX_MODIFY | MLSX_OWNER | MLSX_UNIQUE | MLSX_CREATE); char buf[512]; char str[128]; char uniq[33]; char* cmd; char* p; char* np; char* tp; char* dp; char* ap; char* filespec; char* mode="active"; char old_char; char 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; ulong 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; file_t f; glob_t g; 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->answer_sound[0] && !(startup->options&FTP_OPT_MUTE)) PlaySound(startup->answer_sound, 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; } 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"; 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=matchuser(&scfg,user.alias,FALSE /*sysop_alias*/); if(!user.number && (stricmp(user.alias,"anonymous") == 0 || stricmp(user.alias, "ftp") == 0)) user.number=matchuser(&scfg,"guest",FALSE); if(user.number && getuserdat(&scfg, &user)==0 && user.pass[0]==0) sockprintf(sock,sess,"331 User name okay, give your full e-mail address as password."); 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=matchuser(&scfg,user.alias,FALSE /*sysop_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, host_name, &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); putuserrec(&scfg,user.number,U_NETMAIL,LEN_NETMAIL,password); } else if(user.level>=SYSOP_LEVEL && !stricmp(password,sys_pass)) { lprintf(LOG_INFO,"%04d <%s> Sysop access granted", sock, user.alias); sysop=TRUE; } 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, host_name, &ftp.client_addr)) break; continue; } /* Update client display */ if(user.pass[0]) { client.user=user.alias; loginSuccess(startup->login_attempt_list, &ftp.client_addr); } else { /* anonymous */ sprintf(str,"%s <%.32s>",user.alias,password); client.user=str; } client.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.cdt+user.freecdt)>0) sockprintf(sock,sess,"230-You have %lu download credits." ,user.cdt+user.freecdt); 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); 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)) { 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__); } 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)); if(data_port< IPPORT_RESERVED) { 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 %lu bytes available.",avail); else sockprintf(sock,sess,"200 %lu 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(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(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; f.size = st.st_size; t = st.st_mtime; if(localtime_r(&t,&tm)==NULL) memset(&tm,0,sizeof(tm)); fprintf(fp,"%crw-r--r-- 1 %-8s local %9"PRId32" %s %2d " ,*lastchar(g.gl_pathv[i]) == '/' ? 'd':'-' ,scfg.sys_id ,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 ,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(np+strlen(BBS_VIRTUAL_PATH),&user,&client))<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]->sname, scfg.dir[dir]->code_suffix, tp); } else { alias_dir=TRUE; SAFEPRINTF2(aliaspath,"/%s/%s", scfg.lib[scfg.dir[dir]->lib]->sname, scfg.dir[dir]->code_suffix); } } 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]->sname, mls_fname) != 0) continue; if (cmd[3] == 'T') sockprintf(sock,sess, "250- Listing %s", scfg.lib[i]->sname); 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]->sname); l++; } } else if(dir<0) { if (cmd[3] == 'T' && !*mls_fname) { sockprintf(sock,sess, "250- Listing %s", scfg.lib[lib]->sname); get_owner_name(NULL, str); SAFEPRINTF(aliaspath, "/%s", scfg.lib[lib]->sname); 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]->sname); 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]->sname, 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]->code_suffix, mls_fname) != 0) continue; if (cmd[3] == 'T') sockprintf(sock,sess, "250- Listing %s", scfg.dir[i]->code_suffix); get_dirperm(scfg.lib[lib], scfg.dir[i], &user, &client, permstr); get_owner_name(NULL, str); SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->sname, scfg.dir[i]->code_suffix); 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]->code_suffix); 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]->sname, scfg.dir[dir]->code_suffix,mode); if (cmd[3] == 'T' && !*mls_fname) { sockprintf(sock,sess, "250- Listing %s/%s",scfg.lib[lib]->sname,scfg.dir[dir]->code_suffix); get_owner_name(NULL, str); SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix); 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]->sname); send_mlsx_entry(fp, sock, sess, mlsx_feats, "pdir", permstr, UINT64_MAX, 0, str, NULL, 0, aliaspath); SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix); get_unique(aliaspath, uniq); send_mlsx_entry(fp, sock, sess, mlsx_feats, "cdir", permstr, UINT64_MAX, 0, str, NULL, 0, aliaspath); } time_t start = time(NULL); SAFEPRINTF2(path,"%s%s",scfg.dir[dir]->path,"*"); glob(path, GLOB_MARK, NULL, &g); for(i=0;i<(int)g.gl_pathc;i++) { if(*lastchar(g.gl_pathv[i]) == '/') /* is directory */ continue; #ifdef _WIN32 GetShortPathName(g.gl_pathv[i], str, sizeof(str)); #else SAFECOPY(str,g.gl_pathv[i]); #endif padfname(getfname(str),f.name); f.dateuled = 0; f.dir=dir; if((filedat=getfileixb(&scfg,&f))==FALSE && !(startup->options&FTP_OPT_DIR_FILES) && !(scfg.dir[dir]->misc&DIR_FILES)) continue; if (cmd[3] != 'D' && strcmp(getfname(g.gl_pathv[i]), 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]->sname, scfg.dir[dir]->code_suffix, getfname(g.gl_pathv[i])); get_unique(aliaspath, uniq); f.size = f.cdt; f.date = f.dateuled; if(!filedat || (scfg.dir[dir]->misc&DIR_FCHK)) { struct stat st; if(stat(g.gl_pathv[i], &st) != 0) continue; f.size = st.st_size; f.date = (time32_t)st.st_mtime; } send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", permstr, f.size, f.date, str, uniq, f.dateuled, cmd[3] == 'T' ? mls_path : getfname(g.gl_pathv[i])); 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]->sname, scfg.dir[dir]->code_suffix ,(ulong)g.gl_pathc, (long)time(NULL) - start); } globfree(&g); } else lprintf(LOG_INFO,"%04d <%s> %s listing: /%s/%s directory in %s mode (empty - no access)" ,sock, user.alias, cmd, scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix, 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]->sname : scfg.dir[dir]->code_suffix ,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=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(np+strlen(BBS_VIRTUAL_PATH),&user,&client))<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]->sname ,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]->sname, 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]->sname); else fprintf(fp,"%s\r\n",scfg.lib[i]->sname); } } else if(dir<0) { lprintf(LOG_INFO,"%04d <%s> %slisting: %s library in %s mode" ,sock, user.alias, detail ? "detailed ":"", scfg.lib[lib]->sname, 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]->code_suffix, 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]->sname ,512L ,ftp_mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min ,scfg.dir[i]->code_suffix); else fprintf(fp,"%s\r\n",scfg.dir[i]->code_suffix); } } 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]->sname, scfg.dir[dir]->code_suffix, mode); time_t start = time(NULL); SAFEPRINTF2(path,"%s%s",scfg.dir[dir]->path,filespec); glob(path, GLOB_MARK, NULL, &g); for(i=0;i<(int)g.gl_pathc;i++) { if(*lastchar(g.gl_pathv[i]) == '/') /* is directory */ continue; #ifdef _WIN32 GetShortPathName(g.gl_pathv[i], str, sizeof(str)); #else SAFECOPY(str,g.gl_pathv[i]); #endif padfname(getfname(str),f.name); f.dir=dir; if((filedat=getfileixb(&scfg,&f))==FALSE && !(startup->options&FTP_OPT_DIR_FILES) && !(scfg.dir[dir]->misc&DIR_FILES)) continue; if(detail) { if(filedat && !getfiledat(&scfg,&f)) continue; f.size = f.cdt; t = f.dateuled; if(!filedat || (scfg.dir[dir]->misc&DIR_FCHK)) { struct stat st; if(stat(g.gl_pathv[i], &st) != 0) continue; f.size = st.st_size; t = st.st_mtime; } if(localtime_r(&t,&tm)==NULL) memset(&tm,0,sizeof(tm)); if(filedat) { if(f.misc&FM_ANON) SAFECOPY(str,ANONYMOUS); else dotname(f.uler,str); } else SAFECOPY(str,scfg.sys_id); fprintf(fp,"-r--r--r-- 1 %-*s %-8s %9"PRId32" %s %2d " ,NAME_LEN ,str ,scfg.dir[dir]->code_suffix ,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 ,getfname(g.gl_pathv[i])); else fprintf(fp,"%5d %s\r\n" ,1900+tm.tm_year ,getfname(g.gl_pathv[i])); } else fprintf(fp,"%s\r\n",getfname(g.gl_pathv[i])); } 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]->sname, scfg.dir[dir]->code_suffix ,(ulong)g.gl_pathc, (long)time(NULL) - start); globfree(&g); } else lprintf(LOG_INFO,"%04d <%s> %slisting: /%s/%s directory in %s mode (empty - no access)" ,sock, user.alias, detail ? "detailed ":"", scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix, 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++; } else 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]->sname,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]->code_suffix,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); 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."); 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]->sname,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]->code_suffix,scfg.dir[i]->lname); } } else if(chk_ar(&scfg,scfg.dir[dir]->ar,&user,&client)){ sprintf(cmd,"%s*",scfg.dir[dir]->path); time_t start = time(NULL); glob(cmd, GLOB_MARK, NULL, &g); for(i=0;i<(int)g.gl_pathc;i++) { if(*lastchar(g.gl_pathv[i]) == '/') /* is directory */ continue; #ifdef _WIN32 GetShortPathName(g.gl_pathv[i], str, sizeof(str)); #else SAFECOPY(str,g.gl_pathv[i]); #endif memset(&f, 0, sizeof(f)); padfname(getfname(str),f.name); f.dir=dir; if((filedat=getfileixb(&scfg,&f))==FALSE && !(startup->options&FTP_OPT_DIR_FILES) && !(scfg.dir[dir]->misc&DIR_FILES)) continue; f.size = -1; // Not used, don't query if(filedat && !getfiledat(&scfg,&f)) continue; fprintf(fp,"%-*s %s\r\n",INDEX_FNAME_LEN ,getfname(g.gl_pathv[i]),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]->sname, scfg.dir[dir]->code_suffix ,(ulong)g.gl_pathc, (long)time(NULL) - start); globfree(&g); } 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]->sname ,scfg.dir[dir]->code_suffix); sockprintf(sock,sess,"550 Insufficient access."); filepos=0; continue; } if(!getsize && !getdate && !delecmd && !chk_ar(&scfg,scfg.dir[dir]->dl_ar,&user,&client)) { lprintf(LOG_WARNING,"%04d <%s> has insufficient access to download from /%s/%s" ,sock,user.alias ,scfg.lib[scfg.dir[dir]->lib]->sname ,scfg.dir[dir]->code_suffix); sockprintf(sock,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]->sname ,scfg.dir[dir]->code_suffix); sockprintf(sock,sess,"550 Insufficient access."); filepos=0; continue; } SAFEPRINTF2(fname,"%s%s",scfg.dir[dir]->path,p); #ifdef _WIN32 GetShortPathName(fname, str, sizeof(str)); #else SAFECOPY(str,fname); #endif padfname(getfname(str),f.name); f.dir=dir; f.cdt=0; f.size=-1; filedat=getfileixb(&scfg,&f); if(!filedat && !(startup->options&FTP_OPT_DIR_FILES) && !(scfg.dir[dir]->misc&DIR_FILES)) { 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)) { if(filedat) getfiledat(&scfg,&f); else f.cdt=flength(fname); if(f.cdt>(user.cdt+user.freecdt)) { lprintf(LOG_WARNING,"%04d <%s> has insufficient credit to download /%s/%s/%s (%lu credits)" ,sock,user.alias,scfg.lib[scfg.dir[dir]->lib]->sname ,scfg.dir[dir]->code_suffix ,p ,(ulong)f.cdt); sockprintf(sock,sess,"550 Insufficient credit (%lu required).", (ulong)f.cdt); filepos=0; continue; } } 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) removefiledat(&scfg,&f); 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++; } else if(!strncmp(p,"./",2)) p+=2; /* Need to add support for uploading to aliased directories */ if(lib<0 && (tp=strchr(p,'/'))!=NULL) { dir=-1; *tp=0; for(i=0;i<scfg.total_libs;i++) { if(!chk_ar(&scfg,scfg.lib[i]->ar,&user,&client)) continue; if(!stricmp(scfg.lib[i]->sname,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]->code_suffix,p)) break; } if(i<scfg.total_dirs) dir=i; p=tp+1; } 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 to invalid directory" ,sock,user.alias); 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); } 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)) { lprintf(LOG_WARNING,"%04d <%s> cannot upload to /%s/%s (insufficient access)" ,sock,user.alias ,scfg.lib[scfg.dir[dir]->lib]->sname ,scfg.dir[dir]->code_suffix); 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: %ld files)" ,sock,user.alias ,scfg.lib[scfg.dir[dir]->lib]->sname ,scfg.dir[dir]->code_suffix ,getfiles(&scfg,dir)); sockprintf(sock,sess,"553 Directory full."); continue; } } if(*p=='-' || strcspn(p,ILLEGAL_FILENAME_CHARS)!=strlen(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; } 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 */ #ifdef _WIN32 GetShortPathName(fname, str, sizeof(str)); #else SAFECOPY(str,fname); #endif padfname(getfname(str),f.name); f.dir=dir; f.cdt=0; f.size=-1; if(!getfileixb(&scfg,&f) || !getfiledat(&scfg,&f)) { if(filepos) { lprintf(LOG_WARNING,"%04d <%s> file (%s) not in database for %.4s command" ,sock,user.alias,fname,cmd); sockprintf(sock,sess,"550 File not found: %s",p); continue; } append=FALSE; } /* Verify user is original uploader */ if((append || filepos) && stricmp(f.uler,user.alias)) { lprintf(LOG_WARNING,"%04d <%s> !cannot resume upload of %s, uploaded by %s" ,sock,user.alias,fname,f.uler); sockprintf(sock,sess,"553 Insufficient access (can't resume upload from different user)."); continue; } } lprintf(LOG_INFO,"%04d <%s> uploading: %s to %s (%s) in %s mode" ,sock,user.alias ,p /* filename */ ,genvpath(lib,dir,str) /* virtual path */ ,scfg.dir[dir]->path /* actual path */ ,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 ,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]->sname,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]->code_suffix,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]->sname); else sockprintf(sock,sess,"257 \"/%s/%s\" is current directory." ,scfg.lib[curlib]->sname ,scfg.dir[curdir]->code_suffix); continue; } if(!strnicmp(cmd, "MKD", 3) || !strnicmp(cmd,"XMKD",4) || !strnicmp(cmd,"SITE EXEC",9)) { lprintf(LOG_WARNING,"%04d <%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); lprintf(LOG_INFO,"%04d <%s> logged off", sock, user.alias); } #ifdef _WIN32 if(startup->hangup_sound[0] && !(startup->options&FTP_OPT_MUTE)) PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME); #endif /* status(STATUS_WFC); server thread should control status display */ 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(&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(); status("Down"); if(terminate_server || code) lprintf(LOG_INFO,"#### FTP Server thread terminated (%lu clients served)", served); if(startup!=NULL && startup->terminated!=NULL) startup->terminated(startup->cbdata,code); } const char* DLLCALL 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 DLLCALL 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; } 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"); protected_uint32_adjust(&thread_count,1); thread_up(FALSE /* setuid */); status("Initializing"); 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; } 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(); strlwr(scfg.sys_id); /* Use lower-case unix-looking System ID for group name */ for(i=0;i<scfg.total_libs;i++) { strlwr(scfg.lib[i]->sname); dotname(scfg.lib[i]->sname,scfg.lib[i]->sname); } /* 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); status(STATUS_WFC); /* Setup recycle/shutdown semaphore file lists */ shutdown_semfiles=semfile_list_init(scfg.ctrl_dir,"shutdown","ftp"); recycle_semfiles=semfile_list_init(scfg.ctrl_dir,"recycle","ftp"); 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 */ if(startup->started!=NULL) startup->started(startup->cbdata); lprintf(LOG_INFO,"FTP Server thread started"); 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 !MAXIMUM CLIENTS (%d) reached, access denied" ,client_socket, 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; protected_uint32_adjust(&thread_count,1); _beginthread(ctrl_thread, 0, ftp); served++; } #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); }