Skip to content
Snippets Groups Projects
ftpsrvr.c 177 KiB
Newer Older
				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),"w+b"))==NULL) {
					lprintf(LOG_ERR,"%04d !ERROR %d opening %s",sock,errno,fname);
deuce's avatar
deuce committed
					sockprintf(sock,sess, "451 Insufficient system storage");
					lprintf(LOG_INFO,"%04d %s downloading index for %s in %s mode"
						,sock,user.alias,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(startup->options&FTP_OPT_HTML_INDEX_FILE)
						fprintf(fp,"%-*s File/Folder Descriptions (HTML)\r\n"
							,INDEX_FNAME_LEN,startup->html_index_file);
					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);
						glob(cmd,0,NULL,&g);
						for(i=0;i<(int)g.gl_pathc;i++) {
							if(isdir(g.gl_pathv[i]))
								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(getfileixb(&scfg,&f)) {
								f.size=flength(g.gl_pathv[i]);
								getfiledat(&scfg,&f);
								fprintf(fp,"%-*s %s\r\n",INDEX_FNAME_LEN
									,getfname(g.gl_pathv[i]),f.desc);
							}
			/* HTML Index File */
			} else if(startup->options&FTP_OPT_HTML_INDEX_FILE 
				&& (!stricmp(p,startup->html_index_file) 
				|| !strnicmp(p,html_index_ext,strlen(html_index_ext)))
				&& !delecmd) {
deuce's avatar
deuce committed
					sockprintf(sock,sess, "550 Size not available for dynamically generated files");
				else if(getdate)
					file_date=time(NULL);
				else {
#ifdef JAVASCRIPT
					if(startup->options&FTP_OPT_NO_JAVASCRIPT) {
						lprintf(LOG_ERR,"%04d !JavaScript disabled, cannot generate %s",sock,fname);
deuce's avatar
deuce committed
						sockprintf(sock,sess, "451 JavaScript disabled");
					if(js_runtime == NULL) {
						lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes"
							,sock,startup->js.max_bytes);

						if((js_runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__))==NULL) {
							lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",sock);
deuce's avatar
deuce committed
							sockprintf(sock,sess,"451 Error creating JavaScript runtime");
					if(js_cx==NULL) {	/* Context not yet created, create it now */
						/* js_initcx() starts a request */
						if(((js_cx=js_initcx(js_runtime, sock,&js_glob,&js_ftp,&js_callback))==NULL)) {
							lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript context",sock);
deuce's avatar
deuce committed
							sockprintf(sock,sess,"451 Error initializing JavaScript context");
							filepos=0;
							continue;
						}
						if(js_CreateUserClass(js_cx, js_glob, &scfg)==NULL) 
							lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user class",sock);
						if(js_CreateFileClass(js_cx, js_glob)==NULL) 
							lprintf(LOG_ERR,"%04d !JavaScript ERROR creating file class",sock);
						if(js_CreateUserObject(js_cx, js_glob, &scfg, "user", &user, &client, /* global_user: */TRUE)==NULL) 
							lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user object",sock);
						if(js_CreateClientObject(js_cx, js_glob, "client", &client, sock, -1)==NULL) 
							lprintf(LOG_ERR,"%04d !JavaScript ERROR creating client object",sock);
rswindell's avatar
rswindell committed
						if(js_CreateFileAreaObject(js_cx, js_glob, &scfg, &user, &client
							,startup->html_index_file)==NULL) 
							lprintf(LOG_ERR,"%04d !JavaScript ERROR creating file area object",sock);
					}

					if((js_str=JS_NewStringCopyZ(js_cx, "name"))!=NULL) {
						js_val=STRING_TO_JSVAL(js_str);
						JS_SetProperty(js_cx, js_ftp, "sort", &js_val);
					}
					js_val=BOOLEAN_TO_JSVAL(FALSE);
					JS_SetProperty(js_cx, js_ftp, "reverse", &js_val);

					if(!strnicmp(p,html_index_ext,strlen(html_index_ext))) {
						p+=strlen(html_index_ext);
						tp=strrchr(p,'$');
						if(tp!=NULL)
							*tp=0;
						if(!strnicmp(p,"ext=",4)) {
							p+=4;
							if(!strcmp(p,"on"))
								user.misc|=EXTDESC;
							else
								user.misc&=~EXTDESC;
							if(!(user.rest&FLAG('G')))
								putuserrec(&scfg,user.number,U_MISC,8,ultoa(user.misc,str,16));
						} 
						else if(!strnicmp(p,"sort=",5)) {
							p+=5;
							tp=strchr(p,'&');
							if(tp!=NULL) {
								*tp=0;
								tp++;
								if(!stricmp(tp,"reverse")) {
									js_val=BOOLEAN_TO_JSVAL(TRUE);
									JS_SetProperty(js_cx, js_ftp, "reverse", &js_val);
								}
							}
							if((js_str=JS_NewStringCopyZ(js_cx, p))!=NULL) {
								js_val=STRING_TO_JSVAL(js_str);
								JS_SetProperty(js_cx, js_ftp, "sort", &js_val);
					js_val=BOOLEAN_TO_JSVAL(INT_TO_BOOL(user.misc&EXTDESC));
					JS_SetProperty(js_cx, js_ftp, "extended_descriptions", &js_val);
#endif
					if((fp=fopen(ftp_tmpfname(fname,"html",sock),"w+b"))==NULL) {
						lprintf(LOG_ERR,"%04d !ERROR %d opening %s",sock,errno,fname);
deuce's avatar
deuce committed
						sockprintf(sock,sess, "451 Insufficient system storage");
					lprintf(LOG_INFO,"%04d %s downloading HTML index for %s in %s mode"
						,sock,user.alias,genvpath(lib,dir,str)
					js_val=INT_TO_JSVAL(timeleft);
					if(!JS_SetProperty(js_cx, js_ftp, "time_left", &js_val))
						lprintf(LOG_ERR,"%04d !JavaScript ERROR setting user.time_left",sock);
rswindell's avatar
rswindell committed
					js_generate_index(js_cx, js_ftp, sock, fp, lib, dir, &user, &client);
			} else if(dir>=0) {

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
deuce's avatar
deuce committed
						sockprintf(sock,sess,"550 Insufficient credit (%lu required).",f.cdt);
						filepos=0;
						continue;
					}
				}

				if(strcspn(p,ILLEGAL_FILENAME_CHARS)!=strlen(p)) {
					success=FALSE;
					lprintf(LOG_WARNING,"%04d !ILLEGAL FILENAME ATTEMPT by %s: %s"
						,sock,user.alias,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 !ERROR %d deleting %s",sock,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 !TRANSFER already in progress (%s)",sock,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: %u 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 !ILLEGAL FILENAME ATTEMPT by %s: %s"
						,sock,user.alias,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))
					|| (startup->options&FTP_OPT_HTML_INDEX_FILE 
						&& !stricmp(p,startup->html_index_file))
					) {
					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.");
deuce's avatar
deuce committed
				sockprintf(sock,sess,"550 %s: No such file or 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 !SUSPECTED HACK ATTEMPT by %s: '%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 !UNSUPPORTED COMMAND from %s: '%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 !ERROR in logoutuserdat",sock);
		/* Remove QWK-pack semaphore file (if left behind) */
		sprintf(str,"%spack%04u.now",scfg.data_dir,user.number);
		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
#ifdef JAVASCRIPT
	if(js_cx!=NULL) {
		lprintf(LOG_DEBUG,"%04d JavaScript: Destroying context",sock);
		JS_BEGINREQUEST(js_cx);
		JS_RemoveObjectRoot(js_cx, &js_glob);
		JS_ENDREQUEST(js_cx);
		JS_DestroyContext(js_cx);	/* Free Context */
	}
		lprintf(LOG_DEBUG,"%04d JavaScript: Destroying runtime",sock);
/*	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(&active_clients, -1);
		int32_t	threads = thread_down();
		lprintf(LOG_INFO,"%04d CTRL thread terminated (%ld clients and %ld 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_DEBUG,"#### FTP Server waiting for %d child threads to terminate", protected_uint32_value(thread_count)-1);
		while(protected_uint32_value(thread_count) > 1) {
			mswait(100);
		}
	}

	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 */
	if(protected_uint32_value(active_clients))
		lprintf(LOG_WARNING,"#### !FTP Server terminating with %ld 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* DLLCALL ftp_ver(void)
{
	static char ver[256];
	char compiler[32];

	DESCRIBE_COMPILER(compiler);
	sscanf("$Revision$", "%*s %s", revision);
		"Compiled %s %s with %s"
		,FTP_SERVER
#ifdef _DEBUG
		," Debug"
#else
		,""
#endif
		,__DATE__, __TIME__, compiler);

	return(ver);
}

	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;
	}

	ZERO_VAR(js_server_props);
	SAFEPRINTF2(js_server_props.version,"%s %s",FTP_SERVER,revision);
	js_server_props.version_detail=ftp_ver();
	js_server_props.clients=&active_clients.value;
	js_server_props.options=&startup->options;
	js_server_props.interfaces=&startup->interfaces;
	startup->recycle_now=FALSE;
	protected_uint32_init(&thread_count, 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");
		if(startup->html_index_file[0]==0)		SAFECOPY(startup->html_index_file,"00index.html");
		if(startup->html_index_script[0]==0)	SAFECOPY(startup->html_index_script,"ftp-html.js");
		if(startup->options&FTP_OPT_HTML_INDEX_FILE)
			startup->options&=~FTP_OPT_NO_JAVASCRIPT;
		else
			startup->options|=FTP_OPT_NO_JAVASCRIPT;
		if(startup->js.max_bytes==0)			startup->js.max_bytes=JAVASCRIPT_MAX_BYTES;
		if(startup->js.cx_stack==0)				startup->js.cx_stack=JAVASCRIPT_CONTEXT_STACK;
		protected_uint32_adjust(&thread_count,1);
		memset(&scfg, 0, sizeof(scfg));
		lprintf(LOG_INFO,"Synchronet FTP Server Revision %s%s"
		DESCRIBE_COMPILER(compiler);
		lprintf(LOG_INFO,"Compiled %s %s with %s", __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: %lx"
			,ctime_r(&t,str),startup->options);
		if(chdir(startup->ctrl_dir)!=0)
			lprintf(LOG_ERR,"!ERROR %d changing directory to: %s", 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);
		SAFECOPY(error,UNKNOWN_LOAD_ERROR);
		if(!load_cfg(&scfg, text, TRUE, error)) {
			lprintf(LOG_CRIT,"!ERROR %s",error);
			lprintf(LOG_CRIT,"!Failed to load configuration files");
rswindell's avatar
rswindell committed
			cleanup(1,__LINE__);
			SAFECOPY(startup->host_name,scfg.sys_inetaddr);
		if((t=checktime())!=0) {   /* Check binary time */
			lprintf(LOG_ERR,"!TIME PROBLEM (%ld)",t);
			uptime=time(NULL);	/* this must be done *after* setting the timezone */
			SAFECOPY(scfg.temp_dir,startup->temp_dir);
			SAFECOPY(scfg.temp_dir,"../temp");
	   	prep_dir(scfg.ctrl_dir, scfg.temp_dir, sizeof(scfg.temp_dir));
		if(!isdir(scfg.temp_dir) && MKDIR(scfg.temp_dir) != 0) {
			lprintf(LOG_ERR, "Error %d creating temp directory: %s", errno, scfg.temp_dir);
			cleanup(1,__LINE__);
			break;