Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

SMTP Server

4.43/5 (28 votes)
22 Sep 2007GPL34 min read 1   5.3K  
This article shows how to design an SMTP server

Introduction

In this, I'll show a simple implementation of Simple Mail Transfer Protocol (SMTP) that is defined in RFC 821. According to the document - The objective of Simple Mail Transfer Protocol (SMTP) is to transfer mail reliably and efficiently. SMTP is independent of the particular transmission subsystem and requires only a reliable ordered data stream channel. In this article, we use TCP/IP only for message distribution.

The SMTP provides mechanisms for the transmission of mail; directly from the sending user's host to the receiving user's host when the two hosts are connected to the same transport service, or via one or more relay SMTP-servers when the source and destination hosts are not connected to the same transport service. To be able to provide the relay capability, the SMTP-server must be supplied with the name of the ultimate destination host as well as the destination mailbox name.

The protocol uses 7-bit ASCII characters. If the transport layer provides an 8-bit transmission channel, then the MSB is set to zero and used.

Screenshot - smtp-model.gif

An SMTP client (Sender-SMTP) communicates with the SMTP server (Receiver-SMTP) using a predefined strict set of case insensitive commands. Among them HELO, EHLO, MAIL, RCPT, DATA, QUIT are always implemented by all SMTP servers. In response, the server sends some predefined response code to the client. For example, 250 is returned if everything goes OK. A short description may follow the reply code. All commands are ended with CRLF characters. I'll describe the commands here sequentially.

In this article, I'll show how to implement a simple SMTP server. You can test it with any SMTP client like Outlook Express. To test the server, please stop the Windows SMTP Service first. You may need to create some folders also. For example, if you want to send mail to kuasha@exampledomain.com you should have ./exampledomain.com/kuasha/mbox/ as a valid directory relative to the current directory of the server executable. Simply speaking, if you see an error message like "Cannot copy xxxx file to dddd directory" then you have to make dddd directory structure. I encourage you to see the code instead. And we may declare it to be stable SMTP if we can fix all errors.

There is also a POP3 server implementation here. If you keep two executables in a 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

When a transmission channel is established between client and server, the server sends a ready signal (220 response) to the client:

220 kuashaonline.com Ready. <CRLF>

Client can then start the session issuing a HELO command.

The HELO or EHLO command

These two commands (any one) are used to establish a dialog session between client and server. The client sends HELO command in the following format to start a session:

HELO <SP> <domain> <CRLF>

Here the <domain> is users domain who wish to send a message. If the server allows a user from this domain, it sends OK response:

250 OK You are not kicked off :) <CRLF>

Here is my simple implementation of HELO request. Please note that for simplicity, I have not checked error conditions.

C++
int CMailSession::ProcessHELO(char *buf, int len)
{
    Log("Received HELO\n");
    buf+=5;
    //User session established
    m_nStatus=SMTP_STATUS_HELO;
    m_FromAddress.SetAddress("");
    m_nRcptCount=0;
    // Prepare a new message now.
    CreateNewMessage();
    return SendResponse(250);
}

The session is now established and the client can now send a message.

The MAIL Command

Client starts with MAIL command to send a mail message:

Format: MAIL <SP> FROM:<reverse-path> <CRLF>
Example MAIL FROM:<manir@exampledomain.com> <CRLF>

Here client assigns the FROM address. Let's assume that we do accept this from address and send OK.

250 OK<CRLF>

It is possible to send invalid parameter response (501) from server if, for example, from address format is not valid.

501 Syntax error in parameters or arguments <CRLF>

Here is my simple implementation:

C++
/*
MAIL
S: 250
F: 552, 451, 452
E: 500, 501, 421
*/
int CMailSession::ProcessMAIL(char *buf, int len)
{
    char address[MAX_ADDRESS_LENGTH+5];
    char *st,*en;
    __w64 int alen;
    if(m_nStatus!=SMTP_STATUS_HELO)
    {
        return SendResponse(503);
    }
    memset(address,0,sizeof(address));
    st=strchr(buf,'<');
    en=strchr(buf,'>');
    st++;
    alen=en-st;
    strncpy(address,st,alen);
    printf("FROM [%s]",address);
    if(!CMailAddress::AddressValid(address))
    {
        return SendResponse(501);
    }
    m_FromAddress.SetAddress(address);
    return SendResponse(250);
}

OK from address is set. Now we want the to address.

The RCPT Command

The client sets to address with this command:

Format: RCPT <SP> TO:<forward-path> <CRLF>
Example: RCPT TO:kuasha@exampledomain.com<CRLF>

Let's assume that our SMTP server can accept the message to store the message (if it is local) or relay the message to destination SMTP server somehow. So we accept it:

250 OK<CRLF>

Here is my implementation:

