changeset 4:87317bd93890 default tip

The server now has a separate thread that forwards queued mail to an external server.
author Atul Varma <varmaa@toolness.com>
date Tue, 18 Mar 2008 22:25:31 -0500
parents 769f41699038
children
files .hgignore smtpserver.py
diffstat 2 files changed, 105 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue Mar 18 21:00:41 2008 -0500
+++ b/.hgignore	Tue Mar 18 22:25:31 2008 -0500
@@ -1,5 +1,6 @@
 syntax: glob
 
 *.pyc
+*.log
 *~
 smtpconfig.py
--- a/smtpserver.py	Tue Mar 18 21:00:41 2008 -0500
+++ b/smtpserver.py	Tue Mar 18 22:25:31 2008 -0500
@@ -5,11 +5,59 @@
 import asyncore
 import grp
 import pwd
+import logging
+import traceback
+import threading
+import Queue
+
+class MockSMTP( object ):
+    def __init__( self, server, port ):
+        pass
+
+    def login( self, username, password ):
+        pass
+
+    def sendmail( self, mailfrom, rcpttos, data ):
+        pass
+
+    def quit( self ):
+        pass
+
+def mailThread( queue, config, smtpClass ):
+    done = False
+
+    try:
+        while not done:
+            item = queue.get()
+            if item["cmd"] == "quit":
+                logging.info( "quit signal received, exiting." )
+                done = True
+            elif item["cmd"] == "sendmsg":
+                # TODO: If the sending of the message fails, we should
+                # try to re-queue it and re-send it later.
+                logging.info(
+                    "sending message from %s to %s" % ( item["mailfrom"],
+                                                        item["rcpttos"] )
+                    )
+                server = smtpClass( config.server, config.port )
+                server.login( config.username, config.password )
+                server.sendmail( item["mailfrom"],
+                                 item["rcpttos"],
+                                 item["data"] )
+                server.quit()
+                logging.info( "done." )
+            else:
+                raise AssertionError( "Bad cmd: %s" % item["cmd"] )
+    except:
+        logging.error( "exception in mailThread()." )
+        logging.error( traceback.format_exc() )
 
 class MyServer( smtpd.SMTPServer ):
     import smtpconfig as config
 
-    def __init__( self ):
+    def __init__( self, smtpClass ):
+        self.smtpClass = smtpClass
+        self.queue = Queue.Queue()
         smtpd.SMTPServer.__init__( self, self.config.my_addr, None )
 
     def bind( self, addr ):
@@ -18,27 +66,69 @@
         uid = pwd.getpwnam( self.config.uid )[2]
         os.setgid( gid )
         os.setuid( uid )
-        print "Bound to %s and changed user to %s." % ( addr,
-                                                        self.config.uid )
+        logging.info(
+            "Bound to %s and changed user to %s." % ( addr,
+                                                      self.config.uid )
+            )
+
+        self.mailThread = threading.Thread(
+            target = mailThread,
+            args = (self.queue, self.config, self.smtpClass)
+            )
+        self.mailThread.start()
+
         return retval
 
+    def finalize( self ):
+        self.queue.put( {"cmd" : "quit"} )
+
     def process_message( self, peer, mailfrom, rcpttos, data ):
         if peer[0] != "127.0.0.1":
-            print "not from localhost: %s" % peer
+            logging.warn( "not from localhost: %s" % peer )
             return
-        print "sending message from %s to %s" % (mailfrom, rcpttos)
-        server = smtplib.SMTP( self.config.server, self.config.port )
-        server.set_debuglevel( 1 )
-        server.login( self.config.username, self.config.password )
-        server.sendmail( mailfrom, rcpttos, data )
-        server.quit()
-        print "done."
+
+        if not self.mailThread.isAlive():
+            logging.info( "mailThread is no longer alive! exiting." )
+            raise asyncore.ExitNow()
+
+        logging.info(
+            "queuing message from %s to %s on %s (length %d)." %
+            (mailfrom, rcpttos, peer, len(data))
+            )
+
+        self.queue.put( { "cmd" : "sendmsg",
+                          "mailfrom" : mailfrom,
+                          "rcpttos" : rcpttos,
+                          "data" : data } )
 
 if __name__ == "__main__":
     if os.getuid() != 0:
         print "This program must be run as root."
         sys.exit( -1 )
-    server = MyServer()
-    print "Listening for incoming connections on port %s." % \
+
+    logging.basicConfig(
+        filename = "smtpserver.log",
+        format = "%(asctime)-15s %(levelname)s: %(message)s",
+        level = logging.INFO
+        )
+
+    if len( sys.argv ) == 2 and sys.argv[1] == "test":
+        msg = "Test mode enabled."
+        logging.info( msg )
+        print msg
+
+        smtpClass = MockSMTP
+    else:
+        smtpClass = smtplib.SMTP
+
+    server = MyServer( smtpClass )
+
+    msg = "Listening for incoming connections on port %s." % \
         server.config.my_addr[1]
-    asyncore.loop()
+    logging.info( msg )
+    print msg
+
+    try:
+        asyncore.loop()
+    finally:
+        server.finalize()