Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / MFC

POP3 Server

Rate me:
Please Sign up or sign in to vote.
4.87/5 (10 votes)
23 Sep 2007CPOL4 min read 58.3K   1.9K   36   1
Shows how to implement a POP3 server

Introduction

In this article, I'll show a simple implementation of Post Office Protocol - Version 3 (POP3) that is defined in RFC 1225. According to the document - The Post Office Protocol - Version 3 (POP3) is intended to permit a workstation to dynamically access a maildrop on a server host in a useful fashion. Usually, this means that the POP3 is used to allow a workstation to retrieve mail that the server is holding for it. The POP3 server receives and stores mails for uses and deliver them only to authorized users. Here the SMTP server also gets involved - to answer the question "where and how the messages come?". But for simplicity we assume somehow we get some email messages and they are stored in the mail box.

The POP3 client communicates with the server with a set com predefined commands. Commands in the POP3 consist of a keyword possibly followed by an argument. All commands are terminated by a CRLF pair. Commands are case insensitive.

To run the demo POP3 server provided with this article, we need to specify each user's mailbox folder as user name and in the mailbox a file with extension pwd and name as password. If password is a secret, the file name will be secret.pwd. So, for a user kuasha@kuashaonline.com we have ./kuashaonline.com/kuasha/secret.pwd file path present related to the pop3 server service current directory.

There is also a SMTP Server implementation at smtp-server.aspx. If you keep two executables in the same directory and first setup SMTP and then setup POP3, you can send and receive mails between users with a mail client using the machine IP address of the machine.

Implementation

The server maintains each user session. It also maintains the state of the session which may be POP3_STATE_AUTHORIZATION, POP3_STATE_TRANSACTION, etc. When a client connects to the server, it sends a default welcome message to the client. For example, our server sends:

+OK pop3server 1.0 POP3 Server ready on kuashaonline.com

Now the current user (client) is in authorization state on server.

The USER Command

This is the first command a user may issue to the server. If a user's logon id is kuasha, it should send:

USER kuasha<CRLF>

Assuming there is a known user named kuasha. So, the server sends an affirmative response to the client:

+OK Hello kuasha- now send PASS<CRLF>

Or if kuasha is unknown to the server, it may send:

-ERR Sorry kuasha could you please introduce yourself ;)<CRLF>

Here is my implementation:

C++
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);
}

The PASS Command

After receiving a successful response of USER command, the client should send PASS command in the following format:

PASS secret<CRLF>

The server now checks its record and if it is satisfied, it sends:

+OK kuasha- welcome and proceed<CRLF>

If password is wrong, the server sends -ERR response like:

-ERR Umm.. pardon me, but your password is wrong.<CRLF>

If password is correct, the server sets session state to transaction state.

Here is my implementation:

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

The STAT Command

This command is valid in the TRANSACTION state. On response server sends the following:

+OK nn mm

Here nn is number of messages and mm represents total size of all messages.

Here is my implementation:

C++
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);
}

The LIST Command

This command is issued to get the list of message information. It can optionally send message id following list command. In that case, information of that message is sent. Without message id, the server sends a positive response, then message id and message size in each line and finally sends a period:

+OK 3 messages
1 1229
2 15203
3 23105
.

The client can use the message id to reference the message to server.

Here is my implementation:

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

The RETR Command

This command followed by a (required) message id is used to get the entire message from the server. If the message id is valid, the server sends +OK on the first line. Then the entire message and then <CRLF>.<CRLF> sequence to indicate end of message. For a "RETR 1" command, a server may send the following response:

+OK 1229 octets<CRLF>
<Now server sends 1229 octets here><CRLF>
.<CRLF>

Or if the message id is not valid, the server sends an -ERR response:

-ERR Message not found.

Here is my implementation:

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

The DELE Command

The DELE command followed by message id requests the server to delete specified message in mail box. On success, the server sends a +OK response. The message is marked as deleted and is deleted at the end of the session - when the session enters UPDATE state. User can undelete the message if she/he wants before that.

Here is my implementation:

C++
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);
}

The QUIT Command

Last command from user. It closes the TCP channel and server sets the session to update state and deletes all messages that are marked as deleted.

Here is my implementation:

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

Other Commands

There are some other commands. Please refer to the source code if you are interested. Those are less important commands. So, I do not describe them here.

References

History

It was implemented back in year 2003 as an undergrad student project. I want this server to be a stable server. Please do comment on how to improve the source and article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Microsoft
United States United States
Have completed BSc in Computer Science & Engineering from Shah Jalal University of Science & Technology, Sylhet, Bangladesh (SUST).

Story books (specially Masud Rana series), tourism, songs and programming is most favorite.

Blog:
Maruf Notes
http://blog.kuashaonline.com

Comments and Discussions

 
GeneralGood Work Pin
Hatem Mostafa23-May-08 20:17
Hatem Mostafa23-May-08 20:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.