Skip to content
Snippets Groups Projects
ftpsrvr.c 156 KiB
Newer Older
							,scfg.lib[lib]->sname
							,ftp_mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min
						fprintf(fp,"%s\r\n",scfg.dir[i]->code_suffix);
rswindell's avatar
rswindell committed
			} 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);
				smb_t smb;
				if((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) {
					lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file);
					continue;
				}
				time_t start = time(NULL);
				size_t file_count = 0;
				file_t* file_list = loadfiles(&smb
					,filespec, /* time: */0, file_detail_normal, scfg.dir[dir]->sort, &file_count);
				for(size_t i = 0; i < file_count; i++) {
					file_t* f = &file_list[i];
						f->size = f->cost;
						t = f->hdr.when_imported.time;
						if(scfg.dir[dir]->misc&DIR_FCHK) {
							if(stat(getfilepath(&scfg, f, path), &st) != 0)
						if(f->hdr.attr & MSG_ANONYMOUS)
							SAFECOPY(str,ANONYMOUS);
						else
							dotname(f->from,str);
						fprintf(fp,"-r--r--r--   1 %-*s %-8s %9"PRId64" %s %2d "
						if(tm.tm_year==cur_tm.tm_year)
							fprintf(fp,"%02d:%02d %s\r\n"
								,tm.tm_hour,tm.tm_min
						fprintf(fp,"%s\r\n", f->name);
				lprintf(LOG_INFO, "%04d <%s> %slisting (%ld bytes) of /%s/%s (%lu files) created in %ld seconds"
					,sock, user.alias, detail ? "detailed ":"", ftell(fp), scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix
					,(ulong)file_count, (long)time(NULL) - start);
				freefiles(file_list, file_count);
				smb_close(&smb);
				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);
deuce's avatar
deuce committed
			filexfer(&data_addr,sock,sess,pasv_sock,pasv_sess,&data_sock,&data_sess,fname,0L
				,&transfer_inprogress,&transfer_aborted
				,TRUE /* delfile */
				,TRUE /* tmpfile */
deuce's avatar
deuce committed
				,&lastactive,&user,&client,dir,FALSE,FALSE,FALSE,NULL,protection);
		if(!strnicmp(cmd, "RETR ", 5) 
			|| !strnicmp(cmd, "SIZE ",5) 
			|| !strnicmp(cmd, "MDTM ",5)
			|| !strnicmp(cmd, "DELE ",5)) {
			getdate=FALSE;
			getsize=FALSE;
			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')) {
deuce's avatar
deuce committed
				sockprintf(sock,sess,"550 Insufficient access.");
				filepos=0;
				continue;
			}
			credits=TRUE;
			success=FALSE;
			delfile=FALSE;
			tmpfile=FALSE;
			lib=curlib;
			dir=curdir;

			p=cmd+5;

			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;

rswindell's avatar
rswindell committed
			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(!success && lib<0 && (tp=strchr(p,'/'))!=NULL) {
				dir=-1;
				*tp=0;
				for(i=0;i<scfg.total_libs;i++) {
rswindell's avatar
rswindell committed
					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;
rswindell's avatar
rswindell committed
					if(!chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
					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 
					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;
					}

					while(fexist(str) && !terminate_server) {
						if(!socket_check(sock,NULL,NULL,0))
							break;
						if(time(NULL)-t>startup->qwk_timeout)
							break;
						mswait(1000);
					}
					if(!socket_check(sock,NULL,NULL,0)) {
						lprintf(LOG_NOTICE,"%04d <%s> disconnected while waiting for QWK packet creation"
							,sock, user.alias);
						(void)ftp_remove(sock, __LINE__, str, user.alias);
						lprintf(LOG_WARNING,"%04d <%s> !TIMEOUT waiting for QWK packet creation", sock, user.alias);
deuce's avatar
deuce committed
						sockprintf(sock,sess,"451 Time-out waiting for packet creation.");
						(void)ftp_remove(sock, __LINE__, str, user.alias);
						lprintf(LOG_INFO,"%04d <%s> No QWK Packet created (no new messages)", sock, user.alias);
deuce's avatar
deuce committed
						sockprintf(sock,sess,"550 No QWK packet created (no new messages)");
				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;
					lprintf(LOG_INFO,"%04d <%s> downloading QWK packet (%"PRIuOFF" bytes) in %s mode"
			/* ASCII Index File */
			} else if(startup->options&FTP_OPT_INDEX_FILE 
				&& !stricmp(p,startup->index_file_name)
				&& !delecmd) {
deuce's avatar
deuce committed
					sockprintf(sock,sess, "550 Size not available for dynamically generated files");
				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);
deuce's avatar
deuce committed
					sockprintf(sock,sess, "451 Insufficient system storage");
					lprintf(LOG_INFO,"%04d <%s> downloading %s for %s in %s mode"
						,sock, user.alias, startup->index_file_name, genvpath(lib,dir,str)
					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;
								fprintf(fp,"%-*s %s\r\n",INDEX_FNAME_LEN,p,np);
							}
						/* 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++) {
rswindell's avatar
rswindell committed
							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;
rswindell's avatar
rswindell committed
							if(i!=(int)scfg.sysop_dir && i!=(int)scfg.upload_dir
rswindell's avatar
rswindell committed
								&& !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);
						}
rswindell's avatar
rswindell committed
					} else if(chk_ar(&scfg,scfg.dir[dir]->ar,&user,&client)){
						smb_t smb;
						if((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) {
							lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file);
							continue;
						}
						time_t start = time(NULL);
						size_t file_count = 0;
						file_t* file_list = loadfiles(&smb
							,/* filespec */NULL, /* time: */0, file_detail_normal, scfg.dir[dir]->sort, &file_count);
						for(size_t i = 0; i < file_count; i++) {
							file_t* f = &file_list[i];
							fprintf(fp,"%-*s %s\r\n",INDEX_FNAME_LEN
						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)file_count, (long)time(NULL) - start);
						freefiles(file_list, file_count);
						smb_close(&smb);
rswindell's avatar
rswindell committed
				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);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"550 Insufficient access.");
rswindell's avatar
rswindell committed
					&& !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);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"550 Insufficient access.");
				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);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"550 Insufficient access.");
				SAFEPRINTF2(fname,"%s%s",scfg.dir[dir]->path,p);
				filedat=findfile(&scfg, dir, p, NULL);
				if(!filedat) {
deuce's avatar
deuce committed
					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);

				/* Verify credits */
				if(!getsize && !getdate && !delecmd
rswindell's avatar
rswindell committed
					&& !is_download_free(&scfg,dir,&user,&client)) {
						loadfile(&scfg, dir, p, &f, file_detail_normal);
						f.cost=(uint32_t)flength(fname);
					if(f.cost>(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
							,(ulong)f.cost);
						sockprintf(sock,sess,"550 Insufficient credit (%lu required).", (ulong)f.cost);
				}

				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);
							lprintf(LOG_INFO,"%04d <%s> downloading: %s (%"PRIuOFF" bytes) in %s mode"
								,sock,user.alias,fname,flength(fname)
#if defined(SOCKET_DEBUG_DOWNLOAD)
			socket_debug[sock]|=SOCKET_DEBUG_DOWNLOAD;
deuce's avatar
deuce committed
				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));
