using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
public class MainApp
{
public const String Version = "0.0.1";
public static String szFtpRoot = @"E:/incoming";
public static bool fDebug = true;
//服务器管理界面:
public static void Usage( )
{
Console.WriteLine("ftpd usage:");
Console.WriteLine();
//改变ftp目录
Console.WriteLine("-r [path]/t:/tSpecifies FTP Root");
//帮助
Console.WriteLine("-? /t:/tPrints this help");
return;
}
public static int Main( String[] Arguments )
{
for( int i = 0; i < Arguments.Length; i++ )
{
switch( Arguments [ i ] )
{
//设定ftp目录
case "-r":
szFtpRoot = Arguments[ i + 1 ];
break;
case "-?":
Usage();
return 0;
default:
//无参数运行时ftp目录设为当前目录
szFtpRoot = Directory.GetCurrentDirectory();
break;
}
}
//创建一个Ftpd对象实例
Ftpd pFtpd = new Ftpd();
//启动ftp服务
if( pFtpd.StartServer() == false )
{
Console.WriteLine( "Failed to start FTP Server." );
return -1;
}
return 0;
}
}
//会话类,管理会话的信息
public class SessionInfo
{
public bool fBinary = false;
public bool fPassive = false;
public String szFtpRoot = MainApp.szFtpRoot;
public String szUsername;
public PassiveInfo pi;
}
public struct PassiveInfo
{
//IPHostEntry类,属于System.Net名称空间,存储主机信息
public IPHostEntry iphostentry;
public TcpListener tcpListener;
//32位无符号数,描述端口
public Int32 iPort;
}
//Ftp服务器类
public class Ftpd
{
//服务器接收一个客户请求后,产生的服务套接字
public Socket s;
public TcpListener TCPListener;
//属于System.Net名称空间,用IP地址和端口号表示一个网络终端
public IPEndPoint LocalIPEndPoint;
public IPEndPoint localEP;
public IPEndPoint remoteEP;
public Ftpd()
{
//实例化一个在端口21监听的TCPListener
TCPListener = new TcpListener(21);
}
//开始服务
public bool StartServer()
{
try
{
//开始监听
TCPListener.Start();
//循环
while( true )
{
//接收客户端的一个连接请求,返回一个套接字
s = TCPListener.AcceptSocket();
/* NOTE: Would be using System.Threading.ThreadPool(s) but they are not supported on 9x systems */
//创建一个线程,处理当前连接
Thread client = new Thread( new ThreadStart( ServeConnection ) );
//线程开始运行
client.Start();
}
}
catch( SocketException Se )
{
DBG_TRACE( "An Socket Class Exception Has Occured" );
DBG_TRACE( "Error: {0}", Se.ErrorCode );
return false;
}
}
//:::
// Function:
// Reply()
//
// Purpose:
// Sends a status command back to the client, with a descriptive message.
//
// Parameters:
// Socket sSocket - Client's Connected Socket
// int iResponseCode - Response Code to Send
// String szMessage - Message to send
//:::
public void Reply( Socket sSocket, int iResponseCode, String szMessage )
{
try
{
String szResponse = "" + iResponseCode + " " + szMessage + "/r/n";
//将字符串转换成ASCII码
Byte[] OutputBytes = GetBytes( szResponse );
//通过套接字发送
sSocket.Send( OutputBytes );
}
catch(Exception e)
{
}
return;
}
//:::
// Function:
// GetBytes()
//
// Purpose:
// Returns a Byte Array of the Bytes which formulate the String szMessage
//
// Parameters:
// String szMessage - Message to Convert
//
//:::
public Byte[] GetBytes( String szMessage )
{
//先把String类型的字符串转化为Unicode的字符数组,然后再转为ASCII码的Byte数组
return System.Text.Encoding.ASCII.GetBytes( szMessage.ToCharArray() );
}
//:::
// Function:
// ServeConnection()
//
// Purpose:
// 处理一个请求
//:::
public void ServeConnection( )
{
Socket sSocket = s;
//创建一个会话
SessionInfo si = new SessionInfo();
//显示服务器版本信息
String szMessage = " FTPD Server v" + MainApp.Version + "";
//送到客户端显示
Reply(sSocket, 220, szMessage);
ParseInputs(sSocket, si);
return;
}
//处理客户端发送到服务器的命令
public void ParseInputs( Socket sSocket, SessionInfo si )
{
String Username = "";
String Password = "";
try
{
while( true )
{
Byte[] ReceivedBytes = new Byte[256];
//接收到的信息放在一个Byte数组中
sSocket.Receive( ReceivedBytes );
String Command = "";
String Parameter = "";
for( int i = 0; i < 4; i++ )
{
//如果该ASCII码不是回车换行符则将它加到Command字符串末尾
if( ReceivedBytes[i] != 0 && ReceivedBytes[i] != 13 && ReceivedBytes[i] != 10 && ReceivedBytes[i] != 32 )
{
Command += (char)ReceivedBytes[i];
}
}
if( Command != "CWD" && Command != "PWD" )
{
for( int i = 5; i < ReceivedBytes.Length; i++ )
{
if( ReceivedBytes[i] != 0 && ReceivedBytes[i] != 13 && ReceivedBytes[i] != 10 )
{
// DBG_TRACE("Char: {0} :: Byte {1}", (char)ReceivedBytes[i], ReceivedBytes[i]);
Parameter += (char)ReceivedBytes[i];
}
}
}
else
{
for( int i = 4; i < ReceivedBytes.Length; i++ )
{
if( Command != "CWD" )
{
if( ReceivedBytes[i] != 0 && ReceivedBytes[i] != 13 && ReceivedBytes[i] != 10 )
{
// DBG_TRACE( "Char: {0} :: Byte {1}", (char)ReceivedBytes[i], ReceivedBytes[i] );
Parameter += (char)ReceivedBytes[i];
}
}
else
{
if( ReceivedBytes[i] != 0 && ReceivedBytes[i] != 13 && ReceivedBytes[i] != 10 )
{
// DBG_TRACE("Char: {0} :: Byte {1}", (char)ReceivedBytes[i], ReceivedBytes[i]);
Parameter += (char)ReceivedBytes[i];
}
}
}
}
Parameter.Trim();
System.Console.Write("From "+sSocket.RemoteEndPoint+" : "+Command+" "+Parameter+"/n");
//客户端传给服务器的ftp命令都是这样的格式:前四位为命令类型,后面为命令参数
switch( Command )
{
//用户
case "USER":
Username = Parameter;
Reply( sSocket, 331, "Password required for " + Parameter + "." );
break;
//密码:
case "PASS":
Password = Parameter;
AuthenticateUser( Username, Password, sSocket );
break;
//
case "EPSV":
Reply( sSocket, 522, "Extended Passive Mode not supported." );
break;
//Passive模式
case "PASV":
ProcessPassiveCommand( sSocket, Parameter, si );
break;
case "PORT":
ProcessPortCommand( sSocket,Parameter );
break;
//文件及子目录列表
case "LIST":
ProcessListCommand( sSocket,Parameter, si );
break;
//发送客户端下载的文件
case "RETR":
ProcessRetreiveCommand( sSocket, Parameter, si );
break;
case "NLST":
ProcessListCommand( sSocket,Parameter, si );
break;
case "SYST":
Reply( sSocket, 215, ".NET" );
break;
case "STOR":
ProcessStoreCommand( sSocket, Parameter, si );
break;
//改变工作目录
case "CWD":
ProcessCWDCommand( sSocket, Parameter, si );
break;
case "CDUP":
si.szFtpRoot = MainApp.szFtpRoot;
Reply( sSocket, 250, "CWD Command successful" );
break;
//当前工作目录
case "XPWD":
ProcessPWDCommand( sSocket, Parameter, si );
break;
//传输模式:Binary或ASCII
case "TYPE":
switch( Parameter )
{
case "I":
si.fBinary = true;
Reply( sSocket, 200, "Type set to I." );
break;
case "A":
si.fBinary = false;
Reply( sSocket, 200, "Type set to A." );
break;
default:
break;
}
break;
//当前工作目录
case "PWD":
ProcessPWDCommand( sSocket, Parameter, si );
break;
//退出
case "QUIT":
sSocket.Close();
return;
default:
Reply( sSocket, 502, "'" + Command + "': not implemented." );
break;
}
}
}
catch(Exception e)
{
}
}
//Passive模式,该模式下,服务器会为每一个会话提供一个TcpListener监听
public void ProcessPassiveCommand( Socket sSocket, String Parameter, SessionInfo si )
{
//得到主机地址:
IPHostEntry LocalHostEntry = Dns.GetHostByName(Dns.GetHostName());
//为服务请求在服务端指定一个端口
Random iRand = new Random();
Int32 iRandOne = iRand.Next( 0,100 );
Int32 iRandTwo = iRand.Next( 2,200 );
Int32 iPort = (iRandOne << 8) | (iRandTwo);
String[] tmp = LocalHostEntry.AddressList[0].ToString().Split( '.' );
//Ip地址和端口
String szPasvReply = "" + tmp[0] + "," + tmp[1] + "," + tmp[2] + "," + tmp[3] + "," + iRandOne + "," + iRandTwo;
TcpListener tcpListener = new TcpListener(iPort);
si.pi.iphostentry = LocalHostEntry;
si.pi.iPort = iPort;
si.pi.tcpListener = tcpListener;
si.fPassive = true;
si.pi.tcpListener.Start();
IPEndPoint LocalEndPoint = new IPEndPoint( Dns.GetHostByAddress( "127.0.0.1" ).AddressList[0], iPort );
localEP = LocalEndPoint;
Reply( sSocket, 227, szPasvReply );
return;
}
//改变目录
public void ProcessCWDCommand( Socket sSocket, String Parameter, SessionInfo si )
{
try
{
char[] chTmp = Parameter.ToCharArray(); // Check for initial '/'
const Byte byteSlash = (byte)'/';
const Byte byteBackSlash = (byte)'//';
String szNewDir = "";
DirectoryInfo tmp;
if( chTmp[0] == (char)byteSlash )
{
tmp = new DirectoryInfo( MainApp.szFtpRoot + Parameter );
}
else
{
chTmp = ( si.szFtpRoot + @"/" + Parameter ).ToCharArray();
for( int i = 0; i < chTmp.Length; i++ )
{
switch( chTmp[i] )
{
case (char)byteBackSlash:
szNewDir += @"//";
break;
default:
szNewDir += chTmp[i];
break;
}
}
tmp = new DirectoryInfo( szNewDir );
}
tmp.GetFiles(); // 测试指定目录是否存在,如果不存在将抛出一个DirNotFoundException
si.szFtpRoot = tmp.FullName;
Reply( sSocket, 250, "CWD Command successful" );
}
catch( DirectoryNotFoundException )
{
Reply( sSocket, 550, "" + Parameter + ": No such file or directory" );
}
catch( IOException )
{
Reply( sSocket, 550, "" + Parameter + ": Not a directory." ); // Someone typed a filename
}
catch( ArgumentException )
{
Reply(sSocket, 550, "'" + Parameter + "': illegal characters in file/directory name.");
}
return;
}
//返回当前工作目录
public void ProcessPWDCommand(Socket sSocket, String Parameter, SessionInfo si)
{
Byte byteSlash = 92;
String[] tmp = si.szFtpRoot.Split((char)byteSlash);
String cwd = "/";
for(int i = 1; i < tmp.Length; i++)
{
cwd += tmp[i];
cwd += @"/";
}
Reply(sSocket, 257, "'" + cwd + "' is the current directory.");
return;
}
//当前目录下所有文件和文件夹的列表
public void ProcessListCommand(Socket sSocket, String Parameter, SessionInfo si)
{
String[] DirList = Directory.GetDirectories(si.szFtpRoot);
String szDirList = "";
DirectoryInfo curDir = new DirectoryInfo(si.szFtpRoot);
FileInfo[] szFileListArray = curDir.GetFiles();
String szFileList = "";
for(int i = 0; i < DirList.Length; i++ )
{
DirectoryInfo tmp = new DirectoryInfo(DirList[i]);
szDirList += tmp.Name;
szDirList += "/r/n";
}
// Now Files
for(int i = 0; i < szFileListArray.Length; i++)
{
szFileList += szFileListArray[i].Name;
szFileList += "/r/n";
}
szDirList += szFileList;
Reply(sSocket, 150, "Opening ASCII Mode Data Connection for 'file list'");
if(si.fPassive)
{
SendOverPassiveDataConnection(GetBytes( szDirList ), si);
}
else
{
SendOverDataConnection( GetBytes( szDirList ) );
}
Reply(sSocket, 226, "Transfer complete.");
return;
}
//主动模式
public bool SendOverDataConnection(Byte[] pBytes)
{
//
// Data Connection Socket. See the ProcessPortCommand Function
// for more information
//
try
{
Socket s1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s1.Bind(localEP);
s1.Connect(remoteEP);
s1.Send(pBytes);
s1.Close();
}
catch(SocketException)
{
return false;
}
return true;
}
public bool SendOverPassiveDataConnection(Byte[] pBytes, SessionInfo si)
{
if(si.pi.tcpListener.Pending() == true)
{
Socket s1 = si.pi.tcpListener.AcceptSocket();
s1.Send(pBytes);
s1.Close();
}
return true;
}
public void ProcessStoreCommand(Socket sSocket, String Parameter, SessionInfo si)
{
try
{
FileStream pStream = new FileStream( si.szFtpRoot + @"/" + Parameter, FileMode.Create);
Socket s1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
if(si.fBinary)
{
Reply( sSocket, 150, "Opening BINARY mode data connection for '" + Parameter + "'");
}
else
{
Reply( sSocket, 150, "Opening ASCII mode data connection for '" + Parameter + "'");
}
Socket sBinarySocket;
if(si.fPassive)
{
sBinarySocket = si.pi.tcpListener.AcceptSocket();
}
else
{
sBinarySocket = s1;
sBinarySocket.Bind(localEP);
sBinarySocket.Connect(remoteEP);
}
Byte[] ReceivedBytes = new Byte[1];
BinaryWriter bw = new BinaryWriter(pStream);
StreamWriter bs = new StreamWriter(pStream);
while(sBinarySocket.Receive(ReceivedBytes) > 0)
{
if(si.fBinary)
{
bw.Write(ReceivedBytes);
}
else
{
for(int i = 0; i < ReceivedBytes.Length; i++)
{
bs.Write((char)ReceivedBytes[i]);
}
}
}
bs.Close();
bw.Close();
pStream.Close();
sBinarySocket.Close();
Reply( sSocket, 250, "Transfer complete");
}
catch( Exception Ex )
{
Console.Write("" + Ex.Message);
}
}
public void ProcessRetreiveCommand(Socket sSocket, String Parameter, SessionInfo si)
{
try
{
FileStream pStream = new FileStream(si.szFtpRoot + @"/" + Parameter, FileMode.Open);
// Socket s1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
if(si.fBinary)
{
Reply(sSocket, 150, "Opening BINARY mode data connection for '" + Parameter + "'");
BinaryReader br = new BinaryReader(pStream);
Socket sBinarySocket;
if(si.fPassive)
{
sBinarySocket = si.pi.tcpListener.AcceptSocket();
}
else
{
sBinarySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sBinarySocket.Bind(localEP);
sBinarySocket.Connect(remoteEP);
}
while(br.PeekChar() > -1)
{
sBinarySocket.Send(br.ReadBytes(1024));
}
br.Close();
pStream.Close();
sBinarySocket.Close();
}
else
{
Reply(sSocket, 150, "Opening ASCII mode data connection for '" + Parameter + "'");
StreamReader sr = new StreamReader(pStream);
if(si.fPassive)
{
SendOverPassiveDataConnection(GetBytes(sr.ReadToEnd()), si);
}
else
{
SendOverDataConnection(GetBytes(sr.ReadToEnd()));
}
sr.Close();
}
}
catch(Exception Ex)
{
DBG_TRACE(Ex.Message);
}
Reply(sSocket, 200, "Transfer Complete");
return;
}
public void ProcessPortCommand(Socket sSocket, String Parameter)
{
//
// PORT command -
// PORT is used to denote the Data Port the client wishes to recieve
// data on. We have to send all data requests back to the client via this
// port...
String[] tmp = Parameter.Split( ',' );
String szIP = "" + tmp[0] + "." + tmp[1] + "." + tmp[2] + "." + tmp[3];
int iPortNum = (int.Parse(tmp[4]) << 8) | int.Parse(tmp[5]);
Random iRand = new Random();
Int32 iPort = iRand.Next(0,30000);
IPEndPoint LocalEndPoint = new IPEndPoint( Dns.GetHostByAddress( "127.0.0.1" ).AddressList[0], iPort );
IPEndPoint RemoteEndPoint = new IPEndPoint( Dns.GetHostByAddress(szIP).AddressList[0], iPortNum );
localEP = LocalEndPoint;
remoteEP = RemoteEndPoint;
Reply(sSocket, 200, "PORT Command successful.");
return;
}
public void AuthenticateUser(String Username, String Password, Socket sSocket)
{
//
// TODO: Integrate w/ NTLM if running on NT, Some new Password Scheme on 9x
//
// For now just let anyone in
//
Reply(sSocket, 230, "Login Successful.");
return;
}
//
// Debugging Routines
//
public void DumpSocket(Socket sSocket)
{
DBG_TRACE("AddressFamily: {0}", sSocket.AddressFamily);
DBG_TRACE("Available: {0}", sSocket.Available);
DBG_TRACE("Blocking: {0}", sSocket.Blocking);
DBG_TRACE("Connection: {0}", sSocket.Connected);
DBG_TRACE("Handle: {0}", sSocket.Handle);
DBG_TRACE("LocalEndPoint: {0}", sSocket.LocalEndPoint);
DBG_TRACE("ProtocolType: {0}", sSocket.ProtocolType);
}
public void DBG_TRACE(String szMessage)
{
if(MainApp.fDebug)
{
Console.WriteLine("DEBUG: " + szMessage);
}
}
public void DBG_TRACE(String szMessage, Object Parm1)
{
if(MainApp.fDebug)
{
Console.WriteLine("DEBUG: " + szMessage, Parm1);
}
}
public void DBG_TRACE(String szMessage, Object Parm1, Object Parm2)
{
if(MainApp.fDebug)
{
Console.WriteLine("DEBUG: " + szMessage, Parm1, Parm2);
}
}
public void DBG_TRACE(String szMessage, Object Parm1, Object Parm2, Object Parm3)
{
if(MainApp.fDebug)
{
Console.WriteLine("DEBUG: " + szMessage, Parm1, Parm2, Parm3);
}
}
public void DBG_TRACE(String szMessage, Object Parm1, Object Parm2, Object Parm3, Object Parm4)
{
if(MainApp.fDebug)
{
Console.WriteLine("DEBUG: " + szMessage, Parm1, Parm2, Parm3, Parm4);
}
}
}