分类:
2012-05-26 22:15:03
原文地址:FileZilla FTP服务器源代码分析11 作者:sinodragon21
FTP客户通过ftp localhost命令与FileZilla服务器建立socket连接后,FileZilla Server显示了welcome信息,这时屏幕上显示类似(我们以windows下的ftp命令作为sample):
Connected to dell.
220-FileZilla Server version 0.9.18 beta
220-written
by Tim Kosse ()
220
Please visit
User
(dell:(none)):
提示输入用户名,假设这时用户输入whg,回车,这时ftp客户端会将这用户输入的字符翻译成标准的FTP命令"USER whg"发送到服务器,因为这时是CControlSocket对这个socket进行监听,并且recv相关的事件通过前面提到的分发机制,最终分发到 CControlSocket的OnReceive方法,下面我看一下这个方法:
m_antiHammeringWaitTime还不知是起什么作用,在对源代码进行跟踪的时候,其刚开始的值是0,因此先跳过这个。
下段是获得传输速度限制SpeedLimit,如果没有限制,则为-1。
再往下:
int numread = Receive(buffer, len); //
调用recv来获得socket数据,取长度为len的数据放到buffer中
读取成功后,将buffer中的接收到的数据一个字节一
个字节放到m_RecvBuffer中:
m_RecvBuffer[m_nRecvBufferPos++] = buffer[i];
然后将将刚才收到的m_RecvBuffer放入m_RecvLineBuffer:
m_RecvLineBuffer.push_back(m_RecvBuffer);
m_RecvLineBuffer
相当于一个命令池,里面存放着用户发送来,但还没有处理的命令。
最后当这个recv处理完后,调用ParseCommand()来解释这个命令。
首先通过GetCommand()取出m_RecvLineBuffer中最前面的命令,并解释成命令command,以及参数args,如刚才的 命令USER whg就被解释成command=USER, args=whg
下面的循环:
for (int i = 0; i <
(sizeof(commands)/sizeof(t_command)); i++)
通过在预先定义的FTP
Server所有命令commands中,查找是否包含command,从而校验刚才收到的命令的合法性,如果command不在commands中,显
示command是非法命令,这时发送客户端
Send(_T("500 Syntax error, command
unrecognized."));
即使命令是合法的,但如果参数不对(bHasargs指定这个命令是否需要参数),即有些命令必须带参数,而args没有,这时会发送:
Send(_T("501
Syntax error"));
下面:
if (!m_RecvLineBuffer.empty())
m_pOwner->PostThreadMessage(WM_FILEZILLA_THREADMSG,
FTM_COMMAND, m_userid);
表示如果命令缓冲区中还有未处理的命令,则发送消息给
CServerThread,CServerThread在方法OnThreadMessage中处理这个消息:
else if
(wParam==FTM_COMMAND)
{ //Process a command sent from a client
CControlSocket
*socket=GetControlSocket(lParam);
if (socket)
socket->ParseCommand();
}
在
GetControlSocket()方法中:
CControlSocket *
CServerThread::GetControlSocket(int userid)
{
CControlSocket
*ret=0;
EnterCritSection(m_threadsync);
// 下面这个map是user ->
CControlSocket,即通过userid找到服务这个userid的CControlSocket
std::map
if
(iter!=m_LocalUserIDs.end())
ret=iter->second;
LeaveCritSection(m_threadsync);
return
ret;
}
可见,发送这个消息的作用是让CControlSocket继续调用ParseCommand()来处理下一个命令。
回到最初的ParseCommand(),如果命令参数也没有问题,下面检查这个命令是否必须先登录再使用(由
bValidBeforeLogon指定),比如:get命令是必须先登录的,而USER命令不用,如果必须先登录,发送:
Send(_T("530
Please log in with USER and PASS first."));
下面同样
m_pOwner->PostThreadMessage(WM_FILEZILLA_THREADMSG,
FTM_COMMAND, m_userid);
命令都合格的话,下面:
switch (nCommandID)
来处理不同的命令,由于这时是COMMAND_USER命令,我
们看一下处理过程:
经过一些处理后,下面发送
Send(_T("331 Password required for ") +
args);
要求用户输入密码,时客户端屏幕上会显示:
331 Password required for whg
Password:
用户输入密码后,回车,这时ftp客户端会翻译成标准的FTP命令"PASS
123456"发送到服务器,我们看一下ParseCommand()对这的处理:
case COMMAND_PASS:
else
if (DoUserLogin(args))
Send(_T("230 Logged on"));
在
DoUserLogin()认定成功登录后,发送成功登录消息给客户端,否则会发送错误消息:
Send(_T("530 Login or
password incorrect!"));
仔细看一下CPermissions::CheckUserLogin(),会发现密
码是经过MD5加密的,并且在CServerThread创建时,跟权限相关的成员变量就初始化了:
m_pPermissions = new
CPermissions;
在CPermissions::Init()中,调用ReadSettings(),从配置文件中,将所有的用户信息(包括密码)都读到内存了,因 此刚才的密码校验只是内存中的字符串比对。
用户成功登录后,FTP客户端显示:
C:\Documents and Settings\Administrator>ftp localhost
Connected
to dell.
220-FileZilla Server version 0.9.18 beta
220-written by
Tim Kosse ()
220
Please visit
User
(dell:(none)): whg
331 Password required for whg
Password:
230
Logged on
ftp>
下面FTP服务器等待新的FTP命令了。