Skip to content
Snippets Groups Projects
ftpsrvr.c 160 KiB
Newer Older
deuce's avatar
deuce committed
							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);
deuce's avatar
deuce committed
						sockprintf(sock,sess, "521 Address family not supported");
deuce's avatar
deuce committed
						continue;
				}
deuce's avatar
deuce committed

			inet_addrtop(&data_addr, data_ip, sizeof(data_ip));
			bool bounce_allowed = (startup->options & FTP_OPT_ALLOW_BOUNCE) && !(user.rest & FLAG('G'));
			if(data_port < IPPORT_RESERVED
				|| (memcmp(&data_addr, &ftp.client_addr, ftp.client_addr_len) != 0 && !bounce_allowed)) {
				lprintf(LOG_WARNING,"%04d <%s> !SUSPECTED BOUNCE ATTACK ATTEMPT to %s port %u"
deuce's avatar
deuce committed
					,data_ip,data_port);
				ftp_hacklog("FTP BOUNCE", user.alias, cmd, host_name, &ftp.client_addr);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"504 Bad port number.");	
				continue; /* As recommended by RFC2577 */
			}
deuce's avatar
deuce committed
			inet_setaddrport(&data_addr, data_port);
deuce's avatar
deuce committed
			sockprintf(sock,sess,"200 PORT Command successful.");
		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) {
deuce's avatar
deuce committed
			if(pasv_sock!=INVALID_SOCKET)
				ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
deuce's avatar
deuce committed
			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);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d opening PASV data socket", ERROR_VALUE);
			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);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d disabling REUSEADDR socket option", ERROR_VALUE);
			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);
deuce's avatar
deuce committed
				inet_setaddrport(&pasv_addr, port);
deuce's avatar
deuce committed
				if((result=bind(pasv_sock, &pasv_addr.addr,xp_sockaddr_len(&pasv_addr)))==0)
			if(result!= 0) {
				lprintf(LOG_ERR,"%04d <%s> !PASV ERROR %d (%d) binding socket to port %u"
					,sock, user.alias, result, ERROR_VALUE, port);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d binding data socket",ERROR_VALUE);
				ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
			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);
deuce's avatar
deuce committed
			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);
deuce's avatar
deuce committed
				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);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"425 Error %d listening on data socket",ERROR_VALUE);
				ftp_close_socket(&pasv_sock,&pasv_sess,__LINE__);
deuce's avatar
deuce committed
			port=inet_addrport(&addr);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"229 Entering Extended Passive Mode (|||%hu|)", port);
