mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 11:49:12 +00:00 
			
		
		
		
	svn+ssh://pythondev@svn.python.org/python/branches/p3yk
................
  r56760 | neal.norwitz | 2007-08-05 18:55:39 -0700 (Sun, 05 Aug 2007) | 178 lines
  Merged revisions 56477-56759 via svnmerge from
  svn+ssh://pythondev@svn.python.org/python/trunk
  ........
    r56485 | facundo.batista | 2007-07-21 17:13:00 -0700 (Sat, 21 Jul 2007) | 5 lines
    Selectively enable tests for asyncore.readwrite based on the presence
    of poll support in the select module (since this is the only case in
    which readwrite can be called). [GSoC - Alan McIntyre]
  ........
    r56488 | nick.coghlan | 2007-07-22 03:18:07 -0700 (Sun, 22 Jul 2007) | 1 line
    Add explicit relative import tests for runpy.run_module
  ........
    r56509 | nick.coghlan | 2007-07-23 06:41:45 -0700 (Mon, 23 Jul 2007) | 5 lines
    Correctly cleanup sys.modules after executing runpy relative import
    tests
    Restore Python 2.4 ImportError when attempting to execute a package
    (as imports cannot be guaranteed to work properly if you try it)
  ........
    r56519 | nick.coghlan | 2007-07-24 06:07:38 -0700 (Tue, 24 Jul 2007) | 1 line
    Tweak runpy test to do a better job of confirming that sys has been manipulated correctly
  ........
    r56520 | nick.coghlan | 2007-07-24 06:58:28 -0700 (Tue, 24 Jul 2007) | 1 line
    Fix an incompatibility between the -i and -m command line switches as reported on python-dev by PJE - runpy.run_module now leaves any changes it makes to the sys module intact after the function terminates
  ........
    r56523 | nick.coghlan | 2007-07-24 07:39:23 -0700 (Tue, 24 Jul 2007) | 1 line
    Try to get rid of spurious failure in test_resource on the Debian buildbots by changing the file size limit before attempting to close the file
  ........
    r56533 | facundo.batista | 2007-07-24 14:20:42 -0700 (Tue, 24 Jul 2007) | 7 lines
    New tests for basic behavior of smtplib.SMTP and
    smtpd.DebuggingServer. Change to use global host & port number
    variables. Modified the 'server' to take a string to send back in
    order to vary test server responses. Added a test for the reaction of
    smtplib.SMTP to a non-200 HELO response. [GSoC - Alan McIntyre]
  ........
    r56538 | nick.coghlan | 2007-07-25 05:57:48 -0700 (Wed, 25 Jul 2007) | 1 line
    More buildbot cleanup - let the OS assign the port for test_urllib2_localnet
  ........
    r56539 | nick.coghlan | 2007-07-25 06:18:58 -0700 (Wed, 25 Jul 2007) | 1 line
    Add a temporary diagnostic message before a strange failure on the alpha Debian buildbot
  ........
    r56543 | martin.v.loewis | 2007-07-25 09:24:23 -0700 (Wed, 25 Jul 2007) | 2 lines
    Change location of the package index to pypi.python.org/pypi
  ........
    r56551 | georg.brandl | 2007-07-26 02:36:25 -0700 (Thu, 26 Jul 2007) | 2 lines
    tabs, newlines and crs are valid XML characters.
  ........
    r56553 | nick.coghlan | 2007-07-26 07:03:00 -0700 (Thu, 26 Jul 2007) | 1 line
    Add explicit test for a misbehaving math.floor
  ........
    r56561 | mark.hammond | 2007-07-26 21:52:32 -0700 (Thu, 26 Jul 2007) | 3 lines
    In consultation with Kristjan Jonsson, only define WINVER and _WINNT_WIN32
    if (a) we are building Python itself and (b) no one previously defined them
  ........
    r56562 | mark.hammond | 2007-07-26 22:08:54 -0700 (Thu, 26 Jul 2007) | 2 lines
    Correctly detect AMD64 architecture on VC2003
  ........
    r56566 | nick.coghlan | 2007-07-27 03:36:30 -0700 (Fri, 27 Jul 2007) | 1 line
    Make test_math error messages more meaningful for small discrepancies in results
  ........
    r56588 | martin.v.loewis | 2007-07-27 11:28:22 -0700 (Fri, 27 Jul 2007) | 2 lines
    Bug #978833: Close https sockets by releasing the _ssl object.
  ........
    r56601 | martin.v.loewis | 2007-07-28 00:03:05 -0700 (Sat, 28 Jul 2007) | 3 lines
    Bug #1704793: Return UTF-16 pair if unicodedata.lookup cannot
    represent the result in a single character.
  ........
    r56604 | facundo.batista | 2007-07-28 07:21:22 -0700 (Sat, 28 Jul 2007) | 9 lines
    Moved all of the capture_server socket setup code into the try block
    so that the event gets set if a failure occurs during server setup
    (otherwise the test will block forever).  Changed to let the OS assign
    the server port number, and client side of test waits for port number
    assignment before proceeding. The test data in DispatcherWithSendTests
    is also sent in multiple send() calls instead of one to make sure this
    works properly. [GSoC - Alan McIntyre]
  ........
    r56611 | georg.brandl | 2007-07-29 01:26:10 -0700 (Sun, 29 Jul 2007) | 2 lines
    Clarify PEP 343 description.
  ........
    r56614 | georg.brandl | 2007-07-29 02:11:15 -0700 (Sun, 29 Jul 2007) | 2 lines
    try-except-finally is new in 2.5.
  ........
    r56617 | facundo.batista | 2007-07-29 07:23:08 -0700 (Sun, 29 Jul 2007) | 9 lines
    Added tests for asynchat classes simple_producer & fifo, and the
    find_prefix_at_end function. Check behavior of a string given as a
    producer.  Added tests for behavior of asynchat.async_chat when given
    int, long, and None terminator arguments. Added usepoll attribute to
    TestAsynchat to allow running the asynchat tests with poll support
    chosen whether it's available or not (improves coverage of asyncore
    code). [GSoC - Alan McIntyre]
  ........
    r56620 | georg.brandl | 2007-07-29 10:38:35 -0700 (Sun, 29 Jul 2007) | 2 lines
    Bug #1763149: use proper slice syntax in docstring.
     (backport)
  ........
    r56624 | mark.hammond | 2007-07-29 17:45:29 -0700 (Sun, 29 Jul 2007) | 4 lines
    Correct use of Py_BUILD_CORE - now make sure it is defined before it is
    referenced, and also fix definition of _WIN32_WINNT.
    Resolves patch 1761803.
  ........
    r56632 | facundo.batista | 2007-07-30 20:03:34 -0700 (Mon, 30 Jul 2007) | 8 lines
    When running asynchat tests on OS X (darwin), the test client now
    overrides asyncore.dispatcher.handle_expt to do nothing, since
    select.poll gives a POLLHUP error at the completion of these tests.
    Added timeout & count arguments to several asyncore.loop calls to
    avoid the possibility of a test hanging up a build. [GSoC - Alan
    McIntyre]
  ........
    r56633 | nick.coghlan | 2007-07-31 06:38:01 -0700 (Tue, 31 Jul 2007) | 1 line
    Eliminate RLock race condition reported in SF bug #1764059
  ........
    r56636 | martin.v.loewis | 2007-07-31 12:57:56 -0700 (Tue, 31 Jul 2007) | 2 lines
    Define _BSD_SOURCE, to get access to POSIX extensions on OpenBSD 4.1+.
  ........
    r56653 | facundo.batista | 2007-08-01 16:18:36 -0700 (Wed, 01 Aug 2007) | 9 lines
    Allow the OS to select a free port for each test server. For
    DebuggingServerTests, construct SMTP objects with a localhost argument
    to avoid abysmally long FQDN lookups (not relevant to items under
    test) on some machines that would cause the test to fail. Moved server
    setup code in the server function inside the try block to avoid the
    possibility of setup failure hanging the test.  Minor edits to conform
    to PEP 8. [GSoC - Alan McIntyre]
  ........
    r56681 | matthias.klose | 2007-08-02 14:33:13 -0700 (Thu, 02 Aug 2007) | 2 lines
    - Allow Emacs 22 for building the documentation in info format.
  ........
    r56689 | neal.norwitz | 2007-08-02 23:46:29 -0700 (Thu, 02 Aug 2007) | 1 line
    Py_ssize_t is defined regardless of HAVE_LONG_LONG.  Will backport
  ........
    r56727 | hyeshik.chang | 2007-08-03 21:10:18 -0700 (Fri, 03 Aug 2007) | 3 lines
    Fix gb18030 codec's bug that doesn't map two-byte characters on
    GB18030 extension in encoding. (bug reported by Bjorn Stabell)
  ........
    r56751 | neal.norwitz | 2007-08-04 20:23:31 -0700 (Sat, 04 Aug 2007) | 7 lines
    Handle errors when generating a warning.
    The value is always written to the returned pointer if getting it was
    successful, even if a warning causes an error. (This probably doesn't matter
    as the caller will probably discard the value.)
    Will backport.
  ........
