Embed with the Mailman - Embedded.com

Embed with the Mailman

Next time you need to communicate with a TCP/IP-enabled device, why not send an e-mail? Here's a tiny SMTP server for receiving messages and a client for sending back a response.

When developers consider methods for communicating with networked embedded systems, they usually turn to the traditional mechanisms such as the sockets API or web interfaces such as HTTP. This article focuses on a less traditional method that uses an e-mail client. We'll look at the components and processes that are involved in transporting e-mails through the Internet. Then we'll construct a tiny embeddable SMTP server and client written in C that can be used as an interface to remote embedded systems. (Extended code for this article is available at ftp://ftp.embedded.com/pub/2001/jones10.)

The obvious question is, why would anyone want to use e-mail to communicate with a networked embedded system? The advantages are numerous. First, the user interface is the standard e-mail client that we all use daily and with which even the most non-technical user is familiar. Second, the protocols are in place to provide transport for e-mail all over the Internet from a variety of devices (desktops, handhelds, and even web-aware phones). Next, with just a little extra effort, we can encode our e-mail responses from the embedded devices with HTML tags to provide a richer, more appealing presentation of the encapsulated data. Finally, e-mail clients provide their own logging mechanism, which enable us to archive data for historical purposes.

Components of a mail system

Before we jump into the technical details, let's look at the basic processes for sending and receiving e-mails over the Internet.

When we send e-mail, we use a Mail User Agent (MUA). This is our standard e-mail client. The MUA communicates with a local Mail Transport Agent (MTA), whose job it is to transfer the e-mail to the destination MTA. The destination MTA makes the e-mail available to the recipient MUA through a local delivery agent (such as POP3). This is shown in Figure 1.

The protocol used to transfer e-mail between clients and MTAs is called the Simple Mail Transfer Protocol (SMTP), details of which can be found in RFC-821. The destination MTA receives an e-mail, which is spooled on the receiving system and can then be collected by the recipient through access via POP3 (or Post Office Protocol, version 3). The Post Office Protocol was created as a way to avoid having to host an SMTP server on every node that wants to send or receive e-mail.

An example session

SMTP is a simple ASCII-based protocol that can be performed through a standard telnet client. Knowing that the MTA resides on port 25, let's start an interactive session with a standard MTA called Sendmail (user input is shown in red):

$ telnet 192.168.1.1 25
Trying 192.168.1.1…
Connected to 192.168.1.1.
Escape character is '^]'.
220 192.168.1.1 Sendmail 8.9.3/8.8.7;
    Sat 10 Feb 2001 16:19:14 -0700
HELO itsmeathome.com
250 192.168.1.1 Hello
    IDENT:mtj@[192.168.1.2], pleased to meet you
MAIL FROM:me@itsmeathome.com
250 me@itsmeathome.com…Sender ok
RCPTTO:you@targetsmtpdomain.com
250 you@targetsmtpdomain.com…
    Recipient ok
DATA
354 Enter mail, end with “.” on a line by itselfSubject: Hi

Hello There!

250 RAAA03174 Message accepted for delivery
QUIT
221 192.168.1.1 closing connection
Connection closed by foreign host
$

After opening a socket session via telnet, we identify ourselves with the “HELO” salutation command. Next, we tell the MTA from whom the mail is coming by using the “MAIL FROM” command, and to whom the mail is going with the “RCPT TO” command. Finally, we give the “DATA” command to instruct the MTA that the following text is the body of our e-mail. After entering some text for the body, we end the e-mail with a single “.” on a line by itself. At this point the MTA goes to work to deliver our e-mail to the named recipient.

This is an example that uses few features, but it's enough for our requirements. It's shows that an MTA is simply a specialized command interpreter. After each command is issued to the MTA, a numeric response code is returned so that we can determine success or failure and act accordingly. This model is used by a number of other protocols, including POP3 and Network News Transfer Protocol (NNTP).

Simple SMTP server and client implementation

Now that we've discussed how the SMTP protocol works, let's get down to the nuts and bolts of a simple implementation suitable for embedded systems.

The goal of this implementation is to develop a command-and-response protocol within the SMTP transport system. Our server will receive e-mails and then parse them for commands (in this case, the command is placed in the subject line of the e-mail). If a valid command is found, a handler is called to generate the e-mail response that is passed to our client implementation for transmission back to the user. The entire system is illustrated in Figure 2.

The components for the embedded SMTP server and client differ somewhat from the traditional diagram seen in Figure 1. The client MUA is configured with an SMTP gateway (outgoing mail server) as the address of the embedded device (since our server will act as both the MTU and automated MUA). The embedded client uses the SMTPGATEWAY #define (from Listing 1) to define where response e-mails are to be sent.

Listing 1: Header file for the SMTP server and client

#define MAX_SUBJECT 80
#define MAX_RCPT 80
#define MAX_CONTENT_TYPE 80
#define MAX_SPECIAL 256
#define MAX_CONTENT 2048

struct mailHeader {
            char subject[MAX_SUBJECT+1];
            char mailRcpt[MAX_RCPT+1];
            char specialHeaders[MAX_SPECIAL+1];
            char contentType[MAX_CONTENT_TYPE+1];
            char contents[MAX_CONTENT+1];
};

/*
* System Configurable Parameters Below:
*/

#define SMTPGATEWAY “192.168.1.1”
#define SOURCE_E-MAIL_ADDRESS “device@mydomain.home”

Implementation summary

The main function (not shown here, but available in complete source form at www.embedded.com/code) provides basic setup of the server socket for incoming mail connections. We bind the socket to port 25, the SMTP service port. After this, we enter a loop awaiting connections and call the mailReceive function when a client connects. To simplify our implementation, we'll only handle one connection at a time.

The mailReceive function implements the SMTP server protocol, as shown in Listing 2. Our first step in mailReceive is to send the salutation to initiate the dialogue with the client and await its response. We then wait for the “MAIL FROM,” “RCPT TO,” and “DATA” commands in series, storing results and sending the appropriate return codes.

Receiving the body of the e-mail is the simple process of buffering all characters received until a special sequence of characters is received. The client sends the string “.” (a period on a line by itself) to indicate that the e-mail is complete. We then acknowledge the end of the message and await the “QUIT” command from the client.

At this point we have the received e-mail stored in the mail character array. We then call the mailParse function to analyze the e-mail for commands.

The mailParse function, shown in Listing 2, simply searches the e-mail for the “Subject:” header string and stores the subject-line if found. The subject is then compared to a list of known commands supported by the target (PING, SENSOR, and RESET) and the appropriate handler is called.

Listing 2: SMTP server

void mailReceive(int fd)
{
    int bufIdx = 0, state = 0, stop = 0, i, len;
    char buffer[256]={0};
    char sender[256], recipient[256];

    /* Indicate ready to go and handle premature close or port-scan */
    if (write(fd, salutation, strlen(salutation)) <= 0)="">
    return;
    }

    /* Wait for HELO */
    len = read(fd, buffer, 255); buffer[len] = 0;

    if ((strncmp(buffer, “HELO”, 4)) && (strncmp(buffer, “EHLO”, 4))) {
        closeConnection(fd);
        return;
    }

    /* Wait for MAIL FROM: */
    write(fd, goahead, strlen(goahead));
    len = read(fd, buffer, 255); buffer[len] = 0;
    if (strncmp(buffer, “MAIL FROM”, 9)) { closeConnection(fd); return; }
    parseAddress(&buffer[9], sender, 256);
    if (sender[0] == 0) { closeConnection(fd); return; }

    /* Wait for RCPT TO: */
    write(fd, goahead, strlen(goahead));
    len = read(fd, buffer, 255); buffer[len] = 0;
    if (strncmp(buffer, “RCPT TO”, 7)) { closeConnection(fd); return; }
    parseAddress(&buffer[7], recipient, 256);
    if (recipient[0] == 0) { closeConnection(fd); return; }

    /* Wait for DATA */
    write(fd, goahead, strlen(goahead));
    len = read(fd, buffer, 255); buffer[len] = 0;
    if (strncmp(buffer, “DATA”, 4)) { closeConnection(fd); return; }

    write(fd, gimmesome, strlen(gimmesome));

    while (!stop) {
        len = read(fd, buffer, 80);

        if (bufIdx+len > MAX_E-MAIL_SIZE) { closeConnection(fd); return; }
        if (len <= 0)="" {="" closeconnection(fd);="" return;="">
        else {
            memcpy(&mail[bufIdx], buffer, len);
            bufIdx += len;
        }

        /* Look for the terminating . */
        for (i = 0 ; i < len="" ;="" i++)="">
            if ((state == 0) && (buffer[i] == 0x0d)) state = 1;
            else if ((state == 1) && (buffer[i] == 0x0a)) state = 2;
            else if ((state == 2) && (buffer[i] == 0x0d)) state = 1;
            else if ((state == 2) && (buffer[i] == '.')) state = 3;
            else if ((state == 3) && (buffer[i] == 0x0d)) state = 4;
            else if ((state == 4) && (buffer[i] == 0x0a)) { stop = 1; break; }
else state = 0;
        }

    }

    write(fd, gotit, strlen(gotit));

    /* Wait for QUIT */
    for (i = 0 ; i < 10="" ;="" i++)="">

        len = read(fd, buffer, 255); buffer[len] = 0;

        if (strncmp(buffer, “QUIT”, 4)) {
            closeConnection(fd); return;
        } else break;

    }

    mailParse(mail, bufIdx, sender);

    write(fd, closeit, strlen(closeit));

    return;
}

