These files are a subset of the python-2.7.2.tgz distribution from python.org. Changed files from PyMod-2.7.2 have been copied into the corresponding directories of this tree, replacing the original files in the distribution. Signed-off-by: daryl.mcdaniel@intel.com git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@13197 6f19259b-4bc3-4df7-8a09-765794883524
		
			
				
	
	
		
			556 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			556 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #! /usr/bin/env python
 | |
| """An RFC 2821 smtp proxy.
 | |
| 
 | |
| Usage: %(program)s [options] [localhost:localport [remotehost:remoteport]]
 | |
| 
 | |
| Options:
 | |
| 
 | |
|     --nosetuid
 | |
|     -n
 | |
|         This program generally tries to setuid `nobody', unless this flag is
 | |
|         set.  The setuid call will fail if this program is not run as root (in
 | |
|         which case, use this flag).
 | |
| 
 | |
|     --version
 | |
|     -V
 | |
|         Print the version number and exit.
 | |
| 
 | |
|     --class classname
 | |
|     -c classname
 | |
|         Use `classname' as the concrete SMTP proxy class.  Uses `PureProxy' by
 | |
|         default.
 | |
| 
 | |
|     --debug
 | |
|     -d
 | |
|         Turn on debugging prints.
 | |
| 
 | |
|     --help
 | |
|     -h
 | |
|         Print this message and exit.
 | |
| 
 | |
| Version: %(__version__)s
 | |
| 
 | |
| If localhost is not given then `localhost' is used, and if localport is not
 | |
| given then 8025 is used.  If remotehost is not given then `localhost' is used,
 | |
| and if remoteport is not given, then 25 is used.
 | |
| """
 | |
| 
 | |
| # Overview:
 | |
| #
 | |
| # This file implements the minimal SMTP protocol as defined in RFC 821.  It
 | |
| # has a hierarchy of classes which implement the backend functionality for the
 | |
| # smtpd.  A number of classes are provided:
 | |
| #
 | |
| #   SMTPServer - the base class for the backend.  Raises NotImplementedError
 | |
| #   if you try to use it.
 | |
| #
 | |
| #   DebuggingServer - simply prints each message it receives on stdout.
 | |
| #
 | |
| #   PureProxy - Proxies all messages to a real smtpd which does final
 | |
| #   delivery.  One known problem with this class is that it doesn't handle
 | |
| #   SMTP errors from the backend server at all.  This should be fixed
 | |
| #   (contributions are welcome!).
 | |
| #
 | |
| #   MailmanProxy - An experimental hack to work with GNU Mailman
 | |
| #   <www.list.org>.  Using this server as your real incoming smtpd, your
 | |
| #   mailhost will automatically recognize and accept mail destined to Mailman
 | |
| #   lists when those lists are created.  Every message not destined for a list
 | |
| #   gets forwarded to a real backend smtpd, as with PureProxy.  Again, errors
 | |
| #   are not handled correctly yet.
 | |
| #
 | |
| # Please note that this script requires Python 2.0
 | |
| #
 | |
| # Author: Barry Warsaw <barry@python.org>
 | |
| #
 | |
| # TODO:
 | |
| #
 | |
| # - support mailbox delivery
 | |
| # - alias files
 | |
| # - ESMTP
 | |
| # - handle error codes from the backend smtpd
 | |
| 
 | |
| import sys
 | |
| import os
 | |
| import errno
 | |
| import getopt
 | |
| import time
 | |
| import socket
 | |
| import asyncore
 | |
| import asynchat
 | |
| 
 | |
| __all__ = ["SMTPServer","DebuggingServer","PureProxy","MailmanProxy"]
 | |
| 
 | |
| program = sys.argv[0]
 | |
| __version__ = 'Python SMTP proxy version 0.2'
 | |
| 
 | |
| 
 | |
| class Devnull:
 | |
|     def write(self, msg): pass
 | |