deuce's avatar
deuce committed
			else if (stricmp(cmd,"LPSV")==0) {
				switch(addr.addr.sa_family) {
					case AF_INET:
deuce's avatar
deuce committed
						sockprintf(sock,sess, "228 Entering Long Passive Mode (4, 4, %d, %d, %d, %d, 2, %d, %d)"
deuce's avatar
deuce committed
							,((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:
deuce's avatar
deuce committed
						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)"
deuce's avatar
deuce committed
							,((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) 
deuce's avatar
deuce committed
					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)"
deuce's avatar
deuce committed
						,sock
deuce's avatar
deuce committed
						,(ip_addr>>24)&0xff
						,(ip_addr>>16)&0xff
						,(ip_addr>>8)&0xff
						,ip_addr&0xff
						);
deuce's avatar
deuce committed
				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)
deuce's avatar
deuce committed
			}
			continue;
		}

		if(!strnicmp(cmd, "TYPE ",5)) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"200 All files sent in BINARY mode.");
			continue;
		}

		if(!strnicmp(cmd, "ALLO",4)) {
			p=cmd+5;
			if(*p)
				l=atol(p);	
			else
				l=0;
			if(local_fsys)
				avail=getfreediskspace(local_dir,0);
				avail=getfreediskspace(scfg.data_dir,0);	/* Change to temp_dir? */
			if(l && l>avail)
deuce's avatar
deuce committed
				sockprintf(sock,sess,"504 Only %lu bytes available.",avail);
deuce's avatar
deuce committed
				sockprintf(sock,sess,"200 %lu bytes available.",avail);
			continue;
		}

		if(!strnicmp(cmd, "REST",4)) {
			p=cmd+4;
			if(*p)
				filepos=atol(p);
			else
				filepos=0;
			sockprintf(sock,sess,"350 Restarting at %"PRIdOFF". Send STORE or RETRIEVE to initiate transfer."
			continue;
		}

		if(!strnicmp(cmd, "MODE ",5)) {
			p=cmd+5;
			if(toupper(*p)!='S')
deuce's avatar
deuce committed
				sockprintf(sock,sess,"504 Only STREAM mode supported.");
deuce's avatar
deuce committed
				sockprintf(sock,sess,"200 STREAM mode.");
			continue;
		}

		if(!strnicmp(cmd, "STRU ",5)) {
			p=cmd+5;
			if(toupper(*p)!='F')
deuce's avatar
deuce committed
				sockprintf(sock,sess,"504 Only FILE structure supported.");
deuce's avatar
deuce committed
				sockprintf(sock,sess,"200 FILE structure.");
			continue;
		}

		if(!stricmp(cmd, "SYST")) {
deuce's avatar
deuce committed
			sockprintf(sock,sess,"215 UNIX Type: L8");
			continue;
		}

		if(!stricmp(cmd, "ABOR")) {
			if(!transfer_inprogress)
				sockprintf(sock,sess,"226 No transfer in progress.");
				lprintf(LOG_WARNING,"%04d <%s> aborting transfer"
				transfer_aborted=TRUE;
				YIELD(); /* give send thread time to abort */
deuce's avatar
deuce committed
				sockprintf(sock,sess,"226 Transfer aborted.");
			}
			continue;
		}

		if(!strnicmp(cmd,"SMNT ",5) && sysop && !(startup->options&FTP_OPT_NO_LOCAL_FSYS)) {
			p=cmd+5;
			if(!stricmp(p,BBS_FSYS_DIR)) 
				local_fsys=FALSE;
			else {
				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;
				}
				local_fsys=TRUE;
				SAFECOPY(local_dir,p);
deuce's avatar
deuce committed
			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') {
deuce's avatar
deuce committed
						write_local_mlsx(NULL, sock, sess, mlsx_feats, path, TRUE);
						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);
					}
			}

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


				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);
deuce's avatar
deuce committed
				sockprintf(sock,sess, "150 Directory of %s%s", local_dir, filespec);
					memset(&cur_tm,0,sizeof(cur_tm));
				time_t start = time(NULL);
				glob(path, GLOB_MARK, NULL, &g);
					char fpath[MAX_PATH + 1];
					SAFECOPY(fpath, g.gl_pathv[i]);
					if(*lastchar(fpath) == '/')
						*lastchar(fpath) = 0;
						if(stat(fpath, &st) != 0)
							continue;
						f.size = st.st_size;
						t = st.st_mtime;
							memset(&tm,0,sizeof(tm));
deuce's avatar
deuce committed
						fprintf(fp,"%crw-r--r--   1 %-8s local %9"PRId32" %s %2d "
							,*lastchar(g.gl_pathv[i]) == '/' ? 'd':'-'
							,scfg.sys_id
							,f.size
						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));
						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);
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,-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;
				tp=p;
				if(*tp=='/' || *tp=='\\') /* /local: and /bbs: are valid */
					tp++;
				if(!strnicmp(tp,BBS_FSYS_DIR,strlen(BBS_FSYS_DIR))) {
					local_fsys=FALSE;
deuce's avatar
deuce committed
					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);
deuce's avatar
deuce committed
				else if(*p=='/' || *p=='\\') {
					SAFEPRINTF2(path,"%s%s",root_dir(local_dir),p+1);
					p = FULLPATH(NULL, path, 0);
					SAFECOPY(path, p);
					free(p);
				}
					SAFEPRINTF2(fname,"%s%s",local_dir,p);
					FULLPATH(path,fname,sizeof(path));
deuce's avatar
deuce committed
					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);
deuce's avatar
deuce committed
					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)