Each handler in the example code performs the same basic function. We allocate a mail Header structure, fill in the fields based on the particular response we want to provide, and call the mailSend function to send the e-mail (see Listing 3). The functions sendResponse and processCommand are fairly self-explanatory. The function sendSensorData differs in that instead of sending simple text, an HTML-encoded response is returned. The only difference, other than the contents of the e-mail itself, is the content type that is provided. The type “text/plain” tells the e-mail client that the e-mail is standard text, whereas “text/html” indicates that HTML tags may be embedded in the text. These handlers are simple but could be expanded to support any embedded tags permitted by e-mail clients (including transport of image or sound data).

Listing 3: E-mail message parser

int mailParse(char *mbuf, int len, char *sender)
{
    int i = 0, index=0;
    char subject[MAX_SUBJECT];

    int sendResponse(char *);
    int sendSensorData(char *);
    int processCommand(char *, char *);

    bzero(&subject, MAX_SUBJECT);

    /* Pull the subject line as part of the command. */
    for (i = 0 ; i < len="" ;="" i++)="">
        if (mbuf[i] == 'S') {
            if (!strncmp(&mbuf[i], “Subject:”, 8)) {
                for (i+= 9; ((mbuf[i] != 0x0d) && (mbuf[i] != 0x0a)) ; i++) {
                    subject[index++] = mbuf[i];
                    if (index == MAX_SUBJECT-1) break;
                }
                subject[index] = 0;
                break;
            }
        }
    }

    /* Figure out what the sender wanted and reply accordingly */
    if (!strncmp(subject, “PING”, 4)) {
        printf(“Received PING commandn”);
        sendResponse(sender);
    } else if (!strncmp(subject, “SENSOR”, 6)) {
        printf(“Received SENSOR commandn”);
        sendSensorData(sender);
    } else if (!strncmp(subject, “RESET”, 5)) {
        printf(“Received RESET commandn”);
        processCommand(sender, subject);
    } else {
        /* Don't respond if we didn't receive a valid request. */
        printf(“No handler available for [%s]n”, subject);
    }

    return(0);
}

