Click here to Skip to main content
Click here to Skip to main content

SMTP Server

By , 22 Sep 2007
 

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.

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:

/*
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:

/*
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:

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:

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)

About the Author

Maruf Maniruzzaman
Software Developer KAZ Software Ltd. Bangladesh.
Bangladesh Bangladesh
Member
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: http://blog.kuashaonline.com
 
Working on small project for 2 factor authentication auth2.com

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5membersmtpserver.in25 Oct '12 - 1:11 
we started to start designing our own smtpserver.in relay server
Newsthanks thats great help to testing my demo live at smtp.smtpserver.inmembersmtpserver.in25 Oct '12 - 1:09 
its helps us to start to write our own server.
and now just started to design and development our own opensource smtpserver with the name of smtpserver.in relay server
with some mailing utility clubed together.
GeneralMy vote of 5memberxiaofengwei23 Feb '11 - 22:01 
thanks
QuestionHow does it worksmemberFilip_N21 Nov '10 - 2:01 
Hey Smile | :) I've got question to Alex De Lara, how can I use telnet to check how does that server works ?
GeneralAlmost therememberAlex De Lara19 May '09 - 13:54 
Great job, indeed ! That's what I was looking for. Smile | :)
I managed to make it compile and run using MS VC 2008 Express Edition.
The program launches and seems to be waiting for a connection like it is supposed to do. Roll eyes | :rolleyes:
Trying to launch another instance of the server, fails with the expected message:
"Error: Can not bind socket. Another server running?"
"Quiting with error code: 10048"
which tells me that the first server in fact grabbed port 25.
 
However, when tried using the Mozilla Thunderbird to send an email, I got no trace of any kind on the server side.Confused | :confused:
 
Thunderbird says that the server is not accepting SMTP connections ...
 
What else ? Running on Windows XP D'Oh! | :doh:
 
You mention "stop the smtp service" but XP doesn't have such service.
 
Any hint will be appreciated.
 
-Alex
AnswerRe: Almost there.. Success !!!memberAlex De Lara19 May '09 - 19:21 
It's amazing what some sweat out of desperation can do... after digging the code and not able to find another clue either...
 
This piece was misleading me to believe the server would be kuashalabs....
char g_szDomain[ 300 ] = "KuashaLabs.com";
so I was all the time trying to connect to it on port 25.
 
Finally, a bell rang and I used "netstat -a" and could see that the program actually was using my laptop's name to bind to instead of the "KuashaLabs.com".
 
This part of the code finds the hostname and does the binding to the machine it is running from (in my case, my own laptop)
    LPHOSTENT lpHost=gethostbyname("localhost");
    soc_addr.sin_family=AF_INET;
    soc_addr.sin_port=htons(SMTP_PORT);
    soc_addr.sin_addr=*(LPIN_ADDR)(lpHost->h_addr_list[0]);

Finally, when I realized that, the "telnet 25" worked perfectly.
Going back to Mozilla Thunderbird, I just changed the SMTP server to point to my laptop and it worked perfectly. All the code made full sense. Smile | :)
By the way, one can use telnet to issue those commands that Maruf describe in the article.
 
Cool | :cool: Still on time, it worth to mention that when I compiled in the VC++Express 2008, I had to change the file KLSmtp.rc, all references from:
#include "afxres.h"
TO
#include "windows.h"
 
Thanks again Maruf, for sharing.
 
-Alex
GeneralHow can i run TELNET from other computer ?memberDaniel Mayer8 Dec '12 - 20:05 
From my computer i run TELNET "LOCALHOST" 25 and its OK
 
But how can i run TELNET from other computer ?
GeneralRe: Almost there - working on the forward now...memberAlex De Lara20 May '09 - 6:34 
Yeah... that is the part that I mostly need now, Roll eyes | :rolleyes: ie. the ability to forward the incoming mails to other SMTP servers out there...
 
Will keep y'all posted on progress... (or else.. OMG | :OMG: )
 
-Alex
General5 globesmemberJonathan C Dickinson8 Aug '08 - 2:20 
Just what i needed.
 
He who asks a question is a fool for five minutes. He who does not ask a question remains a fool forever. [Chineese Proverb]
 
Jonathan C Dickinson (C# Software Engineer)

GeneralGood WorkmemberHatem Mostafa23 May '08 - 20:15 
I like this type of work. But, the article needs more sequence diagrams between server and client.
 
Thanks
Hatem
Generaldo NOT take score seriouslymemberxwp25 Sep '07 - 5:17 
hi,your article are very valuable for me and do NOT take score serious,we post articles here just to share with others,not for anything.
Take it easy.
GeneralRe: do NOT take score seriouslymemberMaruf Maniruzzaman25 Sep '07 - 6:12 
>> We post articles here just to share with others,not for anything.
 
You are right. But the problem is, if an article gets low rating it may go down to purgatory. I want to know why my article deservers low ranking- so that I can update it or be carefull about next article. I am not interested about 5 scrore but interested about the opinion of a person who is voting. Thats it. I have one more interest - to improve writing skill. Smile | :) . Thanks.
 
Maruf Maniruzzaman
Dhaka, Bangladesh.
Blog you should not miss
 
Tomorrow is a blank page

GeneralSeems need update to articlememberMaruf Maniruzzaman23 Sep '07 - 18:01 
Vote rating is going low. So I need to update the article. Any sugestion?
 
Maruf Maniruzzaman
Dhaka, Bangladesh.
Blog you should not miss
 
Tomorrow is a blank page

QuestionC# sourcecodememberknoami23 Sep '07 - 0:28 
Can you provide the sourcecode in C#?
 

AnswerRe: C# sourcecodememberMaruf Maniruzzaman23 Sep '07 - 1:16 
Yes, but let me fix all errors and make a stable SMTP server first- it was just a proof of concept 4 years ago. There should be a user management console, the server should forward the messages to destination. Without these it is just a toy. Also I want to make the article better. Then I'll try to provide the C# source. The application is fairly simple to rewrite in C#.

 
Maruf Maniruzzaman
Dhaka, Bangladesh.
Blog you should not miss
 
Tomorrow is a blank page

AnswerRe: C# sourcecodememberPaul A. Howes23 Sep '07 - 2:28 
Considering that the entire project is written in C++, re-writing it in C# is non-trivial. Instead of asking the author to do it, why don't you take the time to do it? Then you could write your own article describing how easy (or not) the conversion was. Smile | :)
 
--
Paul

AnswerRe: C# sourcecodememberkennster23 Sep '07 - 10:56 
If you are looking for C# source code you should check out NMail Server:
 
http://nmailserver.sourceforge.net/index.php/Main_Page[^]
 
100% OSS C# SMTIP/Pop3/IMAP Mail Server using MySQL backend.
QuestionRe: C# sourcecodememberGh0stman24 Sep '07 - 7:29 
Is there a similar (or a port on NMail) project using Microsoft SQL Server 2005 ?
AnswerRe: C# sourcecodememberkennster9 Dec '08 - 2:40 
The latest trunk version of NMail replaced the MySQL provider with a NHibernate provider. I spent about eight hours trying to get it to work with SQL 2005 express ... got the setup wizard to create the database schema in my sql databases, but haven't had any success running it yet.
 
In the newer version, there are two xml files which configure NHibernate which need modification:
hibernate-spool-configuration.xml
hibernate-localstore-configuration.xml
 
Then I found that there is a difference between how GUID's are handled between SQL & MySQL... haven't figured this out yet.
 
I even tried updating NHibernate to the latest 2.0 version (NMail-trunk-r270 uses NHibernate 1.2) but found that it wouldn't work with that either.)
 
In any event it is great code and if anyone does succeed in getting NMail working with SQL 2005 ... post it back here and on the NMail forums?

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 23 Sep 2007
Article Copyright 2007 by Maruf Maniruzzaman
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid