Mini SMTP Server - Noah.org

Mini SMTP Server

From Noah.org

Jump to: navigation, search

This demonstrates a mini SMTP server.

Click here to download: minismtpd.py

#!/usr/bin/env python
 
"""
SYNOPSIS
 
    minismtpd.py [-h] [-v,--verbose] [--version] hostname port Maildir_path
 
DESCRIPTION
 
    This is a mini mail server (SMTP daemon). All mail received will be written
    to a Maildir style mail spool. This simply receives all mail and saves it
    to a common Maildir spool no matter which user the mail is addressed to.
    This may be ideal for a single user system or this may not be what you
    want.
 
EXAMPLES
 
    minismtpd.py localhost 25 /tmp/Maildir
 
AUTHOR
 
    Noah Spurrier <noah@noah.org>
 
LICENSE
 
    This script is in the public domain, free from copyrights or restrictions.
 
VERSION
 
    $Id: minismtpd.py 248 2008-04-30 01:04:08Z noah $
"""
 
import sys, os, threading, traceback, optparse
import time
import asyncore, smtpd, mailbox, email
from email.Utils import COMMASPACE, formatdate
 
class SMTPServerMaildir (smtpd.SMTPServer, threading.Thread):
 
    """This may seem a little bit confusing if you are not familiar with
    asyncore because it will seem as if nothing in this application will ever
    call process_message. But SMTPServer inherits from asyncore.dispatcher, and
    so will insert itself into asyncore's event loop on instantiation."""
 
    def __init__ (self, host = "localhost", port = 25, Maildir_path=None, name=None):
 
        localaddr = (host, port)
        remoteaddr = None
        smtpd.SMTPServer.__init__ (self, localaddr, remoteaddr)
        if name is None:
            name = SMTPServerMaildir.__class__
        threading.Thread.__init__ (self, name = name)
        self.open_Maildir (Maildir_path) # this sets self.inbox
        self.finished = threading.Event ()
 
    def open_Maildir (self, Maildir_path):
 
        # TODO this is a little scruffy. Should be cleaner I think.
        if os.path.isdir (Maildir_path):
            self.inbox = mailbox.Maildir (Maildir_path, None)
        else:
            if not os.path.isfile (Maildir_path):
                self.inbox = mailbox.Maildir (Maildir_path, None, create=True)
            else:
                raise Exception ("Maildir exists and is not readable.")
#            os.makedirs(dts_path)
#            os.makedirs(os.path.join(dts_path, 'cur'))
#            os.makedirs(os.path.join(dts_path, 'new'))
#            os.makedirs(os.path.join(dts_path, 'tmp'))
 
    def process_message (self, peer, mailfrom, rcpttos, data):
 
        m = email.message_from_string (data)
        m ['From'] = mailfrom
        m ['To'] = COMMASPACE.join(rcpttos)
        if 'Date' not in m:
            m ['Date'] = formatdate(localtime=True)
        print 'To:', m['To']
        if self.inbox is not None:
            self.inbox.add (m)
 
    def stop (self, timeout=None):
 
        self.finished.set ()
        threading.Thread.join (self, timeout)
        self.SMTPServer.close ()
 
    def run (self):
 
        while not self.finished.isSet ():
            asyncore.loop (timeout=0.25, count=1)
 
def main ():
 
    global options, args
 
    host = args [0]
    port = int(args [1])
    Maildir_path = args [2]
    SMTPd = SMTPServerMaildir (host=host, port=port, Maildir_path = Maildir_path)
    SMTPd.start ()
    try:
        while True:
            time.sleep (0.25)
    except KeyboardInterrupt, e: # Ctrl-C
        SMTPd.stop ()
        raise e
    except SystemExit, e: # sys.exit ()
        SMTPd.stop ()
        raise e
 
if __name__ == '__main__':
    try:
        start_time = time.time ()
        parser = optparse.OptionParser (formatter=optparse.TitledHelpFormatter (), usage=globals ()['__doc__'], version='$Id: minismtpd.py 248 2008-04-30 01:04:08Z noah $')
        parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output')
        (options, args) = parser.parse_args ()
        if len (args) < 2 or len (args) > 3:
            parser.error ('incorrect number of arguments. Need Maildir_path and hostname.')
        if options.verbose: print time.asctime ()
        main ()
        if options.verbose: print time.asctime ()
        if options.verbose: print 'TOTAL TIME IN MINUTES:',
        if options.verbose: print (time.time () - start_time) / 60.0
        sys.exit (0)
    except KeyboardInterrupt, e: # Ctrl-C
        raise e
    except SystemExit, e: # sys.exit ()
        raise e
    except Exception, e:
        print 'ERROR, UNEXPECTED EXCEPTION'
        print str (e)
        traceback.print_exc ()
        os._exit (1)
 
# vi:ts=4:sw=4:expandtab:ft=python:
-->