int sendSensorData(char *sender)
{
    #define MAX_SENSORS3
    struct mailHeader mail;
    int dummySensorData[MAX_SENSORS] = {28, 11, 19};
    int i;

    bzero(&mail, sizeof(struct mailHeader));

    strcpy(mail.subject, “Sensor Data Response”);

    strcpy(mail.mailRcpt, sender);
    strcpy(mail.contentType, “text/html”);

    strcat(mail.contents, “Sensor                 Data“);
    strcat(mail.contents, “

“);
    strcat(mail.contents, “

Current Readings are:

“);

    strcat(mail.contents, “

“);
    strcat(mail.contents, ““);

    for (i = 0 ; i < max_sensors="" ;="" i++)="">
        sprintf(&mail.contents[strlen(mail.contents)],
            “

“, i, dummySensorData[i]);
    }

    strcat(mail.contents, “

“);
    strcat(mail.contents, “Sensor Data“);
    strcat(mail.contents, “
SensorValue
%1d%2d
“);

    mailSend(&mail);

    return(0);
}

Finally, the mailSend routine implements the SMTP client portion of the protocol. This is essentially a reverse implementation of our server code that speaks to an external SMTP server.

Also of interest is the client header file that defines the mailHeader structure (see Listing 1). Most of the fields are self explanatory, but one special field that was added is the specialHeaders field. If you configure your e-mail client to make all of the header fields of an e-mail visible, you'll see quite a few that you might not be familiar with. Many of these can be found in the IETF document “Common Internet Message Headers” (RFC-2076). With the specialHeaders field, you can alter the handling of the e-mail. Example headers that can be added are “Priority: Urgent” to note the importance of the e-mail or “Content-MD5:” (plus a checksum) to ensure at the receiver that the e-mail has not been modified en-route.

