Permalink
Browse files

- inspired by smptd.PureProxy, have added a relay that can use SSL

  • Loading branch information...
1 parent 0d7d323 commit 6c175c31a18d284a2ac091bed49db595869cc958 @cltrudeau cltrudeau committed Mar 1, 2012
Showing with 156 additions and 0 deletions.
  1. +53 −0 examples/mail_relay.py
  2. +92 −0 secure_smtpd/proxy.py
  3. +11 −0 secure_smtpd/store_credentials.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+import argparse
+from secure_smtpd import ProxyServer
+import asyncore
+
+def run(cmdargs):
+ args = [
+ (cmdargs.localhost, cmdargs.localport),
+ (cmdargs.remotehost, cmdargs.remoteport)
+ ]
+ kwargs = {}
+
+ if cmdargs.sslboth:
+ kwargs['ssl'] = True
+ elif cmdargs.sslout:
+ kwargs['sslOutOnly'] = True
+
+ if not cmdargs.quiet:
+ kwargs['printDebug'] = True
+
+ ProxyServer(*args, **kwargs)
+ asyncore.loop()
+
+parser = argparse.ArgumentParser(description='mail relay tool')
+parser.add_argument('--localhost', default='127.0.0.1',
+ help='Local address to attach to for receiving mail. Defaults to ' +\
+ '127.0.0.1')
+parser.add_argument('--localport', default=1025, type=int,
+ help='Local port to attach to for receiving mail. Defaults to 1025')
+parser.add_argument('--remotehost', required=True,
+ help='Address of the remote server for connection.')
+parser.add_argument('--remoteport', default=25, type=int,
+ help='Port of the remote server for connection. Defaults to 25')
+parser.add_argument('--quiet', action='store_true',
+ help='Use this to turn off the message printing')
+group = parser.add_mutually_exclusive_group()
+group.add_argument('--sslboth', action='store_true',
+ help='Use this parameter if both the inbound and outbound connections ' +\
+ 'should use SSL')
+group.add_argument('--sslout', action='store_true',
+ help='Use this parameter if inbound connection is plain but the ' +\
+ 'outbound connection uses SSL')
+
+args = parser.parse_args()
+
+print 'Starting ProxyServer'
+print 'local: %s:%s' % (args.localhost, args.localport)
+print 'remote: %s:%s' % (args.remotehost, args.remoteport)
+print 'sslboth: ', args.sslboth
+print 'sslout: ', args.sslout
+print
+run(args)
View
@@ -0,0 +1,92 @@
+import secure_smtpd
+
+from smtp_server import SMTPServer
+from store_credentials import StoreCredentials
+
+class ProxyServer(SMTPServer):
+ """Implements an open relay. Inherits from secure_smtpd, so can handle
+ SSL incoming. Modifies attributes slightly:
+
+ * if "ssl" is true accepts SSL connections inbound and connects via SSL
+ outbound
+ * adds "sslOutOnly", which can be set to True when "ssl" is False so that
+ inbound connections are in plain text but outbound are in SSL
+ * adds "printDebug", which if True prints all inbound messages to stdout
+ * ignores any credential validators, passing any credentials upstream
+ """
+ def __init__(self, *args, **kwargs):
+ self.sslOutOnly = False
+ if kwargs.has_key('sslOutOnly'):
+ self.sslOutOnly = kwargs.pop('sslOutOnly')
+
+ self.printDebug = False
+ if kwargs.has_key('printDebug'):
+ self.printDebug = kwargs.pop('printDebug')
+
+ kwargs['credential_validator'] = StoreCredentials()
+ SMTPServer.__init__(self, *args, **kwargs)
+
+ def process_message(self, peer, mailfrom, rcpttos, data):
+ if self.printDebug:
+ # ------------------------
+ # stolen directly from stmpd.DebuggingServer
+ inheaders = 1
+ lines = data.split('\n')
+ print '---------- MESSAGE FOLLOWS ----------'
+ for line in lines:
+ # headers first
+ if inheaders and not line:
+ print 'X-Peer:', peer[0]
+ inheaders = 0
+ print line
+ print '------------ END MESSAGE ------------'
+
+ # ------------------------
+ # following code is direct from smtpd.PureProxy
+ lines = data.split('\n')
+ # Look for the last header
+ i = 0
+ for line in lines:
+ if not line:
+ break
+ i += 1
+ lines.insert(i, 'X-Peer: %s' % peer[0])
+ data = '\n'.join(lines)
+ self._deliver(mailfrom, rcpttos, data)
+
+ def _deliver(self, mailfrom, rcpttos, data):
+ # ------------------------
+ # following code is adapted from smtpd.PureProxy with modifications to
+ # handle upstream SSL
+
+ import smtplib
+ refused = {}
+ try:
+ if self.ssl or self.sslOutOnly:
+ s = smtplib.SMTP_SSL()
+ else:
+ s = smtplib.SMTP()
+
+ s.connect(self._remoteaddr[0], self._remoteaddr[1])
+ if self.credential_validator.stored:
+ # we had credentials passed in, use them
+ s.login(self.credential_validator.username,
+ self.credential_validator.password)
+ try:
+ refused = s.sendmail(mailfrom, rcpttos, data)
+ print 'refused: ', refused
+ finally:
+ s.quit()
+ except smtplib.SMTPRecipientsRefused, e:
+ print '********* ERROR: got SMTPRecipientsRefused'
+ refused = e.recipients
+ except (socket.error, smtplib.SMTPException), e:
+ print '********* ERROR: got', e.__class__
+ # All recipients were refused. If the exception had an associated
+ # error code, use it. Otherwise,fake it with a non-triggering
+ # exception code.
+ errcode = getattr(e, 'smtp_code', -1)
+ errmsg = getattr(e, 'smtp_error', 'ignore')
+ for r in rcpttos:
+ refused[r] = (errcode, errmsg)
+ return refused
@@ -0,0 +1,11 @@
+class StoreCredentials(object):
+ def __init__(self):
+ self.stored = False
+ self.username = None
+ self.password = None
+
+ def validate(self, username, password):
+ self.stored = True
+ self.username = username
+ self.password = password
+ return True

0 comments on commit 6c175c3

Please sign in to comment.