ESMTP/SMTP client
Writing a SMTP client from scratch:
how to send an e-mail and an attached binary file
Objective of this article is to write some C++ code to send an e-mail, together with its
binary attachment, without buying any component on the web.
Prerequisite is being in posses of the DLL described in Build your
own ASPI dll. You are certainly asking: what does ASPI have to do with the SMTP client?
Nothing. But I just needed some place to put into several win32asm functions that encode
Base64 the binary attachment, and I developed and added these functions into the ASPIDLL.DLL
for the sake of simplicity.
The DLL is freeware from this site, and comes complete with full source code. You can also
download it already assembled and zipped, in case you are not familiar with MASM32.
SMTP stands for Simple Mail Transfer Protocol, and is a very simple protocol
used to send mail messages from a sender to a recipient. It is one of the first
and most used Internet protocols, released in 1982 and described in RFC 821.
Mail messages, in turn, are messages sent over the Internet according to the SMTP
client/server model outlined below. These messages must be formatted according to RFC 822
(Standard for ARPA Internet Text Messages). This makes it evident that mail messages
are pure text messages, nothing but normal, plain, character messages.
Additional note: ESMTP stands for Extended SMTP, and is a successive implementation of SMTP that
is described in some RFCs that you can find on the web. Essentially ESMTP adds some features
to the SMTP (without altering it) and uses some forms of authentication mechanisms to identify
the mail sender and thus reducing the possibility of spamming (see RFC 2554).
SMTP servers are servers that operate according to RFC 821. SMTP clients
are clients that operate according to RFC 821 too.
Original SMTP did not define any provision for attachments, and for binary attachments less
than never, but some elegant *tricks* have been invented since 1982 to accomodate this
indispensable feature. Please note that the original SMTP protocol is still in use even
when attachments are sent, and the protocol itself remains unaltered.
These "tricks" are named MIME and Base64 encoding, and are described into
the following paragraphs.
MIME stands for Multipurpose Internet Mail Extension and is defined in
RFC 2045, RFC 2046, RFC 2047, RFC 2048, RFC 2049
(fear not, you don't have to read them all).
MIME defines the following:
- how to divide a mail message into several parts
- how to delimit each part
- how to describe and characterize each part
In this project "parts" help us dividing the mail into
a text message and a binary attachment.
Because mail messages should always be pure text and the attachment is binary,
it is necessary to convert the binary attachment into a stream of text characters before
it is put into the part of the mail message that will be reserved for the attachment.
Base64 encoding makes this possible. It is a very simple algorhytm that converts a
sequence of binary data into a sequence of characters. It has only one drawback: it expands
the attachment by a factor of 4/3. So a 3 kB binary file would convert
into a 4 kB text attachment. I think that this overhead is well worth the service
it provides, and anycase it is universally accepted.
You can easily find on the Internet how does the Base64 encoding scheme work. In these web
pages you will find a free implementation, see Build your own ASPI dll.
I had to put the required assembly functions into a DLL, and I decided to put everything
into the DLL already defined in the ASPI programming section of
these web pages.
You have access to the source code, you can build the DLL, or you can download a zipped copy
of the already assembled DLL (or you can code your own implementation of a Base64 encoder,
it is very simple).
I did not write the Base64 decoder because I only wanted to send a mail and not to receive it.
Base64 decoding will be performed by your Eudora or OutLook as you receive the mail and its
attachment.
The ESMTP/SMTP server works like this:
- as soon you connect port 25 of the ESMTP/SMTP server, it sends you a welcome message
and stands there waiting for your requests
- the ESMTP/SMTP client (i.e.: your application program) sends some requests, one at
a time, and receives responses back
- both requests and responses are ASCII characters (no strange characters allowed, only
plain text characters)
- for each request you get one (only one) response
- you can't send another request if the response to the previous request has not been
received yet
- requests and responses togheter form a conversation
- requests talk about what and where to send e-mail stuff
- responses *usually* make the ESMTP/SMTP client aware that the ESMTP/SMTP server has
correctly received the requests
Typical requests, the ones that will be used in this project, are:
- HELO: the client says "hello" to the SMTP server and introduces itself; must be
followed by the name of the client; example:
HELO foobar mail sender
- EHLO: same as above, but the target server is an ESMTP server (EHLO stands for
Extended HELO)
- AUTH: request for authentication, used only with ESMTP servers (not in use with
normal SMTP servers); the only auth request that will be covered here is AUTH LOGIN
- MAIL: tells the ESMTP/SMTP server who is the mail sender; example:
MAIL From: <donald.duck@disney.com> and it is used as a return address
for undeliverable mails
- RCPT: tells the ESMTP/SMTP server who is the mail recipient; example:
RCPT To: <mickey.mouse@disney.com>
- DATA: asks the ESMTP/SMTP server to put itself into a "listening"
state since the client is ready to send the body of the mail and its eventual
attachments; this requires each mail part to be formatted according to RFC 822 and
the whole message to be structured according to the MIME 1.0 RFCs
- QUIT: says goodbye to the ESMTP/SMTP server
Typical responses are three cyphers numbers followed by an ASCII descriptive text.
Within the application program only the numbers are useful to understand what is
happening. Descriptive text is useful only when interactively talking to
the SMTP server using telnet to make experiments.
(When approaching a ESMTP/SMTP server using telnet on port 25 to make experiments it could
happen that you receive *more* than one response from the server. Don't worry: during the
execution of the application program this will not happen.)
A final note: within your application program you will receive one, and
only one, response to each request.
The described structure is relative to the objective of this article, that is sending a
text message followed by a binary attachment, according to RFC 822 and MIME RFCs. This is
the *logical* structure of the mail:
From: <donald.duck@disney.com> CRLF
To: <mickey.mouse@disney.com> CRLF
Subject: foobar CRLF
MIME-Version: 1.0 CRLF
Content-Type: multipart/mixed; CRLF
boundary= "KkK170891tpbkKk__FV_KKKkkkjjwq" CRLF
CRLF
--KkK170891tpbkKk__FV_KKKkkkjjwq CRLF
Content-Type: text/plain; charset=US-ASCII CRLF
CRLF
here goes the text message CRLF
CRLF
--KkK170891tpbkKk__FV_KKKkkkjjwq CRLF
Content-Type: application/octet-stream CRLF
Content-Transfer-Encoding: base64 CRLF
Content-Disposition: attachment; CRLF
filename= "suggested name of the attachment" CRLF
CRLF
here goes the Base64 encoded attachment CRLF
--KkK170891tpbkKk__FV_KKKkkkjjwq-- CRLF
CRLF.CRLF
Please note the following:
- CR and LF stand for Carriage Return and Line Feed
- each line *must* be terminated by a CRLF because the conversation is character based,
and CRLF let the receiver understand that a line of text has been completed
- a CRLF alone means a blank line
- multipart/mixed means that the mail message is made of more than one part, in this
case a plain text message and a binary attachment, and the parts are of different type
(mixed)
- the string KkK170891tpbkKk__FV_KKKkkkjjwq is a casual arbitrary string of my
invention (you can invent one of yours) that is declared in the boundary= attribute
and used by the mail receiving agent to identify each single part of the mail message
- each part of the message is contained inbetween two consecutive boundary strings
- once declared, successive occurrences of the boundary string must be preceeded by two hyphens
- the last boundary string occurrence is preceeded and followed by two hyphens
- each part in the mail is made of a content-decriptor header followed by a blank
line followed by the actual content
- in the above structure two parts have been defined:
- a text/plain mail text message
- a application/octect-stream mail attachment
- the words boundary and filename are preceeded by a blank character
because they are actually the prosecution of the preceeding line; the line has been
splitted in two to make it more readable, but you can send it all in only one line if
you want
- the final dot enclosed between two blank lines (CRLF.CRLF) tells the SMTP
server that it can stop listening for the DATA to arrive (in othe words the
mail is complete).
Like any client application you should put a client socket component on the form of
your Borland C++ Builder project. Socket communication in Win32 is event driven, so the
application program should provide some event handlers to implement the SMTP client.
Furthermore you need a button component to start the process of sending the mail. In the
provided code the button is called btMail, and its onClick event is called
btMailClick.
Here you will find the source code.
It is a mix of true code and comments. You should provide all the things
described in the comments. There is also some pseudo-code, that you can easily implement
according to your requirements. Pseudo-code is given in angle brackets.
The code has been arranged in a state machine fashion. This means that at any
given time the program stands in one, and only one, well defined program status.
Switching between statuses can only happen according to the accomplishment of some
requisites.
Capital letters variables are global, and must be provided before clicking the send mail
button. Please note the following:
- currentProgramStatus is a global variable that defines the current status of
the program
- a static text component should be put on the form to display the current status of
the program; the pointer to this component is called stStatus; in makes the user
aware of what is happening
- many Sleep statements you will encounter within the code: these have been placed
there because I think that small pauses will make the communication more reliable, without
affecting the total length of the process; please consider that the only time consuming cycle
is the loop that sends the attachment, but in that loop the Sleep will be executed only
in case of effective usefulness
- a progress bar is useful to display the advancement of the attached file being sent
- all SMTP conversation response codes (220, 221, 235, etc.) are fully described
in a lot of easy catch sites in the web; please refer to any description
- the Base64 encoded attachment is sent 512 bytes at a time in a while
loop, as you can see below. I can't remember why I used SendBuf
instead of SendText, but whichever the reason I had to put a carriage
return and a line feed after each 512 bytes sent
The VCL documentation says that SendBuf returns the actual number of
bytes sent, and it is true, but the assertion is made in such a way that
the programmer might understand that the buffer can be partially sent,
and in this case the return value would be the actual number of sent
bytes in the sense that some bytes have been left unsent.
During my experiments I observed that a buffer is never partially
sent. The buffer is always either completely sent or not
sent at all. In this latter case SendBuf returns -1.
According to this the while loop in the source code provided above only checks
whether SendBuf returns -1, and in this case the loop stops 30 milliseconds
and then repeats the SendBuf. Very simple and effective.
When using TClientSocket -> SendText to send lines of text to a Linux server,
also remember to terminate each line with a "newline" (10) and a "zero" (0)
(no need to add a "carriage return"), or extra bytes will be sent over the
socket.
|