Simple SMTP Client Program


  1. Introduction

  2. RFC Commands Used

  3. SMTP in Action

  4. SMTP Client Code


  1. Introduction

Lets begin with some basics of the Net.Way back in the 70's when the Net started evolving....people realized the importance it would play in bringing down the communication barrier. Hence the earlier developers took pains in developing methods to be in touch with each other via this new medium.

This was a tedious job and they realized that it would be very essential for them to follow a set of guidelines which would be universal and machine independent. These rules and regulations came to be known as PROTOCOLS. Thus we have protocols to follow for developing almost anything new on the web.Whenever a proposal for improvement was made a paper for the same was submitted and published on the web.These white papers were called Request For Comments (RFCs). They were all numbered appropriately. Some of them are as follows:

Top|Back


  1. RFC Commands Used

We will now look into the RFC 821 available for the basic SMTP commands required to send a mail.

N.B....SMTP communicates through the standard port25

HELO <SP> <domain> <CRLF>

MAIL <SP> FROM:<reverse-path> <CRLF>

RCPT <SP> TO:<forward-path> <CRLF>

DATA <CRLF>

RSET <CRLF>

SEND <SP> FROM:<reverse-path> <CRLF>

SOML <SP> FROM:<reverse-path> <CRLF>

SAML <SP> FROM:<reverse-path> <CRLF>

VRFY <SP> <string> <CRLF>

EXPN <SP> <string> <CRLF>

HELP [<SP> <string>] <CRLF>

NOOP <CRLF>

QUIT <CRLF>

TURN <CRLF>

 

Of these the basic commands required to make SMTP workable are the following:

HELO

MAIL

RCPT

DATA

QUIT

 

Basically th SMTP transactions are divided into 3 parts.

Top|Back


Lets now take a look at these basic commands

The above is used in transmission channel opening

HELO <SP> <domain> <CRLF>

In the 'HELO' command the host sending the command identifies itself; the command may be interpreted as saying "Hello, I am <domain>".

 

The first step in the procedure is the MAIL command. The <reverse-path> contains the source mailbox.

MAIL <SP> FROM:<reverse-path> <CRLF>

This command tells the SMTP-receiver that a new mail transaction is starting and to reset all its state tables and buffers, including any recipients or mail data. It gives the reverse-path which can be used to report errors. If accepted, the receiver-SMTP returns a 250 OK reply. The <reverse-path> can contain more than just a mailbox. The <reverse-path> is a reverse source routing list of hosts and source mailbox. The first host in the <reverse-path> should be the host sending this command. The second step in the procedure is the RCPT command.

 

RCPT <SP> TO:<forward-path> <CRLF>

This command gives a forward-path identifying one recipient. If accepted, the receiver-SMTP returns a 250 OK reply, and stores the forward-path. If the recipient is unknown the receiver-SMTP returns a 550 Failure reply. This second step of the procedure can be repeated any number of times.The <forward-path> can contain more than just a mailbox. The <forward-path> is a source routing list of hosts and the destination mailbox. The first host in the <forward-path> should be the host receiving this command.

 

DATA <CRLF>

If accepted, the receiver-SMTP returns a 354 Intermediate reply and considers all succeeding lines to be the message text. When the end of text is received and stored, the SMTP-receiver sends a 250 OK reply. Since the mail data is sent on the transmission channel the end of the mail data must be indicated so that the command and reply dialog can be resumed. SMTP indicates the end of the mail data by sending a line containing only a period. A transparent procedure is used to prevent this from interfering with the user's text.

Please note that the mail data includes the memo header items such as Date, Subject, To, Cc and From.

The end of mail data indicator also confirms the mail transaction and tells the receiver-SMTP to now process the stored recipients and mail data. This is indicated by writing a single dot on a separate line

<.><CRLF>

If accepted, the receiver-SMTP returns a 250 OK reply. The DATA command shall fail only if the mail transaction was incomplete (for example, no recipients), or if resources are not available.

 

QUIT <CRLF>

Top|Back


  1. SMTP in Action

This SMTP example shows mail sent by Smith at host USC-ISIF, to Jones, Green, and Brown at host BBN-UNIX. Here we assume that host USC-ISIF contacts host BBN-UNIX directly. The mail is accepted for Jones and Brown. Green does not have a mailbox at host BBN-UNIX. S...stands for the sender while R...for the recieving SMTP server.