deuce's avatar
deuce committed
				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);
					lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) deleting %s", sock, user.alias, errno, strerror(errno), fname);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"450 %s could not be deleted (error: %d)"
					lprintf(LOG_NOTICE,"%04d <%s> deleted %s",sock,user.alias,fname);
						removefile(&scfg, dir, getfname(fname));
deuce's avatar
deuce committed
					sockprintf(sock,sess,"250 %s deleted.",fname);
			} else if(success) {
deuce's avatar
deuce committed
				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
deuce's avatar
deuce committed
					,&lastactive,&user,&client,dir,FALSE,credits,FALSE,NULL,protection);
deuce's avatar
deuce committed
				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);
#if defined(SOCKET_DEBUG_DOWNLOAD)
			socket_debug[sock]&=~SOCKET_DEBUG_DOWNLOAD;
			continue;
		}

		if(!strnicmp(cmd, "DESC", 4)) {

			if(user.rest&FLAG('U')) {
deuce's avatar
deuce committed
				sockprintf(sock,sess,"553 Insufficient access.");
deuce's avatar
deuce committed
				sockprintf(sock,sess,"501 No file description given.");
				SAFECOPY(desc,p);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"200 File description set. Ready to STOR file.");
		if(!strnicmp(cmd, "STOR ", 5) || !strnicmp(cmd, "APPE ", 5)) {

			if(user.rest&FLAG('U')) {
deuce's avatar
deuce committed
				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);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Transfer already in progress.");
			lib=curlib;
			dir=curdir;
			p=cmd+5;


			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++) {
rswindell's avatar
rswindell committed
					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;
rswindell's avatar
rswindell committed
					if(i!=(int)scfg.sysop_dir && i!=(int)scfg.upload_dir 
rswindell's avatar
rswindell committed
						&& !chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
					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);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"553 Invalid directory.");
				sprintf(fname,"%sfile/%04d.rep",scfg.data_dir,user.number);
				lprintf(LOG_INFO,"%04d <%s> uploading: %s in %s mode"
					,sock,user.alias,fname

				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);
deuce's avatar
deuce committed
						sockprintf(sock,sess,"553 Insufficient access.");
					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));