................
		
	
			
		
			
				
	
	
		
			546 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			546 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#! /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(__doc__ % globals(), file=sys.stderr)
 | 
						||
    if msg:
 | 
						||
        print(msg, file=sys.stderr)
 | 
						||
    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()
 | 
						||
        self.__peer = conn.getpeername()
 | 
						||
        print('Peer:', repr(self.__peer), file=DEBUGSTREAM)
 | 
						||
        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(str(data, "utf8"))
 | 
						||
 | 
						||
    # Implementation of base class abstract method
 | 
						||
    def found_terminator(self):
 | 
						||
        line = EMPTYSTRING.join(self.__line)
 | 
						||
        print('Data:', repr(line), file=DEBUGSTREAM)
 | 
						||
        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('===> MAIL', arg, file=DEBUGSTREAM)
 | 
						||
        address = self.__getaddr('FROM:', arg)
 | 
						||
        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('sender:', self.__mailfrom, file=DEBUGSTREAM)
 | 
						||
        self.push('250 Ok')
 | 
						||
 | 
						||
    def smtp_RCPT(self, arg):
 | 
						||
        print('===> RCPT', arg, file=DEBUGSTREAM)
 | 
						||
        if not self.__mailfrom:
 | 
						||
            self.push('503 Error: need MAIL command')
 | 
						||
            return
 | 
						||
        address = self.__getaddr('TO:', arg)
 | 
						||
        if not address:
 | 
						||
            self.push('501 Syntax: RCPT TO: <address>')
 | 
						||
            return
 | 
						||
        self.__rcpttos.append(address)
 | 
						||
        print('recips:', self.__rcpttos, file=DEBUGSTREAM)
 | 
						||
        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)
 | 
						||
        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)
 | 
						||
        print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
 | 
						||
            self.__class__.__name__, time.ctime(time.time()),
 | 
						||
            localaddr, remoteaddr), file=DEBUGSTREAM)
 | 
						||
 | 
						||
    def handle_accept(self):
 | 
						||
        conn, addr = self.accept()
 | 
						||
        print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
 | 
						||
        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('we got some refusals:', refused, file=DEBUGSTREAM)
 | 
						||
 | 
						||
    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 as e:
 | 
						||
            print('got SMTPRecipientsRefused', file=DEBUGSTREAM)
 | 
						||
            refused = e.recipients
 | 
						||
        except (socket.error, smtplib.SMTPException) as e:
 | 
						||
            print('got', e.__class__, file=DEBUGSTREAM)
 | 
						||
            # 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 io 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('forwarding recips:', ' '.join(rcpttos), file=DEBUGSTREAM)
 | 
						||
        if rcpttos:
 | 
						||
            refused = self._deliver(mailfrom, rcpttos, data)
 | 
						||
            # TBD: what to do with refused addresses?
 | 
						||
            print('we got refusals:', refused, file=DEBUGSTREAM)
 | 
						||
        # 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 existance 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('sending message to', rcpt, file=DEBUGSTREAM)
 | 
						||
            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 as e:
 | 
						||
        usage(1, e)
 | 
						||
 | 
						||
    options = Options()
 | 
						||
    for opt, arg in opts:
 | 
						||
        if opt in ('-h', '--help'):
 | 
						||
            usage(0)
 | 
						||
        elif opt in ('-V', '--version'):
 | 
						||
            print(__version__, file=sys.stderr)
 | 
						||
            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('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)
 | 
						||
            sys.exit(1)
 | 
						||
        nobody = pwd.getpwnam('nobody')[2]
 | 
						||
        try:
 | 
						||
            os.setuid(nobody)
 | 
						||
        except OSError as e:
 | 
						||
            if e.errno != errno.EPERM: raise
 | 
						||
            print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)
 | 
						||
            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
 |