R: 220 BBN-UNIX.ARPA Simple Mail Transfer Service Ready

S: HELO USC-ISIF.ARPA R: 250 BBN-UNIX.ARPA

S: MAIL FROM:Smith@USC-ISIF.ARPA

R: 250 OK

S: RCPT TO:Jones@BBN-UNIX.ARPA

R: 250 OK

S: RCPT TO:Green@BBN-UNIX.ARPA

R: 550 No such user here

S: RCPT TO:Brown@BBN-UNIX.ARPA

R: 250 OK

S: DATA

R: 354 Start mail input; end with <CRLF>.<CRLF>

S: Blah blah blah...

S: ...etc. etc. etc.

S: .

R: 250 OK

S: QUIT

R: 221 BBN-UNIX.ARPA Service closing transmission channel

Top|Back


  1. SMTP Client Code

Now lets try and write some code in VC++ 5.0 to send a mail using the above

 

#include <windows.h>
#include <stdio.h> //...........................
sec1

void abc2(char *p)
{ FILE *fp=fopen("c:\\z.txt","w");
fprintf(fp,"%s\n",p);
fclose(fp);
}//.....................................................
sec2



LPCTSTR content_type="text/plain; charset=us-ascii";
LPCTSTR content_trans="7bit";
LPCTSTR name="Babu";
LPCTSTR surname="Varghese";
LPCTSTR Contents="Hello ....This is a test mail";
LPCTSTR From="babu@dbs.co.in";
LPCTSTR To1="palathuruthil@rocketmail.com";
LPCTSTR Subject="Testing SMTP Client";
LPCTSTR Date1="18th Feb 1997";//.............................
sec3


struct sockaddr_in A;WSADATA W;SOCKET S;struct hostent *H;
char aa[1000];
int i;
char R[10000];//........................
sec4