deuce's avatar
deuce committed
						sockprintf(sock,sess,"553 Directory full.");
				if(*p=='-'
					|| strcspn(p,ILLEGAL_FILENAME_CHARS)!=strlen(p)
					lprintf(LOG_WARNING,"%04d <%s> !ILLEGAL FILENAME ATTEMPT by %s [%s]: %s"
						,sock, user.alias, host_name, host_ip, p);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"553 Illegal filename attempt");
					ftp_hacklog("FTP FILENAME", user.alias, cmd, host_name, &ftp.client_addr);
				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);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"553 File already exists.");
				if(append || filepos) {	/* RESUME */
					file_t f;
					if(!loadfile(&scfg, dir, p, &f, file_detail_normal)) {
							lprintf(LOG_WARNING,"%04d <%s> file (%s) not in database for %.4s command"
								,sock,user.alias,fname,cmd);
deuce's avatar
deuce committed
							sockprintf(sock,sess,"550 File not found: %s",p);
					}
					/* Verify user is original uploader */
					if((append || filepos) && stricmp(f.from, user.alias)) {
						lprintf(LOG_WARNING,"%04d <%s> !cannot resume upload of %s, uploaded by %s"
							,sock,user.alias,fname,f.from);
deuce's avatar
deuce committed
						sockprintf(sock,sess,"553 Insufficient access (can't resume upload from different user).");
				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 */
deuce's avatar
deuce committed
			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
rswindell's avatar
rswindell committed
				,&client
				,dir
				,TRUE	/* uploading */
				,TRUE	/* credits */
deuce's avatar
deuce committed
				,protection
				);
			filepos=0;
			continue;
		}

		if(!stricmp(cmd,"CDUP") || !stricmp(cmd,"XCUP")) {
			if(curdir<0)
				curlib=-1;
			else
				curdir=-1;
deuce's avatar
deuce committed
			sockprintf(sock,sess,"200 CDUP command successful.");
			continue;
		}

		if(!strnicmp(cmd, "CWD ", 4) || !strnicmp(cmd,"XCWD ",5)) {
			p=cmd+4;

			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)) {
deuce's avatar
deuce committed
					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;
deuce's avatar
deuce committed
				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? */
rswindell's avatar
rswindell committed
			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++) {
rswindell's avatar
rswindell committed
					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;
				for(i=0;i<scfg.total_dirs;i++) {
					if(scfg.dir[i]->lib!=curlib)
						continue;
rswindell's avatar
rswindell committed
					if(i!=(int)scfg.sysop_dir && i!=(int)scfg.upload_dir
rswindell's avatar
rswindell committed
						&& !chk_ar(&scfg,scfg.dir[i]->ar,&user,&client))
					if(!stricmp(scfg.dir[i]->code_suffix,p))
						break;
				}
				if(i<scfg.total_dirs) {
					curdir=i;
					success=TRUE;
				} else
					success=FALSE;
			}

			if(success)
