POP3 服务器






4.87/5 (10投票s)
演示如何实现 POP3 服务器。
引言
在本文中,我将介绍一个简单的邮局协议第3版(POP3)的实现,该协议定义在RFC 1225中。根据文档——邮局协议第3版(POP3)的目的是允许工作站以有用方式动态访问服务器主机上的邮件箱。通常,这意味着POP3用于允许工作站检索服务器为其保存的邮件。POP3服务器接收并存储用户的邮件,并且仅将邮件交付给授权用户。在这里,SMTP服务器也参与进来——来回答“消息来自哪里以及如何到达?”这个问题。但为了简化起见,我们假设我们通过某种方式收到了一些电子邮件,并且它们存储在邮箱中。
POP3客户端与服务器之间通过一组预定义的命令进行通信。POP3中的命令由关键字组成,可能后跟一个参数。所有命令都以CRLF对结尾。命令不区分大小写。
要运行本文提供的演示POP3服务器,我们需要为每个用户的邮箱文件夹指定用户名,并在邮箱中有一个扩展名为pwd且名称为password的文件。如果密码是保密的,则文件名将是secret.pwd。因此,对于用户kuasha@kuashaonline.com,在pop3服务器服务的当前目录下会存在一个路径为./kuashaonline.com/kuasha/secret.pwd的文件。
在smtp-server.aspx处还有一个SMTP服务器实现。如果您将这两个可执行文件放在同一个目录中,先设置SMTP,然后设置POP3,您就可以使用该机器的机器IP地址,通过邮件客户端在用户之间发送和接收邮件。
实现
服务器维护每个用户会话。它还维护会话的状态,该状态可能为POP3_STATE_AUTHORIZATION
、POP3_STATE_TRANSACTION
等。当客户端连接到服务器时,它会向客户端发送默认的欢迎消息。例如,我们的服务器发送
+OK pop3server 1.0 POP3 Server ready on kuashaonline.com
现在,当前用户(客户端)处于服务器的授权状态。
USER命令
这是用户可能向服务器发出的第一个命令。如果用户的登录ID是kuasha,它应该发送
USER kuasha<CRLF>
假设存在一个名为kuasha的已知用户。那么,服务器会向客户端发送一个肯定的响应
+OK Hello kuasha- now send PASS<CRLF>
或者,如果服务器不知道kuasha,它可能会发送
-ERR Sorry kuasha could you please introduce yourself ;)<CRLF>
这是我的实现
int CPop3Session::ProcessUSER(char* buf, int len)
{
printf("ProcessUSER\n");
buf[len-2]=0; buf+=5;
//printf("User= [%s]\n",buf);
strcpy(m_szUserName,buf);
sprintf(m_szUserHome,"%s\\%s",DOMAIN_ROOT_PATH,buf);
//win32
if(!PathFileExists(m_szUserHome))
{
printf("User %s's Home '%s' not found\n",
m_szUserName, m_szUserHome);
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
}
printf("OK User %s Home %s\n",m_szUserName, m_szUserHome);
if(m_nState!=POP3_STATE_AUTHORIZATION)
{
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
}
return SendResponse(POP3_DEFAULT_AFFERMATIVE_RESPONSE);
}
PASS命令
在收到USER命令的成功响应后,客户端应以以下格式发送PASS命令
PASS secret<CRLF>
服务器现在会检查其记录,如果满意,它会发送
+OK kuasha- welcome and proceed<CRLF>
如果密码错误,服务器会发送-ERR
响应,如下所示
-ERR Umm.. pardon me, but your password is wrong.<CRLF>
如果密码正确,服务器会将会话状态设置为事务状态。
这是我的实现
int CPop3Session::ProcessPASS(char* buf, int len)
{
printf("ProcessPASS\n");
buf[len-2]=0; buf+=5;
if(buf[len-2]==10) buf[len-2]=0;
//printf("Password= [%s]\n",buf);
strcpy(m_szPassword,buf);
if(m_nState!=POP3_STATE_AUTHORIZATION || strlen(m_szUserName)<1)
{
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
}
//TODO: Send message count and size also with +OK
if(Login(m_szUserName, m_szPassword))
return SendResponse(POP3_DEFAULT_AFFERMATIVE_RESPONSE);
else
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
return 0;
}
STAT命令
此命令在TRANSACTION
状态下有效。响应服务器会发送以下内容
+OK nn mm
这里nn
是消息数量,mm
代表所有消息的总大小。
这是我的实现
int CPop3Session::ProcessSTAT(char* buf, int len)
{
printf("ProcessSTAT\n");
if(m_nState!=POP3_STATE_TRANSACTION)
{
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
}
m_nLastMsg=1;
char buf[100];
sprintf(buf,"+OK %d %ld\r\n",m_nTotalMailCount, m_dwTotalMailSize);
return SendResponse(POP3_STAT_RESPONSE, buf);
}
LIST命令
此命令用于获取消息信息的列表。它可以选择性地在list命令后发送消息ID。在这种情况下,将发送该消息的信息。如果没有消息ID,服务器会发送一个正响应,然后是每行的消息ID和消息大小,最后发送一个句点
+OK 3 messages
1 1229
2 15203
3 23105
.
客户端可以使用消息ID在服务器上引用消息。
这是我的实现
int CPop3Session::ProcessLIST(char* buf, int len)
{
buf+=4; buf[4]='0'; buf[len-2]=0;
int msg_id=atol(buf);
printf("ProcessLIST %d\n",msg_id);
if(m_nState!=POP3_STATE_TRANSACTION)
{
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
}
if(msg_id>0)
{
char resp[100];
sprintf(resp, "+OK %d %d\r\n",
msg_id, m_pPop3MessageList[msg_id-1].GetSize());
return SendResponse(resp);
}
else
{
SendResponse("+OK \r\n");
for(int i=0; i < m_nTotalMailCount; i++)
{
char resp[100];
sprintf(resp, "%d %d\r\n",i+1,
m_pPop3MessageList[i].GetSize());
SendResponse(resp);
}
SendResponse(".\r\n");
}
return 0;
}
RETR命令
此命令后跟一个(必需的)消息ID,用于从服务器获取整个消息。如果消息ID有效,服务器会在第一行发送+OK
。然后是整个消息,然后是<CRLF>.<CRLF>
序列以指示消息结束。对于“RETR 1”命令,服务器可能会发送以下响应
+OK 1229 octets<CRLF>
<Now server sends 1229 octets here><CRLF>
.<CRLF>
或者,如果消息ID无效,服务器会发送-ERR
响应
-ERR Message not found.
这是我的实现
int CPop3Session::ProcessRETR(char* buf, int len)
{
buf+=4; buf[4]='0'; buf[len-2]=0;
int msg_id=atol(buf);
printf("ProcessRETR %d\n", msg_id);
if(m_nState!=POP3_STATE_TRANSACTION)
{
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
}
if(msg_id>m_nTotalMailCount)
{
return SendResponse("-ERR Invalid message number\r\n");
}
if(m_nLastMsg<(unsigned int)msg_id) m_nLastMsg=msg_id;
char resp[25];
sprintf(resp,"+OK %d octets\r\n",
m_pPop3MessageList[msg_id-1].GetSize());
SendResponse(resp);
SendMessageFile(m_pPop3MessageList[msg_id-1].GetPath());
SendResponse("\r\n.\r\n");
return 0;
}
DELE命令
DELE
命令后跟消息ID,请求服务器删除邮箱中指定的消息。成功时,服务器会发送+OK
响应。该消息将被标记为已删除,并在会话结束时——当会话进入UPDATE
状态时——被删除。用户可以在此之前取消删除该消息。
这是我的实现
int CPop3Session::ProcessDELE(char* buf, int len)
{
buf+=4; buf[4]='0'; buf[len-2]=0;
int msg_id=atol(buf);
printf("ProcessDELE %d\n",msg_id);
if(m_nState!=POP3_STATE_TRANSACTION || msg_id>m_nTotalMailCount)
{
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
}
m_pPop3MessageList[msg_id-1].Delete(); // set delete flag
return SendResponse(POP3_DEFAULT_AFFERMATIVE_RESPONSE);
}
QUIT命令
用户的最后一条命令。它关闭TCP通道,服务器将会话设置为更新状态,并删除所有标记为已删除的消息。
这是我的实现
int CPop3Session::ProcessQUIT(char* buf, int len)
{
printf("ProcessQUIT\n");
if(m_nState==POP3_STATE_TRANSACTION)
m_nState=POP3_STATE_UPDATE;
SendResponse(POP3_DEFAULT_AFFERMATIVE_RESPONSE,"Goodbye");
UpdateMails(); // delete all mails with delete flag
return -1;
}
其他命令
还有一些其他命令。如果您感兴趣,请参考源代码。这些命令不太重要。因此,我在这里不作描述。
参考文献
历史
这是我在2003年作为一名本科生项目实现的。我希望这个服务器能成为一个稳定的服务器。请就如何改进源代码和文章发表评论。