|     def flush(self): pass
 | |
| 
 | |
| 
 | |
| DEBUGSTREAM = Devnull()
 | |
| NEWLINE = '\n'
 | |
| EMPTYSTRING = ''
 | |
| COMMASPACE = ', '
 | |
| 
 | |
| 
 | |
| def usage(code, msg=''):
 | |
|     print >> sys.stderr, __doc__ % globals()
 | |
|     if msg:
 | |
|         print >> sys.stderr, msg
 | |
|     sys.exit(code)
 | |
| 
 | |
| 
 | |
| class SMTPChannel(asynchat.async_chat):
 | |
|     COMMAND = 0
 | |
|     DATA = 1
 | |
| 
 | |
|     def __init__(self, server, conn, addr):
 | |
|         asynchat.async_chat.__init__(self, conn)
 | |
|         self.__server = server
 | |
|         self.__conn = conn
 | |
|         self.__addr = addr
 | |
|         self.__line = []
 | |
|         self.__state = self.COMMAND
 | |
|         self.__greeting = 0
 | |
|         self.__mailfrom = None
 | |
|         self.__rcpttos = []
 | |
|         self.__data = ''
 | |
|         self.__fqdn = socket.getfqdn()
 | |
|         try:
 | |
|             self.__peer = conn.getpeername()
 | |
|         except socket.error, err:
 | |
|             # a race condition  may occur if the other end is closing
 | |
|             # before we can get the peername
 | |
|             self.close()
 | |
|             if err[0] != errno.ENOTCONN:
 | |
|                 raise
 | |
|             return
 | |
|         print >> DEBUGSTREAM, 'Peer:', repr(self.__peer)
 | |
|         self.push('220 %s %s' % (self.__fqdn, __version__))
 | |
|         self.set_terminator('\r\n')
 | |
| 
 | |
|     # Overrides base class for convenience
 | |
|     def push(self, msg):
 | |
|         asynchat.async_chat.push(self, msg + '\r\n')
 | |
| 
 | |
|     # Implementation of base class abstract method
 | |
|     def collect_incoming_data(self, data):
 | |
|         self.__line.append(data)
 | |
| 
 | |
|     # Implementation of base class abstract method
 | |
|     def found_terminator(self):
 | |
|         line = EMPTYSTRING.join(self.__line)
 | |
|         print >> DEBUGSTREAM, 'Data:', repr(line)
 | |
|         self.__line = []
 | |
|         if self.__state == self.COMMAND:
 | |
|             if not line:
 | |
|                 self.push('500 Error: bad syntax')
 | |
|                 return
 | |
|             method = None
 | |
|             i = line.find(' ')
 | |
|             if i < 0:
 | |
|                 command = line.upper()
 | |
|                 arg = None
 | |
|             else:
 | |
|                 command = line[:i].upper()
 | |
|                 arg = line[i+1:].strip()
 | |
|             method = getattr(self, 'smtp_' + command, None)
 | |
|             if not method:
 | |
|                 self.push('502 Error: command "%s" not implemented' % command)
 | |
|                 return
 | |
|             method(arg)
 | |
|             return
 | |
|         else:
 | |
|             if self.__state != self.DATA:
 | |
|                 self.push('451 Internal confusion')
 | |
|                 return
 | |
|             # Remove extraneous carriage returns and de-transparency according
 | |
|             # to RFC 821, Section 4.5.2.
 | |
|             data = []
 | |
|             for text in line.split('\r\n'):
 | |
|                 if text and text[0] == '.':
 | |
|                     data.append(text[1:])
 | |
|                 else:
 | |
|                     data.append(text)
 | |
|             self.__data = NEWLINE.join(data)
 | |
|             status = self.__server.process_message(self.__peer,
 | |
|                                                    self.__mailfrom,
 | |
|                                                    self.__rcpttos,
 | |
|                                                    self.__data)
 | |
|             self.__rcpttos = []
 | |
