Skip to content
Snippets Groups Projects
ftpsrvr.c 160 KiB
Newer Older
							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);
					}
				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 */

						if(*p==';')	/* comment */
							continue;

						tp=p;		/* terminator pointer */
						if(*tp) *tp=0;

						np=tp+1;	/* filename pointer */

						tp=np;		/* terminator pointer */
						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))) {
rswindell's avatar
rswindell committed
							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);
							lprintf(LOG_WARNING,"%04d <%s> !Missing aliased file: %s", sock, user.alias, np);

						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
									,ftp_mon[cur_tm.tm_mon],cur_tm.tm_mday,cur_tm.tm_hour,cur_tm.tm_min
									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++) {
rswindell's avatar
rswindell committed
					if(!chk_ar(&scfg,scfg.lib[i]->ar,&user,&client))
					if(!wildmatchi(scfg.lib[i]->sname, filespec, FALSE))
						continue;
						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
							,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;
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(!wildmatchi(scfg.dir[i]->code_suffix, filespec, FALSE))
						continue;
						fprintf(fp,"drwxrwxrwx   1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
							,NAME_LEN
							,scfg.sys_id
							,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);
				time_t start = time(NULL);
				SAFEPRINTF2(path,"%s%s",scfg.dir[dir]->path,filespec);
				glob(path, GLOB_MARK, NULL, &g);
					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]);
						&& !(startup->options&FTP_OPT_DIR_FILES)
						&& !(scfg.dir[dir]->misc&DIR_FILES))
						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(filedat) {
							if(f.misc&FM_ANON)
								SAFECOPY(str,ANONYMOUS);
							SAFECOPY(str,scfg.sys_id);
deuce's avatar
deuce committed
						fprintf(fp,"-r--r--r--   1 %-*s %-8s %9"PRId32" %s %2d "
						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);
				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)){
						sprintf(cmd,"%s*",scfg.dir[dir]->path);
						time_t start = time(NULL);
						glob(cmd, GLOB_MARK, NULL, &g);
							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);
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);
				GetShortPathName(fname, str, sizeof(str));
				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)) {
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)) {
					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
							,(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);
							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);
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 */
#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)) {
							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.uler,user.alias)) {
						lprintf(LOG_WARNING,"%04d <%s> !cannot resume upload of %s, uploaded by %s"
							,sock,user.alias,fname,f.uler);
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)