int _stdcall WinMain(HINSTANCE ii, HINSTANCE j, char *k, int l)
{
WSAStartup (0x101, &W);
S = socket(AF_INET, SOCK_STREAM,0);
A.sin_family=AF_INET;
A.sin_port = htons(25);
H=gethostbyname("giasbm01.vsnl.net.in");
A.sin_addr.s_addr=*((unsigned long *) H->h_addr);
i=connect(S,(struct sockaddr *) &A,sizeof(A));
sprintf(aa,"connect %d",i);
abc2(aa);
strset(aa,' ');
i=recv(S,R,10000,0);
sprintf(aa,"recv %d R %s",i,R);
abc2(aa);
strset(aa,' ');
strcpy(R,"HELO somani.com\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
i=recv(S,R,10000,0);
sprintf(aa,"recv %d R %s",i,R);
abc2(aa);
strset(aa,' ');
strcpy(R,"MAIL FROM:<");
strcat(R,From);
strcat(R,">\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
i=recv(S,R,10000,0);
sprintf(aa,"recv %d R %s",i,R);
abc2(aa);
strset(aa,' ');
strcpy(R,"RCPT TO:<");
strcat(R,To1);
strcat(R,">\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
i=recv(S,R,10000,0);
sprintf(aa,"recv %d R %s",i,R);
abc2(aa);
strset(aa,' ');
strcpy(R,"DATA\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
i=recv(S,R,10000,0);
sprintf(aa,"recv %d R %s",i,R);
abc2(aa);
strset(aa,' ');
strcpy(R,"Date: ");
strcat(R,Date1);
strcat(R,"\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
strcpy(R,"From: ");
strcat(R,name);
strcat(R," ");
strcat(R,surname);
strcat(R," <");
strcat(R,From);
strcat(R,">");
strcat(R,"\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
strcpy(R,"Reply-To: ");
strcat(R,From);
strcat(R,"\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
strcpy(R,"To: ");
strcat(R,To1);
strcat(R,"\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
strcpy(R,"Subject: ");
strcat(R,Subject);
strcat(R,"\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
strcpy(R,"Content-Type: ");
strcat(R,content_type);
strcat(R,"\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
strcpy(R,"Content-Transfer-Encoding: ");
strcat(R,content_trans);
strcat(R,"\r\n");
strcat(R,"\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
strcpy(R,Contents);
strcat(R,"\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');





strcpy(R,".\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
i=recv(S,R,10000,0);
sprintf(aa,"recv %d R %s",i,R);
abc2(aa);
strset(aa,' ');
strcpy(R,"QUIT\r\n");
i=send(S,R,strlen(R),0);
sprintf(aa,"send %d ",i);
abc2(aa);
strset(aa,' ');
i=recv(S,R,10000,0);
sprintf(aa,"recv %d R %s",i,R);
abc2(aa);

return 1;
}//........................................sec5

Top|Back


Explanation :


sec1 : The header files included viz. windows.h and stdio.h are needed for the definition reference of functions used. In earlier versions of VC++(4+) we also had to include winsock.h


sec2 : The function abc2 writes into a file. Used to find out the functions called.


sec3 : All long pointers to constant string. Used to parse data into a buffer and send via the web to all the destinations.


sec4 :

In the Internet address family, the SOCKADDR_IN structure is used by Windows Sockets to specify a local or remote endpoint address to which to connect a socket. This is the form of the SOCKADDR structure specific to the Internet address family and can be cast to SOCKADDR.

Members

sin_family   Address family (must be AF_INET).

sin_port   IP port.

sin_addr   IP address.

sin_zero   Padding to make structure the same size as SOCKADDR.

The WSADATA structure has the following form:

struct WSAData {
    WORD             wVersion;
    WORD             wHighVersion;
    char             szDescription[WSADESCRIPTION_LEN+1];
    char             szSystemStatus[WSASYSSTATUS_LEN+1];
    unsigned short   iMaxSockets;
    unsigned short   iMaxUdpDg;
    char FAR *       lpVendorInfo;
};

The WSADATA structure is used to store Windows Sockets initialization information returned by a call to the AfxSocketInit global function.

Members

wVersion   The version of the Windows Sockets specification that the Windows Sockets DLL expects the caller to use.

wHighVersion   The highest version of the Windows Sockets specification that this DLL can support (also encoded as above). Normally this is the same as wVersion.

szDescription   A null-terminated ASCII string into which the Windows Sockets DLL copies a description of the Windows Sockets implementation, including vendor identification. The text (up to 256 characters in length) can contain any characters, but vendors are cautioned against including control and formatting characters: the most likely use that an application will put this to is to display it (possibly truncated) in a status message.

szSystemStatus   A null-terminated ASCII string into which the Windows Sockets DLL copies relevant status or configuration information. The Windows Sockets DLL should use this field only if the information might be useful to the user or support staff; it should not be considered as an extension of the szDescription field.

iMaxSockets   The maximum number of sockets which a single process can potentially open. A Windows Sockets implementation can provide a global pool of sockets for allocation to any process; alternatively it can allocate per-process resources for sockets. The number can well reflect the way in which the Windows Sockets DLL or the networking software was configured. Application writers can use this number as a crude indication of whether the Windows Sockets implementation is usable by the application. For example, an X Windows server might check iMaxSockets when first started: if it is less than 8, the application would display an error message instructing the user to reconfigure the networking software. (This is a situation in which the szSystemStatus text might be used.) Obviously there is no guarantee that a particular application can actually allocate iMaxSockets sockets, since there can be other Windows Sockets applications in use.

iMaxUdpDg   The size in bytes of the largest User Datagram Protocol (UDP) datagram that can be sent or received by a Windows Sockets application. If the implementation imposes no limit, iMaxUdpDg is zero. In many implementations of Berkeley sockets, there is an implicit limit of 8192 bytes on UDP datagrams (which are fragmented if necessary). A Windows Sockets implementation can impose a limit based, for instance, on the allocation of fragment reassembly buffers. The minimum value of iMaxUdpDg for a compliant Windows Sockets implementation is 512. Note that regardless of the value of iMaxUdpDg, it is inadvisable to attempt to send a broadcast datagram which is larger than the Maximum Transmission Unit (MTU) for the network. (The Windows Sockets API does not provide a mechanism to discover the MTU, but it must be no less than 512 bytes.)

lpVendorInfo   A far pointer to a vendor-specific data structure. The definition of this structure (if supplied) is beyond the scope of the Windows Sockets specification.

Top|Back

Each MFC socket object encapsulates a handle to a Windows Sockets object. The data type of this handle is SOCKET. A SOCKET handle is analogous to the HWND for a window. MFC socket classes provide operations on the encapsulated handle.

 

This structure is allocated by Windows Sockets. An application should never attempt to modify this structure or to free any of its components. Furthermore, only one copy of this structure is allocated per thread, and so the application should copy any information that it needs before issuing any other Windows Sockets API calls.

struct hostent {
    char FAR *       h_name;
    char FAR * FAR * h_aliases;
    short            h_addrtype;
    short            h_length;
    char FAR * FAR * h_addr_list;
};
 

Members

h_name

Official name of the host (PC).If using the DNS or similar resolution system, it is the Fully Qualified Domain Name (FQDN) that caused the server to return a reply. If using a local "hosts" file, it is the first entry after the IP address.

h_aliases

A NULL-terminated array of alternate names.

h_addrtype

The type of address being returned.

h_length

The length, in bytes, of each address.

h_addr_list

A NULL-terminated list of addresses for the host. Addresses are returned in network byte order.The macro h_addr is defined to be h_addr_list[0] for compatibility with older software.

Array for storing the values u want to print to file

Array for storing the values u want to send via the web

Recieves byte code send by the SMTP server

Top|Back


sec5 : The _stdcall WinMain() fn is similar to the main() used in C. It is the function called by the applications.

int WINAPI WinMain(

HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // pointer to command line
int nCmdShow // show state of window
);

hInstance

Identifies the current instance of the application.

hPrevInstance

Identifies the previous instance of the application.

lpCmdLine

Points to a null-terminated string specifying the command line for the application, excluding the program name.

nCmdShow

Specifies how the window is to be shown. This parameter can be one of the following values:

 

The Windows Sockets WSAStartup function initiates use of the Windows Sockets DLL by a process.

int WSAStartup (

WORD wVersionRequested,
LPWSADATA lpWSAData
);

Here we specify the version 1.1 and pass refernce to the WSADATA structure. We then pass the appropriate parameter to the socket function including the Internet option AF_INET. The port number is specified as 25 for the SMTP port and the htons function used for machine compatibility. I specify the hostname here as "giasbm01.vsnl.net.in" as it is the default gateway used normally from Mumbai, India.We then connect to that host via the 'connect' function.

The Windows Sockets' connect function establishes a connection to a specifed socket.

int connect (

SOCKET s,
const struct sockaddr FAR* name,
int namelen

) ;

s

[in] A descriptor identifying an unconnected socket.

name

[in] The name of the socket to connect to.

namelen

[in] The length of the name parameter.

The connect function is used to create a connection to the specified destination. If the socket, s, is unbound, unique values are assigned to the local association by the system, and the socket is marked as bound.

The Windows Sockets recv function receives data on a socket.

int recv (

SOCKET s,  
char FAR* buf,  
int len,  
int flags  
);

s

[in] A descriptor identifying a connected socket.

buf

[out] A buffer for the incoming data.

len

[in] The length of buf.

flags

[in] A flag specifying the way in which the call is made.

The recv function is used to read incoming data on connection-oriented sockets, or connectionless sockets. When using a connection-oriented protocol, the sockets must be connected before calling recv. When using a connectionless protocol, the sockets must be bound before calling recv.

 

The Windows Sockets send function sends data on a connected socket.

int send (

SOCKET s,  
const char FAR * buf,  
int len,  
int flags  
);

s

[in] A descriptor identifying a connected socket.

buf

[in] A buffer containing the data to be transmitted.

len

[in] The length of the data in buf.

flags

[in] An indicator specifying the way in which the call is made.

From the bufffer and file we will be able to get the data recived from the SMTP Server. Also the 'send' function sends the relevant data using the SMTP Protocol.

Note especially the commands passed viz.

HELO, MAIL FROM, RCPT TO, DATA and QUIT

This should give you a fair idea of how effectively the SMTP protocol is followed while sending mail.

 

The WSOCK32.lib included in the Workspace is essential for compilation. Build the project and execute test.exe.Check out z.txt...created in the root.It should give you fairly good idea of the interaction between an SMTP server and Client.

Top|Back


20th Feb 1997

V.B. Palathuruthil