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:
int CPop3Session::ProcessUSER(char* buf, int len)
{
printf("ProcessUSER\n");
buf[len-2]=0; buf+=5;
strcpy(m_szUserName,buf);
sprintf(m_szUserHome,"%s\\%s",DOMAIN_ROOT_PATH,buf);
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:
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;
strcpy(m_szPassword,buf);
if(m_nState!=POP3_STATE_AUTHORIZATION || strlen(m_szUserName)<1)
{
return SendResponse(POP3_DEFAULT_NEGATIVE_RESPONSE);
}
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:
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:
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:
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:
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(); 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:
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(); 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.