SMTP Client
Overview
This simple tutorial demonstrates how Mongoose Library can be used to implement an SMTP client over TLS.
SMTP in a nutshell
SMTP was originally a very simple protocol, hence its name (Simple Mail Transport Protocol), commands are four-letter human readable words and responses are three-digit codes, followed by a human readable text. It even has a built-in HELP command !
Focusing on the mail transfer, we find the following states or phases:
- The client connects, the server greets with a
220
code - The client sends
HELO
and its name, the server responds250
- The client sends
MAIL
followed byFROM:
and its email address, the server responds250
again - The client sends
RCPT
followed byTO:
and the recipient email address, the server responds250
again - The client sends
DATA
, the server responds354
- The client sends the mail body, and ends it with a dot
.
on a line by itself, the server responds with the250
code once more - The client sends
QUIT
, the server responds221
and the session is finished
Due to the necessity of having proper authentication, ESMTP (for Enhanced SMTP) was introduced. The client now starts the dialog by sending EHLO
instead. The server will then respond with a list of capabilities, among which there'll be those we need to start a TLS session and exchange authorization credentials.
TLS and authentication
Now focusing on establishing a TLS session and authenticate to the server, we find the following states or phases:
- The client sends
EHLO
and its name, the server responds250
and a list of its cababilities. - The client sends
STARTTLS
, the server responds with a220
code and the TLS handshake starts - The server sends its certificate and the client validates it against a CA certificate
- The client sends
EHLO
and its name again, the server responds250
and a list of its cababilities at this stage - The client chooses an authentication method and sends
AUTH
, followed by the chosen method and any credentials; the server responds with a235
code and the client may proceed with the mail transfer
Main function
In the main()
function we initialize an event manager, start our client, and start the event loop.
- The client connection is created by calling mg_connect() and passing it a pointer to the URL, and a pointer to the event handler function, with an optional argument.
Event handler
The event handler function is called by the event manager every time there is an event to be handled, here we will use:
MG_EV_OPEN
: The connection has been createdMG_EV_READ
: There is outstanding data, received from the socket. We will mainly drive here our state machineMG_EV_TLS_HS
: The TLS handshake has been done, we resume our processingMG_EV_CLOSE
: The connection has closed, we just set a variable so the main loop exits
The SMTP client evolves through a series of states. We use c->data
, an auxiliary buffer, to hold the client state; it is initialized to zero at startup so that will be our first state. Those are the following:
EHLO
: We have just received the server greeting and send theEHLO
command, our next state, once the server responds, will beSTARTTLS
STARTTLS
: The server has just sent its list of capabilities. We send theSTARTTLS
command and wait for its confirmationSTARTTLS_WAIT
: The server has just sent its confirmation; so we initialize TLS by calling mg_tls_init(). Our next state will beAUTH
. Once the TLS handshake is done, we'll get anMG_EV_TLS_HS
event; then we'll sendEHLO
again to restart the initial handshake and get the server capabilities, among them those belonging to authentication.AUTH
: The server has just sent its list of capabilities. We send theAUTH
command along with the chosen authentication method and proper credentials, and wait for its confirmationFROM
,TO
,DATA
,BODY
: Once the server confirms, we send the rest of the pertaining commands along with proper data, in order to send the mail. After sending the mail body, we send an extra line containing only a dot and the end of line sequence.QUIT
,END
: Once the server acknowledges receipt of the mail to be sent, we send theQUIT
command, and after we get its confirmation we request Mongoose to drain its output buffer and close the connection when it has finished.
Note that this client is very simple and does not do any form of error checking neither at the SMTP level nor at the connection level. For the latter, check the error handling tutorial.
Build and run
Before trying this example, open main.c
and change the email server, sender, and recipient addresses.
- Follow the Build Tools tutorial to setup your development environment.
- Start a terminal in the project directory; if you've not already done so, clone the Mongoose Library repo
git clone https://github.com/cesanta/mongoose
- Build and run the example. It requires TLS support, check the "How to build" section of the TLS tutorial for specific information on building options for your OS
- Observe the log
26f6264f 2 main.c:21:fn <-- 220-domain.com ESMTP Exim 4.95 #2 Wed, 28 Sep 2022 12:24:38 -0500 26f6264f 2 main.c:62:fn --> EHLO myname 26f626fe 2 main.c:21:fn <-- 250-domain.com Hello myname [ip address] 250-SIZE 52428800 250-8BITMIME 250-PIPELINING 250-PIPE_CONNECT 250-AUTH PLAIN LOGIN 250-STARTTLS 250 HELP 26f626ff 2 main.c:62:fn --> STARTTLS 26f627a6 2 main.c:21:fn <-- 220 TLS go ahead 26f627a7 2 main.c:62:fn --> 26f62905 2 main.c:67:fn TLS handshake done! Sending EHLO again 26f629ab 2 main.c:21:fn <-- 250-domain.com Hello myname [ip address] 250-SIZE 52428800 250-8BITMIME 250-PIPELINING 250-PIPE_CONNECT 250-AUTH PLAIN LOGIN 250 HELP 26f629ab 2 main.c:62:fn --> AUTH PLAIN kjHKJHkjhkJHkjhaWxlQHdlYnRvcklhgkjggKLJGIUYGIK== 26f63228 2 main.c:21:fn <-- 235 Authentication succeeded 26f63228 2 main.c:62:fn --> MAIL FROM: <aaa@domain.com> 26f632cd 2 main.c:21:fn <-- 250 OK 26f632cd 2 main.c:62:fn --> RCPT TO: <bbb@domain.com> 26f6337e 2 main.c:21:fn <-- 250 Accepted 26f6337e 2 main.c:62:fn --> DATA 26f63423 2 main.c:21:fn <-- 354 Enter message, ending with "." on a line by itself 26f63423 2 main.c:62:fn --> From: My Mail Sender <aaa@domain.com> Subject: Test email from Mongoose library! Hi! This is a test message. Bye. . 26f634c9 2 main.c:21:fn <-- 250 OK id=1odfhf-000PfG-Dy 26f634c9 2 main.c:62:fn --> QUIT 26f636ea 2 main.c:21:fn <-- 221 domain.com closing connection 26f636ea 2 main.c:60:fn end 26f636ea 2 main.c:62:fn -->
Notes on TLS support
- The included set of CA certificates will cover major sites, but if your site certificate happens to be signed by a CA that is not included there, you'll have to get that certificate. You'll be able to tell because the example will fail right after sending STARTTLS:
26d1ad47 2 main.c:62:fn --> STARTTLS 26d1adf7 2 main.c:21:fn <-- 220 TLS go ahead 26d1adfa 2 main.c:62:fn --> 26d1aead 1 mongoose.c:404:mg_error 1 0x5 TLS handshake: -0x2700
- If you don't know beforehand, run a network sniffer and at the start of the certificate exchange you'll be able to see your site's certificate and those who signed it. Go the issuer/signer site and get the root of trust certificates; replace
ca.pem
with those.
For more information on developing TLS clients, check the TLS tutorial
Notes on Google accounts
If you want to try this on a Gmail account, or a Workspace account with its own domain name that uses Google as its provider, you need to create an App Password. At the time of writing this tutorial, this requires that your account has 2-factor authentication enabled, otherwise this option won't even show in your Security menus. Once you create this App Password, you will use it with your email address and in place of your account password; that is:
static const char *server = "tcp://smtp.gmail.com:587";
static const char *user = "myusername@gmail.com";
static const char *pass = "AppPassword4this";
To create your Application Password, just follow Google's documentation