deuce's avatar
deuce committed
					sockprintf(sock,sess,"550 Directory does not exist.");
deuce's avatar
deuce committed
					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 '/' */

deuce's avatar
deuce committed
				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;
				if(*p=='/')	/* absolute */
					SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
				else		/* relative */
					SAFEPRINTF2(fname,"%s%s",local_dir,p);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"257 \"%s\" directory created",fname);
					lprintf(LOG_NOTICE,"%04d <%s> created directory: %s",sock,user.alias,fname);
					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;
				if(*p=='/')	/* absolute */
					SAFEPRINTF2(fname,"%s%s",root_dir(local_dir),p+1);
				else		/* relative */
					SAFEPRINTF2(fname,"%s%s",local_dir,p);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"250 \"%s\" directory removed",fname);
					lprintf(LOG_NOTICE,"%04d <%s> removed directory: %s",sock,user.alias,fname);
					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;
				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)) {
deuce's avatar
deuce committed
					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
deuce's avatar
deuce committed
					sockprintf(sock,sess,"350 File exists, ready for destination name");
				continue;
			}

			if(!strnicmp(cmd, "RNTO ",5)) {
				p=cmd+5;
				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) {
deuce's avatar
deuce committed
					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);
					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;

				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);
deuce's avatar
deuce committed
					sockprintf(sock,sess,"550 File not found: %s",fname);
					continue;
				}
				if(!strnicmp(cmd,"SIZE ",5)) {
deuce's avatar
deuce committed
					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));
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);					
					continue;
				}
				if(!strnicmp(cmd,"DELE ",5)) {
					if(ftp_remove(sock, __LINE__, fname, user.alias) == 0) {
deuce's avatar
deuce committed
						sockprintf(sock,sess,"250 \"%s\" removed successfully.",fname);
						lprintf(LOG_NOTICE,"%04d <%s> deleted file: %s",sock,user.alias,fname);
						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);
				lprintf(LOG_INFO,"%04d <%s> downloading: %s (%"PRIuOFF" bytes) in %s mode"
					,sock,user.alias,fname,flength(fname)
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
deuce's avatar
deuce committed
					,&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;

				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
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
					,-1		/* dir */
					,TRUE	/* uploading */
					,FALSE	/* credits */
					,!strnicmp(cmd,"APPE",4) ? TRUE : FALSE	/* append */
					,NULL	/* desc */
deuce's avatar
deuce committed
					,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);
				}

				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);
					if (cmd[3] == 'T' && !*mls_fname) {
						sockprintf(sock,sess, "250- Listing root");
						get_owner_name(NULL, str);
						send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", (startup->options&FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, aliaspath);
deuce's avatar
deuce committed
					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? */
							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);
									SAFEPRINTF2(aliaspath,"/%s/%s", scfg.lib[scfg.dir[dir]->lib]->sname, scfg.dir[dir]->code_suffix);
								}
								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);
					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);
					if (cmd[3] == 'D') {
						get_owner_name(NULL, str);
deuce's avatar
deuce committed
						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);
					if (cmd[3] == 'D') {
						get_libperm(scfg.lib[lib], &user, &client, permstr);
						get_owner_name(NULL, str);
deuce's avatar
deuce committed
						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);
						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]));
deuce's avatar
deuce committed
					if (cmd[3] == 'D') {
						lprintf(LOG_INFO, "%04d <%s> %s listing (%ld bytes) of /%s/%s (%lu files) created in %ld seconds"
deuce's avatar
deuce committed
						    ,sock, user.alias, cmd, ftell(fp), scfg.lib[lib]->sname, scfg.dir[dir]->code_suffix
						    ,(ulong)g.gl_pathc, (long)time(NULL) - start);
					}
					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;

			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);
deuce's avatar
deuce committed
				sockprintf(sock,sess, "451 Insufficient system storage");
			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);
				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)) {
					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
						,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);
				if(startup->options&FTP_OPT_ALLOW_QWK) {
					SAFEPRINTF(str,"%s.qwk",scfg.sys_id);
					if(wildmatchi(str, filespec, FALSE)) {
						if(detail) {