C++
/*
RCPT
S: 250, 251
F: 550, 551, 552, 553, 450, 451, 452
E: 500, 501, 503, 421
*/
int CMailSession::ProcessRCPT(char *buf, int len)
{
    char address[MAX_ADDRESS_LENGTH+5];
    char user[MAX_USER_LENGTH+5];
    char tdom[MAX_DOMAIN_LENGTH+5];
    char szUserPath[MAX_PATH+1];
    char *st,*en, *domain=tdom;
    __w64 int alen;
    if(m_nStatus!=SMTP_STATUS_HELO)
    {
        //503 Bad Command
        return SendResponse(503);
    }
    if(m_nRcptCount>=MAX_RCPT_ALLOWED)
    {
        //552 Requested mail action aborted: exceeded storage allocation
        return SendResponse(552);
    }
    memset(address,0,sizeof(address));
    st=strchr(buf,'<');
    en=strchr(buf,'>');
    st++;
    alen=en-st;
    strncpy(address,st,alen);
    domain=strchr(address,'@');
    domain+=1;
    memset(user,0,sizeof(user));
    strncpy(user,address,strlen(address)-strlen(domain)-1);
    printf("RCPT [%s] User [%s] Domain [%s]\n",address, user, domain);
    char domain_path[300];
    sprintf(domain_path,"%s%s",DIRECTORY_ROOT,domain);
    if(PathFileExists(domain_path))
    {
        sprintf(szUserPath,"%s\\%s",domain_path,user);
        printf("User MBox path [%s]\n",szUserPath);
        if(!PathFileExists(szUserPath))
        {
            TRACE("PathFileExists(%s) FALSE\n",szUserPath);
            printf("User not found on this domain\n");
            return SendResponse(550);
        }
    }
    else
    {
        TRACE("PathFileExists(%s) FALSE\n",domain_path);
        return SendResponse(551);
    }
    m_ToAddress[m_nRcptCount].SetMBoxPath(szUserPath);
    m_ToAddress[m_nRcptCount].SetAddress(address);
    m_nRcptCount++;
    return SendResponse(250);
}

OK. Now it's time to receive DATA from client.

The DATA Command

Client sends DATA command to start sending data:

DATA <CRLF>

The server now sets its state to receive data and sends an affirmative result to client using 354 return.

354 Start mail input; end with [CRLF].[CRLF] <CRLF>

When client receives this reply, client starts to send the mail body. At the end, client sends a [CRLF].[CRLF] sequence to tell server that data sending is finished.

Here is my implementation to process DATA command:

C++
int CMailSession::ProcessDATA(char *buf, int len)
{
    DWORD dwIn=len, dwOut;
    if(m_nStatus!=SMTP_STATUS_DATA)
    {
        m_nStatus=SMTP_STATUS_DATA;
        return SendResponse(354);
    }
    //client should send term in separate line 
    if(strstr(buf,SMTP_DATA_TERMINATOR)) //if a [CRLF].CRLF] found
    {
        printf("Data End\n");
        m_nStatus=SMTP_STATUS_DATA_END;
        return ProcessDATAEnd();
    }
    // We write the data to a file
    WriteFile(m_hMsgFile,buf,dwIn, &dwOut,NULL);
    return 220;
}

When the terminator sequence ([CRLF].CRLF]) is received, the mail reception is finished. The attachments are also received at this stage. The SMTP server may not separate the attachment part. It is the responsibility of the mail viewer. Everything is considered as data.

The QUIT Command

OK, we are finished with the sending mail. Client should now send QUIT command:

QUIT<CRLF>

And server closes the session by sending 221 response.

221 Service closing transmission channel.

OK. That's all about the good world. No error. But there may be some errors or failure. Server must send an appropriate response to notify that. Here is some example code in my response method:

C++
int CMailSession::SendResponse(int nResponseType)
{
    char buf[100];
    int len;
    if(nResponseType==220)
        sprintf(buf,"220 %s Welcome to %s %s \r\n",
			DOMAIN_NAME,APP_TITLE, APP_VERSION);
    else if(nResponseType==221)
        strcpy(buf,"221 Service closing transmission channel\r\n");
    else if (nResponseType==250) 
        strcpy(buf,"250 OK\r\n");
    else if (nResponseType==354)
        strcpy(buf,"354 Start mail input; end with <CRLF>.<CRLF>\r\n");
    else if(nResponseType==501)
        strcpy(buf,"501 Syntax error in parameters or arguments\r\n"); 
    else if(nResponseType==502)
        strcpy(buf,"502 Command not implemented\r\n"); 
    else if(nResponseType==503)
        strcpy(buf,"503 Bad sequence of commands\r\n"); 
    else if(nResponseType==550)
        strcpy(buf,"550 No such user\r\n");
    else if(nResponseType==551)
        strcpy(buf,"551 User not local. Can not forward the mail\r\n");
    else
        sprintf(buf,"%d No description\r\n",nResponseType);
    len=(int)strlen(buf);
    printf("Sending: %s",buf);
    send(m_socConnection,buf,len,0);
    return nResponseType;
}

Reference

History

  • 23rd September, 2007: Article posted on CodeProject

This server was written for test purposes only when I was a 2nd year undergrad student back in the year 2003. So, it is not stable at all.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)