|             self.__mailfrom = None
 | |
|             self.__state = self.COMMAND
 | |
|             self.set_terminator('\r\n')
 | |
|             if not status:
 | |
|                 self.push('250 Ok')
 | |
|             else:
 | |
|                 self.push(status)
 | |
| 
 | |
|     # SMTP and ESMTP commands
 | |
|     def smtp_HELO(self, arg):
 | |
|         if not arg:
 | |
|             self.push('501 Syntax: HELO hostname')
 | |
|             return
 | |
|         if self.__greeting:
 | |
|             self.push('503 Duplicate HELO/EHLO')
 | |
|         else:
 | |
|             self.__greeting = arg
 | |
|             self.push('250 %s' % self.__fqdn)
 | |
| 
 | |
|     def smtp_NOOP(self, arg):
 | |
|         if arg:
 | |
|             self.push('501 Syntax: NOOP')
 | |
|         else:
 | |
|             self.push('250 Ok')
 | |
| 
 | |
|     def smtp_QUIT(self, arg):
 | |
|         # args is ignored
 | |
|         self.push('221 Bye')
 | |
|         self.close_when_done()
 | |
| 
 | |
|     # factored
 | |
|     def __getaddr(self, keyword, arg):
 | |
|         address = None
 | |
|         keylen = len(keyword)
 | |
|         if arg[:keylen].upper() == keyword:
 | |
|             address = arg[keylen:].strip()
 | |
|             if not address:
 | |
|                 pass
 | |
|             elif address[0] == '<' and address[-1] == '>' and address != '<>':
 | |
|                 # Addresses can be in the form <person@dom.com> but watch out
 | |
|                 # for null address, e.g. <>
 | |
|                 address = address[1:-1]
 | |
|         return address
 | |
| 
 | |
|     def smtp_MAIL(self, arg):
 | |
|         print >> DEBUGSTREAM, '===> MAIL', arg
 | |
|         address = self.__getaddr('FROM:', arg) if arg else None
 | |
|         if not address:
 | |
|             self.push('501 Syntax: MAIL FROM:<address>')
 | |
|             return
 | |
|         if self.__mailfrom:
 | |
|             self.push('503 Error: nested MAIL command')
 | |
|             return
 | |
|         self.__mailfrom = address
 | |
|         print >> DEBUGSTREAM, 'sender:', self.__mailfrom
 | |
|         self.push('250 Ok')
 | |
| 
 | |
|     def smtp_RCPT(self, arg):
 | |
|         print >> DEBUGSTREAM, '===> RCPT', arg
 | |
|         if not self.__mailfrom:
 | |
|             self.push('503 Error: need MAIL command')
 | |
|             return
 | |
|         address = self.__getaddr('TO:', arg) if arg else None
 | |
|         if not address:
 | |
|             self.push('501 Syntax: RCPT TO: <address>')
 | |
|             return
 | |
|         self.__rcpttos.append(address)
 | |
|         print >> DEBUGSTREAM, 'recips:', self.__rcpttos
 | |
|         self.push('250 Ok')
 | |
| 
 | |
|     def smtp_RSET(self, arg):
 | |
|         if arg:
 | |
|             self.push('501 Syntax: RSET')
 | |
|             return
 | |
|         # Resets the sender, recipients, and data, but not the greeting
 | |
|         self.__mailfrom = None
 | |
|         self.__rcpttos = []
 | |
|         self.__data = ''
 | |
|         self.__state = self.COMMAND
 | |
|         self.push('250 Ok')
 | |
| 
 | |
|     def smtp_DATA(self, arg):
 | |
|         if not self.__rcpttos:
 | |
|             self.push('503 Error: need RCPT command')
 | |
|             return
 | |
|         if arg:
 | |
|             self.push('501 Syntax: DATA')
 | |
|             return
 | |
|         self.__state = self.DATA
 | |