deuce's avatar
deuce committed
				sockprintf(sock,sess,"250 CWD command successful.");
				sockprintf(sock,sess,"550 %s: No such directory.",p);
				curlib=orglib;
				curdir=orgdir;
			}
			continue;
		}

		if(!stricmp(cmd, "PWD") || !stricmp(cmd,"XPWD")) {
			if(curlib<0)
deuce's avatar
deuce committed
				sockprintf(sock,sess,"257 \"/\" is current directory.");
			else if(curdir<0)
deuce's avatar
deuce committed
				sockprintf(sock,sess,"257 \"/%s\" is current directory."
					,scfg.lib[curlib]->sname);
			else
deuce's avatar
deuce committed
				sockprintf(sock,sess,"257 \"/%s/%s\" is current directory."
					,scfg.lib[curlib]->sname
					,scfg.dir[curdir]->code_suffix);

		if(!strnicmp(cmd, "MKD", 3) || 
			!strnicmp(cmd,"XMKD",4) || 
			!strnicmp(cmd,"SITE EXEC",9)) {
			lprintf(LOG_WARNING,"%04d <%s> !SUSPECTED HACK ATTEMPT: %s"
			ftp_hacklog("FTP", user.alias, cmd, host_name, &ftp.client_addr);
		// TODO: STAT is mandatory
deuce's avatar
deuce committed
		sockprintf(sock,sess,"500 Syntax error: '%s'",cmd);
		lprintf(LOG_WARNING,"%04d <%s> !UNSUPPORTED COMMAND: %s"
#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);
		while(transfer_inprogress==TRUE) {
deuce's avatar
deuce committed
			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);
deuce's avatar
deuce committed
					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);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"421 Disconnecting due to inactivity (%u seconds)."
deuce's avatar
deuce committed
					ftp_close_socket(&data_sock,&data_sess,__LINE__);
				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);
rswindell's avatar
rswindell committed
#ifdef _WIN32
	if(startup->hangup_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
		PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME);
rswindell's avatar
rswindell committed
#endif
/*	status(STATUS_WFC); server thread should control status display */
rswindell's avatar
rswindell committed

deuce's avatar
deuce committed
		ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
deuce's avatar
deuce committed
		ftp_close_socket(&data_sock,&data_sess,__LINE__);
rswindell's avatar
rswindell committed

#ifdef SOCKET_DEBUG_CTRL
	socket_debug[sock]&=~SOCKET_DEBUG_CTRL;
#if defined(SOCKET_DEBUG_TERMINATE)
	socket_debug[sock]&=~SOCKET_DEBUG_TERMINATE;
#endif
rswindell's avatar
rswindell committed

deuce's avatar
deuce committed
	ftp_close_socket(&tmp_sock,&sess,__LINE__);
		int32_t	clients = protected_uint32_adjust_fetch(&active_clients, -1);
		int32_t	threads = thread_down();
		lprintf(LOG_INFO,"%04d CTRL thread terminated (%d clients and %d threads remain, %lu served)"
			,sock, clients, threads, served);
rswindell's avatar
rswindell committed
static void cleanup(int code, int line)
rswindell's avatar
rswindell committed
#ifdef _DEBUG
	lprintf(LOG_DEBUG,"0000 cleanup called from line %d",line);
rswindell's avatar
rswindell committed
#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);
deuce's avatar
deuce committed
	if(ftp_set != NULL) {
		xpms_destroy(ftp_set, ftp_close_socket_cb, NULL);
		ftp_set = NULL;
	}
	update_clients();	/* active_clients is destroyed below */
	listFree(&current_connections);

	if(protected_uint32_value(active_clients))
		lprintf(LOG_WARNING,"!!!! Terminating with %d active clients", protected_uint32_value(active_clients));
		protected_uint32_destroy(active_clients);
	if(WSAInitialized && WSACleanup()!=0) 
		lprintf(LOG_ERR,"0000 !WSACleanup ERROR %d",ERROR_VALUE);
rswindell's avatar
rswindell committed
	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* 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
#ifdef _DEBUG
		," Debug"
#else
		,""
#endif
		,__DATE__, __TIME__, compiler);

	return(ver);
}

void ftp_server(void* arg)
	char			compiler[32];
deuce's avatar
deuce committed
	union xp_sockaddr client_addr;
	socklen_t		client_addr_len;
	SOCKET			client_socket;
	int				i;
	time_t			t;
	time_t			start;
deuce's avatar
deuce committed
	char			client_ip[INET6_ADDRSTRLEN];
deuce's avatar
deuce committed
	CRYPT_SESSION		none = -1;

	startup=(ftp_startup_t*)arg;
	SetThreadName("sbbs/ftpServer");
	if(thread_suid_broken)
		startup->seteuid(TRUE);
    if(startup==NULL) {
    	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;
	}

	startup->recycle_now=FALSE;
	protected_uint32_init(&thread_count, 0);
		listInit(&current_connections, LINK_LIST_MUTEX);
		protected_uint32_init(&active_clients, 0);
		/* Setup intelligent defaults */
		if(startup->port==0)					startup->port=IPPORT_FTP;
		if(startup->qwk_timeout==0)				startup->qwk_timeout=FTP_DEFAULT_QWK_TIMEOUT;		/* seconds */
		if(startup->max_inactivity==0)			startup->max_inactivity=FTP_DEFAULT_MAX_INACTIVITY;	/* seconds */
		if(startup->sem_chk_freq==0)			startup->sem_chk_freq=DEFAULT_SEM_CHK_FREQ;		/* seconds */
		if(startup->index_file_name[0]==0)		SAFECOPY(startup->index_file_name,"00index");
		(void)protected_uint32_adjust(&thread_count,1);
		memset(&scfg, 0, sizeof(scfg));
		lprintf(LOG_INFO,"Synchronet FTP Server Version %s%c%s"
			,VERSION, REVISION
		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 */
rswindell's avatar
rswindell committed
			cleanup(1,__LINE__);
		lprintf(LOG_INFO,"Initializing on %.24s with options: %x"
			,ctime_r(&t,str),startup->options);