Test setup

The sample code can be compiled and run directly on Linux for test purposes. Before compilation, the software must be configured with two parameters for proper operation (found in client.h ).

The first parameter is the SMTPGATEWAY #define, which tells the embedded SMTP client where to send the e-mail. This is commonly an SMTP gateway for which you have either an account to send or receive e-mail or a gateway that provides an e-mail relay service. SMTPGATEWAY may be defined as an IP address or as a domain name (special code in the mailSend function provides for name resolution).

The second configurable parameter is the SOURCE_E-MAIL_ADDRESS . This parameter is mostly cosmetic and provides the contents of the “From:” field in the response e-mail indicating from whom it came. Once these parameters are configured, a simple make will build the tinyms image.

The desktop e-mail client from which e-mails are sent must be configured with the SMTP gateway (or outgoing server) defined as the IP address of the embedded device. When sending an e-mail to the software, it doesn't matter to whom the e-mail is addressed, because the first hop of the e-mail is through the predefined SMTP gateway (the embedded device). Since our embedded device doesn't care about addressing, it simply processes all e-mails it receives. The e-mail response from the embedded device will be directed to whomever sent the e-mail. Remember that the subject line contains the command to perform (such as SENSOR or PING). This implementation ignores the body of the command e-mail, but could be easily modified to do so since the entire e-mail is stored within the mail character array.

An interesting alternative

Now we have a way to e-mail our embedded device and have it respond. This is a minimal configuration, but it provides an interesting alternative to more common communication methods.

The implementation could be expanded in a number of ways, including support for XML messages within the e-mail payload (given an XML parser implementation). Support for binary attachments could also be added for both incoming and outgoing e-mails using Base64 or Quoted-Printable MIME encoding/decoding methods. This would provide the ability to e-mail software updates to the remote device or receive binary collected data from the device (in addition to images or other multimedia data). Finally, with almost no modification, asynchronous e-mails could be supported to automatically send e-mails from the embedded device based upon alarm conditions.

M. Tim Jones is a software engineer with Emulex in Longmont, CO. He has been developing embedded software since 1986 and has designed and developed software ranging from OS kernels for spacecraft to terrestrial embedded network applications. Tim has bachelor's and master's degrees in computer science. He can be reached at .

Resources

Return to October 2001 Table of Contents

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.