|         self.set_terminator('\r\n.\r\n')
 | |
|         self.push('354 End data with <CR><LF>.<CR><LF>')
 | |
| 
 | |
| 
 | |
| class SMTPServer(asyncore.dispatcher):
 | |
|     def __init__(self, localaddr, remoteaddr):
 | |
|         self._localaddr = localaddr
 | |
|         self._remoteaddr = remoteaddr
 | |
|         asyncore.dispatcher.__init__(self)
 | |
|         try:
 | |
|             self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
 | |
|             # try to re-use a server port if possible
 | |
|             self.set_reuse_addr()
 | |
|             self.bind(localaddr)
 | |
|             self.listen(5)
 | |
|         except:
 | |
|             # cleanup asyncore.socket_map before raising
 | |
|             self.close()
 | |
|             raise
 | |
|         else:
 | |
|             print >> DEBUGSTREAM, \
 | |
|                   '%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
 | |
|                 self.__class__.__name__, time.ctime(time.time()),
 | |
|                 localaddr, remoteaddr)
 | |
| 
 | |
|     def handle_accept(self):
 | |
|         pair = self.accept()
 | |
|         if pair is not None:
 | |
|             conn, addr = pair
 | |
|             print >> DEBUGSTREAM, 'Incoming connection from %s' % repr(addr)
 | |
|             channel = SMTPChannel(self, conn, addr)
 | |
| 
 | |
|     # API for "doing something useful with the message"
 | |
|     def process_message(self, peer, mailfrom, rcpttos, data):
 | |
