Monday, June 11, 2012

Minimalist C SMTP Mail Server

For one of my projects, I thought it would be really useful for my home file server to be able to receive emails from public email addresses.  This way I would be able to send emails from any contemporary email address to some magic "something@kwf.dyndns.org" email address, and have it be received on my server.  This is different than how most people are used to interacting with their email, because most people expect to have something like example@gmail.com, and then expect Google to deal with keeping a server on all the time to handle incoming mail (via SMTP), so that the user only needs to use a mail agent (such as Outlook or Gmail's web interface) to interact with email when they feel like it.

The problem is that, while digging through all of the documentation for Postfix and Sendmail, which are the programs one would typically use to set up these email backends, is that they do way more than I need and are likewise much more complicated than I need (as they should be - email is a hard thing).  My project really only needs the most basic of mail processing; receive an email, figure out which account it is addressed to, and extract the subject line and actual body of the email.  What I plan on doing with it once I have these things becomes quite a bit easier, so I really only want an email server which can handle the network exchange and then intelligently punt received emails to different programs to do with as they please.

Many moons ago, I happened upon the very interesting Mailinator blog. Mailinator is a rather useful service providing you with disposable email addresses, but what I most enjoy about it is Paul's very interesting technical articles on the trials of writing your own high-performance custom-application mail server.  In one of his early articles, he really impressed on the fact that SMTP (Simple Mail Transfer Protocol) is actually a really simple protocol, so I figured that if I think Postfix is too complicated for what I need, why don't I just write my very own mail server from scratch?

SMTP, like the vast majority of protocols used throughout the Internet, has been freely documented in a series of documents called RFCs, or "Request for Comments."  Granted, just because something exists as an RFC does not mean that it is actually used, but it's a good place to start looking for how the Internet works.  The basics of SMTP happen to be documented in RFC821, which is a somewhat intimidating 67 pages long.  It is a running joke that actually trying to read RFCs is a nightmare, and the jokes are mostly correct; reading 67 pages on the gripping subject of how mail is transferred between mail servers is simply not an exciting exercise.  On the other hand, once I managed to get 400 lines of C to handle enough of SMTP to receive mail and print it on the screen, that was pretty cool.



How SMTP Works

 SMTP really is a very simple protocol.  Given an email that needs to be delivered to another server, your mail server connects to port 25 on the remote server, and then trades four letter verbs for three digit return codes.  For example, when an exchange first starts, the connecting server sends a "HELO" verb, and the remote server typically responds with a "250" code indicating the completion of the verb.  Once a connection is established, moving emails is nothing more than a long series of these back and forth until there's no more work to be done.

The Wikipedia article and the RFC both give simple exchange examples, and I will supplement those with one of my own, captured with my very own SMTP server:





The Source Code

Now let us be very clear here; I am not posting this 400 line SMTP server in the hopes that you find it useful as a mail server.  I very much hope that I do not ever actually see a copy of this running in the wild, and here is why: this is not a good SMTP server.

It just isn't.  It is a rather neat little toy to play with, and I think it is a very educational example of the basis for how SMTP works, Posix threads, and network programming.  That is all I think it is good for, at this point.  It isn't done yet, because I have built in almost no safe-guards with respect to remote servers doing anything naughty or unexpected.  There is no limits on bandwidth usage, RAM usage, number of connections opened at once, nothing.  That being said, all of those things would make this quite a bit longer, and muddle the basics of exactly how it works.  I thought you may find this academic example interesting, but dear God don't treat it as anything except an academic example.

That all being said, here is 409 lines of C which can manage to receive basic SMTP transfers from other mail hosts. It does crap out on emails with attachments for some reason that I don't quite understand, but this project is not even close to finished, but simply at a good point for me to take pause for your benefit.  Source code:


5 comments:

  1. I'm not a 100% sure what exactly where you are going to send emails, but there is something you might need to think about.
    A few years ago I needed to send mail from a few different locations with the same respond to adress, but my ISP only allowed me to send from their server if I was actually on thier network. So i ran sendmail (I think, it was quite a long time ago) from my linux server and use it as SMTP server. My emails immediately got marked as spam by the servers I sent to, and it took quite a long time before this label was removed. So be careful if you plan to send emails to some mail service that is not in your own control.

    ReplyDelete
    Replies
    1. The few local internet connections I've checked have all had outgoing port 25 blocked entirely. I'm writing this as a receive-only system to accept emails as a message-passing system to control my network.

      Delete
  2. You coded this from scratch in C?? So I guess you've got a lot of time on your hands. Use Perl, visit CPAN and find appropriate modules for what you're trying to do, glue them together, small tweaks - voila, done.

    Perl is your friend...

    Regards, David

    ReplyDelete
    Replies
    1. There was a few motivations; I've never a C network daemon before, so this was good practice. I'm also considering running this in a busybox environment, so even expecting the perl interpreter not to be brain dead isn't necessarily reasonable.

      There's eventually going to be much more to this, but this was a good point at which to take pause to post a good example of a bare-bones socket daemon.

      Delete
  3. hello, my name is Hardi
    I've tried your source code, but it did not want the road
    and I found this,
    Failed to bind to ipv6 socket
    if you can help me?

    ReplyDelete