65.9K
CodeProject 正在变化。 阅读更多。
Home

POP3 服务器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (10投票s)

2007 年 9 月 24 日

CPOL

4分钟阅读

viewsIcon

59448

downloadIcon

1956

演示如何实现 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_AUTHORIZATIONPOP3_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年作为一名本科生项目实现的。我希望这个服务器能成为一个稳定的服务器。请就如何改进源代码和文章发表评论。

© . All rights reserved.