|         """Override this abstract method to handle messages from the client.
 | |
| 
 | |
|         peer is a tuple containing (ipaddr, port) of the client that made the
 | |
|         socket connection to our smtp port.
 | |
| 
 | |
|         mailfrom is the raw address the client claims the message is coming
 | |
|         from.
 | |
| 
 | |
|         rcpttos is a list of raw addresses the client wishes to deliver the
 | |
|         message to.
 | |
| 
 | |
|         data is a string containing the entire full text of the message,
 | |
|         headers (if supplied) and all.  It has been `de-transparencied'
 | |
|         according to RFC 821, Section 4.5.2.  In other words, a line
 | |
|         containing a `.' followed by other text has had the leading dot
 | |
|         removed.
 | |
| 
 | |
|         This function should return None, for a normal `250 Ok' response;
 | |
|         otherwise it returns the desired response string in RFC 821 format.
 | |
| 
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
| 
 | |
| class DebuggingServer(SMTPServer):
 | |
|     # Do something with the gathered message
 | |
|     def process_message(self, peer, mailfrom, rcpttos, data):
 | |
|         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 ------------'
 | |
| 
 | |
| 
 | |
| class PureProxy(SMTPServer):
 | |
|     def process_message(self, peer, mailfrom, rcpttos, data):
 | |
|         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 = NEWLINE.join(lines)
 | |
|         refused = self._deliver(mailfrom, rcpttos, data)
 | |
|         # TBD: what to do with refused addresses?
 | |
|         print >> DEBUGSTREAM, 'we got some refusals:', refused
 | |
| 
 | |
|     def _deliver(self, mailfrom, rcpttos, data):
 | |
|         import smtplib
 | |
|         refused = {}
 | |
|         try:
 | |
|             s = smtplib.SMTP()
 | |
|             s.connect(self._remoteaddr[0], self._remoteaddr[1])
 | |
|             try:
 | |
|                 refused = s.sendmail(mailfrom, rcpttos, data)
 | |
|             finally:
 | |
|                 s.quit()
 | |
|         except smtplib.SMTPRecipientsRefused, e:
 | |
|             print >> DEBUGSTREAM, 'got SMTPRecipientsRefused'
 | |
|             refused = e.recipients
 | |
|         except (socket.error, smtplib.SMTPException), e:
 | |
|             print >> DEBUGSTREAM, '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
 | |
| 
 | |
| 
 | |
| class MailmanProxy(PureProxy):
 | |
|     def process_message(self, peer, mailfrom, rcpttos, data):
 | |
|         from cStringIO import StringIO
 | |
|         from Mailman import Utils
 | |
|         from Mailman import Message
 | |
|         from Mailman import MailList
 | |
|         # If the message is to a Mailman mailing list, then we'll invoke the
 | |
|         # Mailman script directly, without going through the real smtpd.
 | |
|         # Otherwise we'll forward it to the local proxy for disposition.
 | |
|         listnames = []
 | |
|         for rcpt in rcpttos:
 | |
|             local = rcpt.lower().split('@')[0]
 | |
|             # We allow the following variations on the theme
 | |
|             #   listname
 | |
|             #   listname-admin
 | |
|             #   listname-owner
 | |
|             #   listname-request
 | |
|             #   listname-join
 | |
|             #   listname-leave
 | |
|             parts = local.split('-')
 | |
|             if len(parts) > 2:
 | |
|                 continue
 | |
|             listname = parts[0]
 | |
|             if len(parts) == 2:
 | |
|                 command = parts[1]
 | |
|             else:
 | |
|                 command = ''
 | |
|             if not Utils.list_exists(listname) or command not in (
 | |
|                     '', 'admin', 'owner', 'request', 'join', 'leave'):
 | |
|                 continue
 | |
|             listnames.append((rcpt, listname, command))
 | |
|         # Remove all list recipients from rcpttos and forward what we're not
 | |
|         # going to take care of ourselves.  Linear removal should be fine
 | |
|         # since we don't expect a large number of recipients.
 | |
|         for rcpt, listname, command in listnames:
 | |
|             rcpttos.remove(rcpt)
 | |
|         # If there's any non-list destined recipients left,
 | |
|         print >> DEBUGSTREAM, 'forwarding recips:', ' '.join(rcpttos)
 | |
|         if rcpttos:
 | |
|             refused = self._deliver(mailfrom, rcpttos, data)
 | |
|             # TBD: what to do with refused addresses?
 | |
|             print >> DEBUGSTREAM, 'we got refusals:', refused
 | |
|         # Now deliver directly to the list commands
 | |
|         mlists = {}
 | |
|         s = StringIO(data)
 | |
|         msg = Message.Message(s)
 | |
|         # These headers are required for the proper execution of Mailman.  All
 | |
|         # MTAs in existence seem to add these if the original message doesn't
 | |
|         # have them.
 | |
|         if not msg.getheader('from'):
 | |
|             msg['From'] = mailfrom
 | |
|         if not msg.getheader('date'):
 | |
|             msg['Date'] = time.ctime(time.time())
 | |
|         for rcpt, listname, command in listnames:
 | |
|             print >> DEBUGSTREAM, 'sending message to', rcpt
 | |
|             mlist = mlists.get(listname)
 | |
|             if not mlist:
 | |
|                 mlist = MailList.MailList(listname, lock=0)
 | |
|                 mlists[listname] = mlist
 | |
|             # dispatch on the type of command
 | |
|             if command == '':
 | |
|                 # post
 | |
|                 msg.Enqueue(mlist, tolist=1)
 | |
|             elif command == 'admin':
 | |
|                 msg.Enqueue(mlist, toadmin=1)
 | |
|             elif command == 'owner':
 | |
|                 msg.Enqueue(mlist, toowner=1)
 | |
|             elif command == 'request':
 | |
|                 msg.Enqueue(mlist, torequest=1)
 | |
|             elif command in ('join', 'leave'):
 | |
|                 # TBD: this is a hack!
 | |
|                 if command == 'join':
 | |
|                     msg['Subject'] = 'subscribe'
 | |
|                 else:
 | |
|                     msg['Subject'] = 'unsubscribe'
 | |
|                 msg.Enqueue(mlist, torequest=1)
 | |
| 
 | |
| 
 | |
| class Options:
 | |
|     setuid = 1
 | |
|     classname = 'PureProxy'
 | |
| 
 | |
| 
 | |
| def parseargs():
 | |
|     global DEBUGSTREAM
 | |
|     try:
 | |
|         opts, args = getopt.getopt(
 | |
|             sys.argv[1:], 'nVhc:d',
 | |
|             ['class=', 'nosetuid', 'version', 'help', 'debug'])
 | |
|     except getopt.error, e:
 | |
|         usage(1, e)
 | |
| 
 | |
|     options = Options()
 | |
|     for opt, arg in opts:
 | |
|         if opt in ('-h', '--help'):
 | |
|             usage(0)
 | |
|         elif opt in ('-V', '--version'):
 | |
|             print >> sys.stderr, __version__
 | |
|             sys.exit(0)
 | |
|         elif opt in ('-n', '--nosetuid'):
 | |
|             options.setuid = 0
 | |
|         elif opt in ('-c', '--class'):
 | |
|             options.classname = arg
 | |
|         elif opt in ('-d', '--debug'):
 | |
|             DEBUGSTREAM = sys.stderr
 | |
| 
 | |
|     # parse the rest of the arguments
 | |
|     if len(args) < 1:
 | |
|         localspec = 'localhost:8025'
 | |
|         remotespec = 'localhost:25'
 | |
|     elif len(args) < 2:
 | |
|         localspec = args[0]
 | |
|         remotespec = 'localhost:25'
 | |
|     elif len(args) < 3:
 | |
|         localspec = args[0]
 | |
|         remotespec = args[1]
 | |
|     else:
 | |
|         usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
 | |
| 
 | |
|     # split into host/port pairs
 | |
|     i = localspec.find(':')
 | |
|     if i < 0:
 | |
|         usage(1, 'Bad local spec: %s' % localspec)
 | |
|     options.localhost = localspec[:i]
 | |
|     try:
 | |
|         options.localport = int(localspec[i+1:])
 | |
|     except ValueError:
 | |
|         usage(1, 'Bad local port: %s' % localspec)
 | |
|     i = remotespec.find(':')
 | |
|     if i < 0:
 | |
|         usage(1, 'Bad remote spec: %s' % remotespec)
 | |
|     options.remotehost = remotespec[:i]
 | |
|     try:
 | |
|         options.remoteport = int(remotespec[i+1:])
 | |
|     except ValueError:
 | |
|         usage(1, 'Bad remote port: %s' % remotespec)
 | |
|     return options
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     options = parseargs()
 | |
|     # Become nobody
 | |
|     if options.setuid:
 | |
|         try:
 | |
|             import pwd
 | |
|         except ImportError:
 | |
|             print >> sys.stderr, \
 | |
|                   'Cannot import module "pwd"; try running with -n option.'
 | |
|             sys.exit(1)
 | |
|         nobody = pwd.getpwnam('nobody')[2]
 | |
|         try:
 | |
|             os.setuid(nobody)
 | |
|         except OSError, e:
 | |
|             if e.errno != errno.EPERM: raise
 | |
|             print >> sys.stderr, \
 | |
|                   'Cannot setuid "nobody"; try running with -n option.'
 | |
|             sys.exit(1)
 | |
|     classname = options.classname
 | |
|     if "." in classname:
 | |
|         lastdot = classname.rfind(".")
 | |
|         mod = __import__(classname[:lastdot], globals(), locals(), [""])
 | |
|         classname = classname[lastdot+1:]
 | |
|     else:
 | |
|         import __main__ as mod
 | |
|     class_ = getattr(mod, classname)
 | |
|     proxy = class_((options.localhost, options.localport),
 | |
|                    (options.remotehost, options.remoteport))
 | |
|     try:
 | |
|         asyncore.loop()
 | |
|     except KeyboardInterrupt:
 | |
|         pass
 |