17db96d56Sopenharmony_ci#! /usr/bin/env python3
27db96d56Sopenharmony_ci"""An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ciUsage: %(program)s [options] [localhost:localport [remotehost:remoteport]]
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ciOptions:
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_ci    --nosetuid
97db96d56Sopenharmony_ci    -n
107db96d56Sopenharmony_ci        This program generally tries to setuid `nobody', unless this flag is
117db96d56Sopenharmony_ci        set.  The setuid call will fail if this program is not run as root (in
127db96d56Sopenharmony_ci        which case, use this flag).
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_ci    --version
157db96d56Sopenharmony_ci    -V
167db96d56Sopenharmony_ci        Print the version number and exit.
177db96d56Sopenharmony_ci
187db96d56Sopenharmony_ci    --class classname
197db96d56Sopenharmony_ci    -c classname
207db96d56Sopenharmony_ci        Use `classname' as the concrete SMTP proxy class.  Uses `PureProxy' by
217db96d56Sopenharmony_ci        default.
227db96d56Sopenharmony_ci
237db96d56Sopenharmony_ci    --size limit
247db96d56Sopenharmony_ci    -s limit
257db96d56Sopenharmony_ci        Restrict the total size of the incoming message to "limit" number of
267db96d56Sopenharmony_ci        bytes via the RFC 1870 SIZE extension.  Defaults to 33554432 bytes.
277db96d56Sopenharmony_ci
287db96d56Sopenharmony_ci    --smtputf8
297db96d56Sopenharmony_ci    -u
307db96d56Sopenharmony_ci        Enable the SMTPUTF8 extension and behave as an RFC 6531 smtp proxy.
317db96d56Sopenharmony_ci
327db96d56Sopenharmony_ci    --debug
337db96d56Sopenharmony_ci    -d
347db96d56Sopenharmony_ci        Turn on debugging prints.
357db96d56Sopenharmony_ci
367db96d56Sopenharmony_ci    --help
377db96d56Sopenharmony_ci    -h
387db96d56Sopenharmony_ci        Print this message and exit.
397db96d56Sopenharmony_ci
407db96d56Sopenharmony_ciVersion: %(__version__)s
417db96d56Sopenharmony_ci
427db96d56Sopenharmony_ciIf localhost is not given then `localhost' is used, and if localport is not
437db96d56Sopenharmony_cigiven then 8025 is used.  If remotehost is not given then `localhost' is used,
447db96d56Sopenharmony_ciand if remoteport is not given, then 25 is used.
457db96d56Sopenharmony_ci"""
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_ci# Overview:
487db96d56Sopenharmony_ci#
497db96d56Sopenharmony_ci# This file implements the minimal SMTP protocol as defined in RFC 5321.  It
507db96d56Sopenharmony_ci# has a hierarchy of classes which implement the backend functionality for the
517db96d56Sopenharmony_ci# smtpd.  A number of classes are provided:
527db96d56Sopenharmony_ci#
537db96d56Sopenharmony_ci#   SMTPServer - the base class for the backend.  Raises NotImplementedError
547db96d56Sopenharmony_ci#   if you try to use it.
557db96d56Sopenharmony_ci#
567db96d56Sopenharmony_ci#   DebuggingServer - simply prints each message it receives on stdout.
577db96d56Sopenharmony_ci#
587db96d56Sopenharmony_ci#   PureProxy - Proxies all messages to a real smtpd which does final
597db96d56Sopenharmony_ci#   delivery.  One known problem with this class is that it doesn't handle
607db96d56Sopenharmony_ci#   SMTP errors from the backend server at all.  This should be fixed
617db96d56Sopenharmony_ci#   (contributions are welcome!).
627db96d56Sopenharmony_ci#
637db96d56Sopenharmony_ci#
647db96d56Sopenharmony_ci# Author: Barry Warsaw <barry@python.org>
657db96d56Sopenharmony_ci#
667db96d56Sopenharmony_ci# TODO:
677db96d56Sopenharmony_ci#
687db96d56Sopenharmony_ci# - support mailbox delivery
697db96d56Sopenharmony_ci# - alias files
707db96d56Sopenharmony_ci# - Handle more ESMTP extensions
717db96d56Sopenharmony_ci# - handle error codes from the backend smtpd
727db96d56Sopenharmony_ci
737db96d56Sopenharmony_ciimport sys
747db96d56Sopenharmony_ciimport os
757db96d56Sopenharmony_ciimport errno
767db96d56Sopenharmony_ciimport getopt
777db96d56Sopenharmony_ciimport time
787db96d56Sopenharmony_ciimport socket
797db96d56Sopenharmony_ciimport collections
807db96d56Sopenharmony_cifrom warnings import _deprecated, warn
817db96d56Sopenharmony_cifrom email._header_value_parser import get_addr_spec, get_angle_addr
827db96d56Sopenharmony_ci
837db96d56Sopenharmony_ci__all__ = [
847db96d56Sopenharmony_ci    "SMTPChannel", "SMTPServer", "DebuggingServer", "PureProxy",
857db96d56Sopenharmony_ci]
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ci_DEPRECATION_MSG = ('The {name} module is deprecated and unmaintained and will '
887db96d56Sopenharmony_ci                    'be removed in Python {remove}.  Please see aiosmtpd '
897db96d56Sopenharmony_ci                    '(https://aiosmtpd.readthedocs.io/) for the recommended '
907db96d56Sopenharmony_ci                    'replacement.')
917db96d56Sopenharmony_ci_deprecated(__name__, _DEPRECATION_MSG, remove=(3, 12))
927db96d56Sopenharmony_ci
937db96d56Sopenharmony_ci
947db96d56Sopenharmony_ci# These are imported after the above warning so that users get the correct
957db96d56Sopenharmony_ci# deprecation warning.
967db96d56Sopenharmony_ciimport asyncore
977db96d56Sopenharmony_ciimport asynchat
987db96d56Sopenharmony_ci
997db96d56Sopenharmony_ci
1007db96d56Sopenharmony_ciprogram = sys.argv[0]
1017db96d56Sopenharmony_ci__version__ = 'Python SMTP proxy version 0.3'
1027db96d56Sopenharmony_ci
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ciclass Devnull:
1057db96d56Sopenharmony_ci    def write(self, msg): pass
1067db96d56Sopenharmony_ci    def flush(self): pass
1077db96d56Sopenharmony_ci
1087db96d56Sopenharmony_ci
1097db96d56Sopenharmony_ciDEBUGSTREAM = Devnull()
1107db96d56Sopenharmony_ciNEWLINE = '\n'
1117db96d56Sopenharmony_ciCOMMASPACE = ', '
1127db96d56Sopenharmony_ciDATA_SIZE_DEFAULT = 33554432
1137db96d56Sopenharmony_ci
1147db96d56Sopenharmony_ci
1157db96d56Sopenharmony_cidef usage(code, msg=''):
1167db96d56Sopenharmony_ci    print(__doc__ % globals(), file=sys.stderr)
1177db96d56Sopenharmony_ci    if msg:
1187db96d56Sopenharmony_ci        print(msg, file=sys.stderr)
1197db96d56Sopenharmony_ci    sys.exit(code)
1207db96d56Sopenharmony_ci
1217db96d56Sopenharmony_ci
1227db96d56Sopenharmony_ciclass SMTPChannel(asynchat.async_chat):
1237db96d56Sopenharmony_ci    COMMAND = 0
1247db96d56Sopenharmony_ci    DATA = 1
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ci    command_size_limit = 512
1277db96d56Sopenharmony_ci    command_size_limits = collections.defaultdict(lambda x=command_size_limit: x)
1287db96d56Sopenharmony_ci
1297db96d56Sopenharmony_ci    @property
1307db96d56Sopenharmony_ci    def max_command_size_limit(self):
1317db96d56Sopenharmony_ci        try:
1327db96d56Sopenharmony_ci            return max(self.command_size_limits.values())
1337db96d56Sopenharmony_ci        except ValueError:
1347db96d56Sopenharmony_ci            return self.command_size_limit
1357db96d56Sopenharmony_ci
1367db96d56Sopenharmony_ci    def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
1377db96d56Sopenharmony_ci                 map=None, enable_SMTPUTF8=False, decode_data=False):
1387db96d56Sopenharmony_ci        asynchat.async_chat.__init__(self, conn, map=map)
1397db96d56Sopenharmony_ci        self.smtp_server = server
1407db96d56Sopenharmony_ci        self.conn = conn
1417db96d56Sopenharmony_ci        self.addr = addr
1427db96d56Sopenharmony_ci        self.data_size_limit = data_size_limit
1437db96d56Sopenharmony_ci        self.enable_SMTPUTF8 = enable_SMTPUTF8
1447db96d56Sopenharmony_ci        self._decode_data = decode_data
1457db96d56Sopenharmony_ci        if enable_SMTPUTF8 and decode_data:
1467db96d56Sopenharmony_ci            raise ValueError("decode_data and enable_SMTPUTF8 cannot"
1477db96d56Sopenharmony_ci                             " be set to True at the same time")
1487db96d56Sopenharmony_ci        if decode_data:
1497db96d56Sopenharmony_ci            self._emptystring = ''
1507db96d56Sopenharmony_ci            self._linesep = '\r\n'
1517db96d56Sopenharmony_ci            self._dotsep = '.'
1527db96d56Sopenharmony_ci            self._newline = NEWLINE
1537db96d56Sopenharmony_ci        else:
1547db96d56Sopenharmony_ci            self._emptystring = b''
1557db96d56Sopenharmony_ci            self._linesep = b'\r\n'
1567db96d56Sopenharmony_ci            self._dotsep = ord(b'.')
1577db96d56Sopenharmony_ci            self._newline = b'\n'
1587db96d56Sopenharmony_ci        self._set_rset_state()
1597db96d56Sopenharmony_ci        self.seen_greeting = ''
1607db96d56Sopenharmony_ci        self.extended_smtp = False
1617db96d56Sopenharmony_ci        self.command_size_limits.clear()
1627db96d56Sopenharmony_ci        self.fqdn = socket.getfqdn()
1637db96d56Sopenharmony_ci        try:
1647db96d56Sopenharmony_ci            self.peer = conn.getpeername()
1657db96d56Sopenharmony_ci        except OSError as err:
1667db96d56Sopenharmony_ci            # a race condition  may occur if the other end is closing
1677db96d56Sopenharmony_ci            # before we can get the peername
1687db96d56Sopenharmony_ci            self.close()
1697db96d56Sopenharmony_ci            if err.errno != errno.ENOTCONN:
1707db96d56Sopenharmony_ci                raise
1717db96d56Sopenharmony_ci            return
1727db96d56Sopenharmony_ci        print('Peer:', repr(self.peer), file=DEBUGSTREAM)
1737db96d56Sopenharmony_ci        self.push('220 %s %s' % (self.fqdn, __version__))
1747db96d56Sopenharmony_ci
1757db96d56Sopenharmony_ci    def _set_post_data_state(self):
1767db96d56Sopenharmony_ci        """Reset state variables to their post-DATA state."""
1777db96d56Sopenharmony_ci        self.smtp_state = self.COMMAND
1787db96d56Sopenharmony_ci        self.mailfrom = None
1797db96d56Sopenharmony_ci        self.rcpttos = []
1807db96d56Sopenharmony_ci        self.require_SMTPUTF8 = False
1817db96d56Sopenharmony_ci        self.num_bytes = 0
1827db96d56Sopenharmony_ci        self.set_terminator(b'\r\n')
1837db96d56Sopenharmony_ci
1847db96d56Sopenharmony_ci    def _set_rset_state(self):
1857db96d56Sopenharmony_ci        """Reset all state variables except the greeting."""
1867db96d56Sopenharmony_ci        self._set_post_data_state()
1877db96d56Sopenharmony_ci        self.received_data = ''
1887db96d56Sopenharmony_ci        self.received_lines = []
1897db96d56Sopenharmony_ci
1907db96d56Sopenharmony_ci
1917db96d56Sopenharmony_ci    # properties for backwards-compatibility
1927db96d56Sopenharmony_ci    @property
1937db96d56Sopenharmony_ci    def __server(self):
1947db96d56Sopenharmony_ci        warn("Access to __server attribute on SMTPChannel is deprecated, "
1957db96d56Sopenharmony_ci            "use 'smtp_server' instead", DeprecationWarning, 2)
1967db96d56Sopenharmony_ci        return self.smtp_server
1977db96d56Sopenharmony_ci    @__server.setter
1987db96d56Sopenharmony_ci    def __server(self, value):
1997db96d56Sopenharmony_ci        warn("Setting __server attribute on SMTPChannel is deprecated, "
2007db96d56Sopenharmony_ci            "set 'smtp_server' instead", DeprecationWarning, 2)
2017db96d56Sopenharmony_ci        self.smtp_server = value
2027db96d56Sopenharmony_ci
2037db96d56Sopenharmony_ci    @property
2047db96d56Sopenharmony_ci    def __line(self):
2057db96d56Sopenharmony_ci        warn("Access to __line attribute on SMTPChannel is deprecated, "
2067db96d56Sopenharmony_ci            "use 'received_lines' instead", DeprecationWarning, 2)
2077db96d56Sopenharmony_ci        return self.received_lines
2087db96d56Sopenharmony_ci    @__line.setter
2097db96d56Sopenharmony_ci    def __line(self, value):
2107db96d56Sopenharmony_ci        warn("Setting __line attribute on SMTPChannel is deprecated, "
2117db96d56Sopenharmony_ci            "set 'received_lines' instead", DeprecationWarning, 2)
2127db96d56Sopenharmony_ci        self.received_lines = value
2137db96d56Sopenharmony_ci
2147db96d56Sopenharmony_ci    @property
2157db96d56Sopenharmony_ci    def __state(self):
2167db96d56Sopenharmony_ci        warn("Access to __state attribute on SMTPChannel is deprecated, "
2177db96d56Sopenharmony_ci            "use 'smtp_state' instead", DeprecationWarning, 2)
2187db96d56Sopenharmony_ci        return self.smtp_state
2197db96d56Sopenharmony_ci    @__state.setter
2207db96d56Sopenharmony_ci    def __state(self, value):
2217db96d56Sopenharmony_ci        warn("Setting __state attribute on SMTPChannel is deprecated, "
2227db96d56Sopenharmony_ci            "set 'smtp_state' instead", DeprecationWarning, 2)
2237db96d56Sopenharmony_ci        self.smtp_state = value
2247db96d56Sopenharmony_ci
2257db96d56Sopenharmony_ci    @property
2267db96d56Sopenharmony_ci    def __greeting(self):
2277db96d56Sopenharmony_ci        warn("Access to __greeting attribute on SMTPChannel is deprecated, "
2287db96d56Sopenharmony_ci            "use 'seen_greeting' instead", DeprecationWarning, 2)
2297db96d56Sopenharmony_ci        return self.seen_greeting
2307db96d56Sopenharmony_ci    @__greeting.setter
2317db96d56Sopenharmony_ci    def __greeting(self, value):
2327db96d56Sopenharmony_ci        warn("Setting __greeting attribute on SMTPChannel is deprecated, "
2337db96d56Sopenharmony_ci            "set 'seen_greeting' instead", DeprecationWarning, 2)
2347db96d56Sopenharmony_ci        self.seen_greeting = value
2357db96d56Sopenharmony_ci
2367db96d56Sopenharmony_ci    @property
2377db96d56Sopenharmony_ci    def __mailfrom(self):
2387db96d56Sopenharmony_ci        warn("Access to __mailfrom attribute on SMTPChannel is deprecated, "
2397db96d56Sopenharmony_ci            "use 'mailfrom' instead", DeprecationWarning, 2)
2407db96d56Sopenharmony_ci        return self.mailfrom
2417db96d56Sopenharmony_ci    @__mailfrom.setter
2427db96d56Sopenharmony_ci    def __mailfrom(self, value):
2437db96d56Sopenharmony_ci        warn("Setting __mailfrom attribute on SMTPChannel is deprecated, "
2447db96d56Sopenharmony_ci            "set 'mailfrom' instead", DeprecationWarning, 2)
2457db96d56Sopenharmony_ci        self.mailfrom = value
2467db96d56Sopenharmony_ci
2477db96d56Sopenharmony_ci    @property
2487db96d56Sopenharmony_ci    def __rcpttos(self):
2497db96d56Sopenharmony_ci        warn("Access to __rcpttos attribute on SMTPChannel is deprecated, "
2507db96d56Sopenharmony_ci            "use 'rcpttos' instead", DeprecationWarning, 2)
2517db96d56Sopenharmony_ci        return self.rcpttos
2527db96d56Sopenharmony_ci    @__rcpttos.setter
2537db96d56Sopenharmony_ci    def __rcpttos(self, value):
2547db96d56Sopenharmony_ci        warn("Setting __rcpttos attribute on SMTPChannel is deprecated, "
2557db96d56Sopenharmony_ci            "set 'rcpttos' instead", DeprecationWarning, 2)
2567db96d56Sopenharmony_ci        self.rcpttos = value
2577db96d56Sopenharmony_ci
2587db96d56Sopenharmony_ci    @property
2597db96d56Sopenharmony_ci    def __data(self):
2607db96d56Sopenharmony_ci        warn("Access to __data attribute on SMTPChannel is deprecated, "
2617db96d56Sopenharmony_ci            "use 'received_data' instead", DeprecationWarning, 2)
2627db96d56Sopenharmony_ci        return self.received_data
2637db96d56Sopenharmony_ci    @__data.setter
2647db96d56Sopenharmony_ci    def __data(self, value):
2657db96d56Sopenharmony_ci        warn("Setting __data attribute on SMTPChannel is deprecated, "
2667db96d56Sopenharmony_ci            "set 'received_data' instead", DeprecationWarning, 2)
2677db96d56Sopenharmony_ci        self.received_data = value
2687db96d56Sopenharmony_ci
2697db96d56Sopenharmony_ci    @property
2707db96d56Sopenharmony_ci    def __fqdn(self):
2717db96d56Sopenharmony_ci        warn("Access to __fqdn attribute on SMTPChannel is deprecated, "
2727db96d56Sopenharmony_ci            "use 'fqdn' instead", DeprecationWarning, 2)
2737db96d56Sopenharmony_ci        return self.fqdn
2747db96d56Sopenharmony_ci    @__fqdn.setter
2757db96d56Sopenharmony_ci    def __fqdn(self, value):
2767db96d56Sopenharmony_ci        warn("Setting __fqdn attribute on SMTPChannel is deprecated, "
2777db96d56Sopenharmony_ci            "set 'fqdn' instead", DeprecationWarning, 2)
2787db96d56Sopenharmony_ci        self.fqdn = value
2797db96d56Sopenharmony_ci
2807db96d56Sopenharmony_ci    @property
2817db96d56Sopenharmony_ci    def __peer(self):
2827db96d56Sopenharmony_ci        warn("Access to __peer attribute on SMTPChannel is deprecated, "
2837db96d56Sopenharmony_ci            "use 'peer' instead", DeprecationWarning, 2)
2847db96d56Sopenharmony_ci        return self.peer
2857db96d56Sopenharmony_ci    @__peer.setter
2867db96d56Sopenharmony_ci    def __peer(self, value):
2877db96d56Sopenharmony_ci        warn("Setting __peer attribute on SMTPChannel is deprecated, "
2887db96d56Sopenharmony_ci            "set 'peer' instead", DeprecationWarning, 2)
2897db96d56Sopenharmony_ci        self.peer = value
2907db96d56Sopenharmony_ci
2917db96d56Sopenharmony_ci    @property
2927db96d56Sopenharmony_ci    def __conn(self):
2937db96d56Sopenharmony_ci        warn("Access to __conn attribute on SMTPChannel is deprecated, "
2947db96d56Sopenharmony_ci            "use 'conn' instead", DeprecationWarning, 2)
2957db96d56Sopenharmony_ci        return self.conn
2967db96d56Sopenharmony_ci    @__conn.setter
2977db96d56Sopenharmony_ci    def __conn(self, value):
2987db96d56Sopenharmony_ci        warn("Setting __conn attribute on SMTPChannel is deprecated, "
2997db96d56Sopenharmony_ci            "set 'conn' instead", DeprecationWarning, 2)
3007db96d56Sopenharmony_ci        self.conn = value
3017db96d56Sopenharmony_ci
3027db96d56Sopenharmony_ci    @property
3037db96d56Sopenharmony_ci    def __addr(self):
3047db96d56Sopenharmony_ci        warn("Access to __addr attribute on SMTPChannel is deprecated, "
3057db96d56Sopenharmony_ci            "use 'addr' instead", DeprecationWarning, 2)
3067db96d56Sopenharmony_ci        return self.addr
3077db96d56Sopenharmony_ci    @__addr.setter
3087db96d56Sopenharmony_ci    def __addr(self, value):
3097db96d56Sopenharmony_ci        warn("Setting __addr attribute on SMTPChannel is deprecated, "
3107db96d56Sopenharmony_ci            "set 'addr' instead", DeprecationWarning, 2)
3117db96d56Sopenharmony_ci        self.addr = value
3127db96d56Sopenharmony_ci
3137db96d56Sopenharmony_ci    # Overrides base class for convenience.
3147db96d56Sopenharmony_ci    def push(self, msg):
3157db96d56Sopenharmony_ci        asynchat.async_chat.push(self, bytes(
3167db96d56Sopenharmony_ci            msg + '\r\n', 'utf-8' if self.require_SMTPUTF8 else 'ascii'))
3177db96d56Sopenharmony_ci
3187db96d56Sopenharmony_ci    # Implementation of base class abstract method
3197db96d56Sopenharmony_ci    def collect_incoming_data(self, data):
3207db96d56Sopenharmony_ci        limit = None
3217db96d56Sopenharmony_ci        if self.smtp_state == self.COMMAND:
3227db96d56Sopenharmony_ci            limit = self.max_command_size_limit
3237db96d56Sopenharmony_ci        elif self.smtp_state == self.DATA:
3247db96d56Sopenharmony_ci            limit = self.data_size_limit
3257db96d56Sopenharmony_ci        if limit and self.num_bytes > limit:
3267db96d56Sopenharmony_ci            return
3277db96d56Sopenharmony_ci        elif limit:
3287db96d56Sopenharmony_ci            self.num_bytes += len(data)
3297db96d56Sopenharmony_ci        if self._decode_data:
3307db96d56Sopenharmony_ci            self.received_lines.append(str(data, 'utf-8'))
3317db96d56Sopenharmony_ci        else:
3327db96d56Sopenharmony_ci            self.received_lines.append(data)
3337db96d56Sopenharmony_ci
3347db96d56Sopenharmony_ci    # Implementation of base class abstract method
3357db96d56Sopenharmony_ci    def found_terminator(self):
3367db96d56Sopenharmony_ci        line = self._emptystring.join(self.received_lines)
3377db96d56Sopenharmony_ci        print('Data:', repr(line), file=DEBUGSTREAM)
3387db96d56Sopenharmony_ci        self.received_lines = []
3397db96d56Sopenharmony_ci        if self.smtp_state == self.COMMAND:
3407db96d56Sopenharmony_ci            sz, self.num_bytes = self.num_bytes, 0
3417db96d56Sopenharmony_ci            if not line:
3427db96d56Sopenharmony_ci                self.push('500 Error: bad syntax')
3437db96d56Sopenharmony_ci                return
3447db96d56Sopenharmony_ci            if not self._decode_data:
3457db96d56Sopenharmony_ci                line = str(line, 'utf-8')
3467db96d56Sopenharmony_ci            i = line.find(' ')
3477db96d56Sopenharmony_ci            if i < 0:
3487db96d56Sopenharmony_ci                command = line.upper()
3497db96d56Sopenharmony_ci                arg = None
3507db96d56Sopenharmony_ci            else:
3517db96d56Sopenharmony_ci                command = line[:i].upper()
3527db96d56Sopenharmony_ci                arg = line[i+1:].strip()
3537db96d56Sopenharmony_ci            max_sz = (self.command_size_limits[command]
3547db96d56Sopenharmony_ci                        if self.extended_smtp else self.command_size_limit)
3557db96d56Sopenharmony_ci            if sz > max_sz:
3567db96d56Sopenharmony_ci                self.push('500 Error: line too long')
3577db96d56Sopenharmony_ci                return
3587db96d56Sopenharmony_ci            method = getattr(self, 'smtp_' + command, None)
3597db96d56Sopenharmony_ci            if not method:
3607db96d56Sopenharmony_ci                self.push('500 Error: command "%s" not recognized' % command)
3617db96d56Sopenharmony_ci                return
3627db96d56Sopenharmony_ci            method(arg)
3637db96d56Sopenharmony_ci            return
3647db96d56Sopenharmony_ci        else:
3657db96d56Sopenharmony_ci            if self.smtp_state != self.DATA:
3667db96d56Sopenharmony_ci                self.push('451 Internal confusion')
3677db96d56Sopenharmony_ci                self.num_bytes = 0
3687db96d56Sopenharmony_ci                return
3697db96d56Sopenharmony_ci            if self.data_size_limit and self.num_bytes > self.data_size_limit:
3707db96d56Sopenharmony_ci                self.push('552 Error: Too much mail data')
3717db96d56Sopenharmony_ci                self.num_bytes = 0
3727db96d56Sopenharmony_ci                return
3737db96d56Sopenharmony_ci            # Remove extraneous carriage returns and de-transparency according
3747db96d56Sopenharmony_ci            # to RFC 5321, Section 4.5.2.
3757db96d56Sopenharmony_ci            data = []
3767db96d56Sopenharmony_ci            for text in line.split(self._linesep):
3777db96d56Sopenharmony_ci                if text and text[0] == self._dotsep:
3787db96d56Sopenharmony_ci                    data.append(text[1:])
3797db96d56Sopenharmony_ci                else:
3807db96d56Sopenharmony_ci                    data.append(text)
3817db96d56Sopenharmony_ci            self.received_data = self._newline.join(data)
3827db96d56Sopenharmony_ci            args = (self.peer, self.mailfrom, self.rcpttos, self.received_data)
3837db96d56Sopenharmony_ci            kwargs = {}
3847db96d56Sopenharmony_ci            if not self._decode_data:
3857db96d56Sopenharmony_ci                kwargs = {
3867db96d56Sopenharmony_ci                    'mail_options': self.mail_options,
3877db96d56Sopenharmony_ci                    'rcpt_options': self.rcpt_options,
3887db96d56Sopenharmony_ci                }
3897db96d56Sopenharmony_ci            status = self.smtp_server.process_message(*args, **kwargs)
3907db96d56Sopenharmony_ci            self._set_post_data_state()
3917db96d56Sopenharmony_ci            if not status:
3927db96d56Sopenharmony_ci                self.push('250 OK')
3937db96d56Sopenharmony_ci            else:
3947db96d56Sopenharmony_ci                self.push(status)
3957db96d56Sopenharmony_ci
3967db96d56Sopenharmony_ci    # SMTP and ESMTP commands
3977db96d56Sopenharmony_ci    def smtp_HELO(self, arg):
3987db96d56Sopenharmony_ci        if not arg:
3997db96d56Sopenharmony_ci            self.push('501 Syntax: HELO hostname')
4007db96d56Sopenharmony_ci            return
4017db96d56Sopenharmony_ci        # See issue #21783 for a discussion of this behavior.
4027db96d56Sopenharmony_ci        if self.seen_greeting:
4037db96d56Sopenharmony_ci            self.push('503 Duplicate HELO/EHLO')
4047db96d56Sopenharmony_ci            return
4057db96d56Sopenharmony_ci        self._set_rset_state()
4067db96d56Sopenharmony_ci        self.seen_greeting = arg
4077db96d56Sopenharmony_ci        self.push('250 %s' % self.fqdn)
4087db96d56Sopenharmony_ci
4097db96d56Sopenharmony_ci    def smtp_EHLO(self, arg):
4107db96d56Sopenharmony_ci        if not arg:
4117db96d56Sopenharmony_ci            self.push('501 Syntax: EHLO hostname')
4127db96d56Sopenharmony_ci            return
4137db96d56Sopenharmony_ci        # See issue #21783 for a discussion of this behavior.
4147db96d56Sopenharmony_ci        if self.seen_greeting:
4157db96d56Sopenharmony_ci            self.push('503 Duplicate HELO/EHLO')
4167db96d56Sopenharmony_ci            return
4177db96d56Sopenharmony_ci        self._set_rset_state()
4187db96d56Sopenharmony_ci        self.seen_greeting = arg
4197db96d56Sopenharmony_ci        self.extended_smtp = True
4207db96d56Sopenharmony_ci        self.push('250-%s' % self.fqdn)
4217db96d56Sopenharmony_ci        if self.data_size_limit:
4227db96d56Sopenharmony_ci            self.push('250-SIZE %s' % self.data_size_limit)
4237db96d56Sopenharmony_ci            self.command_size_limits['MAIL'] += 26
4247db96d56Sopenharmony_ci        if not self._decode_data:
4257db96d56Sopenharmony_ci            self.push('250-8BITMIME')
4267db96d56Sopenharmony_ci        if self.enable_SMTPUTF8:
4277db96d56Sopenharmony_ci            self.push('250-SMTPUTF8')
4287db96d56Sopenharmony_ci            self.command_size_limits['MAIL'] += 10
4297db96d56Sopenharmony_ci        self.push('250 HELP')
4307db96d56Sopenharmony_ci
4317db96d56Sopenharmony_ci    def smtp_NOOP(self, arg):
4327db96d56Sopenharmony_ci        if arg:
4337db96d56Sopenharmony_ci            self.push('501 Syntax: NOOP')
4347db96d56Sopenharmony_ci        else:
4357db96d56Sopenharmony_ci            self.push('250 OK')
4367db96d56Sopenharmony_ci
4377db96d56Sopenharmony_ci    def smtp_QUIT(self, arg):
4387db96d56Sopenharmony_ci        # args is ignored
4397db96d56Sopenharmony_ci        self.push('221 Bye')
4407db96d56Sopenharmony_ci        self.close_when_done()
4417db96d56Sopenharmony_ci
4427db96d56Sopenharmony_ci    def _strip_command_keyword(self, keyword, arg):
4437db96d56Sopenharmony_ci        keylen = len(keyword)
4447db96d56Sopenharmony_ci        if arg[:keylen].upper() == keyword:
4457db96d56Sopenharmony_ci            return arg[keylen:].strip()
4467db96d56Sopenharmony_ci        return ''
4477db96d56Sopenharmony_ci
4487db96d56Sopenharmony_ci    def _getaddr(self, arg):
4497db96d56Sopenharmony_ci        if not arg:
4507db96d56Sopenharmony_ci            return '', ''
4517db96d56Sopenharmony_ci        if arg.lstrip().startswith('<'):
4527db96d56Sopenharmony_ci            address, rest = get_angle_addr(arg)
4537db96d56Sopenharmony_ci        else:
4547db96d56Sopenharmony_ci            address, rest = get_addr_spec(arg)
4557db96d56Sopenharmony_ci        if not address:
4567db96d56Sopenharmony_ci            return address, rest
4577db96d56Sopenharmony_ci        return address.addr_spec, rest
4587db96d56Sopenharmony_ci
4597db96d56Sopenharmony_ci    def _getparams(self, params):
4607db96d56Sopenharmony_ci        # Return params as dictionary. Return None if not all parameters
4617db96d56Sopenharmony_ci        # appear to be syntactically valid according to RFC 1869.
4627db96d56Sopenharmony_ci        result = {}
4637db96d56Sopenharmony_ci        for param in params:
4647db96d56Sopenharmony_ci            param, eq, value = param.partition('=')
4657db96d56Sopenharmony_ci            if not param.isalnum() or eq and not value:
4667db96d56Sopenharmony_ci                return None
4677db96d56Sopenharmony_ci            result[param] = value if eq else True
4687db96d56Sopenharmony_ci        return result
4697db96d56Sopenharmony_ci
4707db96d56Sopenharmony_ci    def smtp_HELP(self, arg):
4717db96d56Sopenharmony_ci        if arg:
4727db96d56Sopenharmony_ci            extended = ' [SP <mail-parameters>]'
4737db96d56Sopenharmony_ci            lc_arg = arg.upper()
4747db96d56Sopenharmony_ci            if lc_arg == 'EHLO':
4757db96d56Sopenharmony_ci                self.push('250 Syntax: EHLO hostname')
4767db96d56Sopenharmony_ci            elif lc_arg == 'HELO':
4777db96d56Sopenharmony_ci                self.push('250 Syntax: HELO hostname')
4787db96d56Sopenharmony_ci            elif lc_arg == 'MAIL':
4797db96d56Sopenharmony_ci                msg = '250 Syntax: MAIL FROM: <address>'
4807db96d56Sopenharmony_ci                if self.extended_smtp:
4817db96d56Sopenharmony_ci                    msg += extended
4827db96d56Sopenharmony_ci                self.push(msg)
4837db96d56Sopenharmony_ci            elif lc_arg == 'RCPT':
4847db96d56Sopenharmony_ci                msg = '250 Syntax: RCPT TO: <address>'
4857db96d56Sopenharmony_ci                if self.extended_smtp:
4867db96d56Sopenharmony_ci                    msg += extended
4877db96d56Sopenharmony_ci                self.push(msg)
4887db96d56Sopenharmony_ci            elif lc_arg == 'DATA':
4897db96d56Sopenharmony_ci                self.push('250 Syntax: DATA')
4907db96d56Sopenharmony_ci            elif lc_arg == 'RSET':
4917db96d56Sopenharmony_ci                self.push('250 Syntax: RSET')
4927db96d56Sopenharmony_ci            elif lc_arg == 'NOOP':
4937db96d56Sopenharmony_ci                self.push('250 Syntax: NOOP')
4947db96d56Sopenharmony_ci            elif lc_arg == 'QUIT':
4957db96d56Sopenharmony_ci                self.push('250 Syntax: QUIT')
4967db96d56Sopenharmony_ci            elif lc_arg == 'VRFY':
4977db96d56Sopenharmony_ci                self.push('250 Syntax: VRFY <address>')
4987db96d56Sopenharmony_ci            else:
4997db96d56Sopenharmony_ci                self.push('501 Supported commands: EHLO HELO MAIL RCPT '
5007db96d56Sopenharmony_ci                          'DATA RSET NOOP QUIT VRFY')
5017db96d56Sopenharmony_ci        else:
5027db96d56Sopenharmony_ci            self.push('250 Supported commands: EHLO HELO MAIL RCPT DATA '
5037db96d56Sopenharmony_ci                      'RSET NOOP QUIT VRFY')
5047db96d56Sopenharmony_ci
5057db96d56Sopenharmony_ci    def smtp_VRFY(self, arg):
5067db96d56Sopenharmony_ci        if arg:
5077db96d56Sopenharmony_ci            address, params = self._getaddr(arg)
5087db96d56Sopenharmony_ci            if address:
5097db96d56Sopenharmony_ci                self.push('252 Cannot VRFY user, but will accept message '
5107db96d56Sopenharmony_ci                          'and attempt delivery')
5117db96d56Sopenharmony_ci            else:
5127db96d56Sopenharmony_ci                self.push('502 Could not VRFY %s' % arg)
5137db96d56Sopenharmony_ci        else:
5147db96d56Sopenharmony_ci            self.push('501 Syntax: VRFY <address>')
5157db96d56Sopenharmony_ci
5167db96d56Sopenharmony_ci    def smtp_MAIL(self, arg):
5177db96d56Sopenharmony_ci        if not self.seen_greeting:
5187db96d56Sopenharmony_ci            self.push('503 Error: send HELO first')
5197db96d56Sopenharmony_ci            return
5207db96d56Sopenharmony_ci        print('===> MAIL', arg, file=DEBUGSTREAM)
5217db96d56Sopenharmony_ci        syntaxerr = '501 Syntax: MAIL FROM: <address>'
5227db96d56Sopenharmony_ci        if self.extended_smtp:
5237db96d56Sopenharmony_ci            syntaxerr += ' [SP <mail-parameters>]'
5247db96d56Sopenharmony_ci        if arg is None:
5257db96d56Sopenharmony_ci            self.push(syntaxerr)
5267db96d56Sopenharmony_ci            return
5277db96d56Sopenharmony_ci        arg = self._strip_command_keyword('FROM:', arg)
5287db96d56Sopenharmony_ci        address, params = self._getaddr(arg)
5297db96d56Sopenharmony_ci        if not address:
5307db96d56Sopenharmony_ci            self.push(syntaxerr)
5317db96d56Sopenharmony_ci            return
5327db96d56Sopenharmony_ci        if not self.extended_smtp and params:
5337db96d56Sopenharmony_ci            self.push(syntaxerr)
5347db96d56Sopenharmony_ci            return
5357db96d56Sopenharmony_ci        if self.mailfrom:
5367db96d56Sopenharmony_ci            self.push('503 Error: nested MAIL command')
5377db96d56Sopenharmony_ci            return
5387db96d56Sopenharmony_ci        self.mail_options = params.upper().split()
5397db96d56Sopenharmony_ci        params = self._getparams(self.mail_options)
5407db96d56Sopenharmony_ci        if params is None:
5417db96d56Sopenharmony_ci            self.push(syntaxerr)
5427db96d56Sopenharmony_ci            return
5437db96d56Sopenharmony_ci        if not self._decode_data:
5447db96d56Sopenharmony_ci            body = params.pop('BODY', '7BIT')
5457db96d56Sopenharmony_ci            if body not in ['7BIT', '8BITMIME']:
5467db96d56Sopenharmony_ci                self.push('501 Error: BODY can only be one of 7BIT, 8BITMIME')
5477db96d56Sopenharmony_ci                return
5487db96d56Sopenharmony_ci        if self.enable_SMTPUTF8:
5497db96d56Sopenharmony_ci            smtputf8 = params.pop('SMTPUTF8', False)
5507db96d56Sopenharmony_ci            if smtputf8 is True:
5517db96d56Sopenharmony_ci                self.require_SMTPUTF8 = True
5527db96d56Sopenharmony_ci            elif smtputf8 is not False:
5537db96d56Sopenharmony_ci                self.push('501 Error: SMTPUTF8 takes no arguments')
5547db96d56Sopenharmony_ci                return
5557db96d56Sopenharmony_ci        size = params.pop('SIZE', None)
5567db96d56Sopenharmony_ci        if size:
5577db96d56Sopenharmony_ci            if not size.isdigit():
5587db96d56Sopenharmony_ci                self.push(syntaxerr)
5597db96d56Sopenharmony_ci                return
5607db96d56Sopenharmony_ci            elif self.data_size_limit and int(size) > self.data_size_limit:
5617db96d56Sopenharmony_ci                self.push('552 Error: message size exceeds fixed maximum message size')
5627db96d56Sopenharmony_ci                return
5637db96d56Sopenharmony_ci        if len(params.keys()) > 0:
5647db96d56Sopenharmony_ci            self.push('555 MAIL FROM parameters not recognized or not implemented')
5657db96d56Sopenharmony_ci            return
5667db96d56Sopenharmony_ci        self.mailfrom = address
5677db96d56Sopenharmony_ci        print('sender:', self.mailfrom, file=DEBUGSTREAM)
5687db96d56Sopenharmony_ci        self.push('250 OK')
5697db96d56Sopenharmony_ci
5707db96d56Sopenharmony_ci    def smtp_RCPT(self, arg):
5717db96d56Sopenharmony_ci        if not self.seen_greeting:
5727db96d56Sopenharmony_ci            self.push('503 Error: send HELO first');
5737db96d56Sopenharmony_ci            return
5747db96d56Sopenharmony_ci        print('===> RCPT', arg, file=DEBUGSTREAM)
5757db96d56Sopenharmony_ci        if not self.mailfrom:
5767db96d56Sopenharmony_ci            self.push('503 Error: need MAIL command')
5777db96d56Sopenharmony_ci            return
5787db96d56Sopenharmony_ci        syntaxerr = '501 Syntax: RCPT TO: <address>'
5797db96d56Sopenharmony_ci        if self.extended_smtp:
5807db96d56Sopenharmony_ci            syntaxerr += ' [SP <mail-parameters>]'
5817db96d56Sopenharmony_ci        if arg is None:
5827db96d56Sopenharmony_ci            self.push(syntaxerr)
5837db96d56Sopenharmony_ci            return
5847db96d56Sopenharmony_ci        arg = self._strip_command_keyword('TO:', arg)
5857db96d56Sopenharmony_ci        address, params = self._getaddr(arg)
5867db96d56Sopenharmony_ci        if not address:
5877db96d56Sopenharmony_ci            self.push(syntaxerr)
5887db96d56Sopenharmony_ci            return
5897db96d56Sopenharmony_ci        if not self.extended_smtp and params:
5907db96d56Sopenharmony_ci            self.push(syntaxerr)
5917db96d56Sopenharmony_ci            return
5927db96d56Sopenharmony_ci        self.rcpt_options = params.upper().split()
5937db96d56Sopenharmony_ci        params = self._getparams(self.rcpt_options)
5947db96d56Sopenharmony_ci        if params is None:
5957db96d56Sopenharmony_ci            self.push(syntaxerr)
5967db96d56Sopenharmony_ci            return
5977db96d56Sopenharmony_ci        # XXX currently there are no options we recognize.
5987db96d56Sopenharmony_ci        if len(params.keys()) > 0:
5997db96d56Sopenharmony_ci            self.push('555 RCPT TO parameters not recognized or not implemented')
6007db96d56Sopenharmony_ci            return
6017db96d56Sopenharmony_ci        self.rcpttos.append(address)
6027db96d56Sopenharmony_ci        print('recips:', self.rcpttos, file=DEBUGSTREAM)
6037db96d56Sopenharmony_ci        self.push('250 OK')
6047db96d56Sopenharmony_ci
6057db96d56Sopenharmony_ci    def smtp_RSET(self, arg):
6067db96d56Sopenharmony_ci        if arg:
6077db96d56Sopenharmony_ci            self.push('501 Syntax: RSET')
6087db96d56Sopenharmony_ci            return
6097db96d56Sopenharmony_ci        self._set_rset_state()
6107db96d56Sopenharmony_ci        self.push('250 OK')
6117db96d56Sopenharmony_ci
6127db96d56Sopenharmony_ci    def smtp_DATA(self, arg):
6137db96d56Sopenharmony_ci        if not self.seen_greeting:
6147db96d56Sopenharmony_ci            self.push('503 Error: send HELO first');
6157db96d56Sopenharmony_ci            return
6167db96d56Sopenharmony_ci        if not self.rcpttos:
6177db96d56Sopenharmony_ci            self.push('503 Error: need RCPT command')
6187db96d56Sopenharmony_ci            return
6197db96d56Sopenharmony_ci        if arg:
6207db96d56Sopenharmony_ci            self.push('501 Syntax: DATA')
6217db96d56Sopenharmony_ci            return
6227db96d56Sopenharmony_ci        self.smtp_state = self.DATA
6237db96d56Sopenharmony_ci        self.set_terminator(b'\r\n.\r\n')
6247db96d56Sopenharmony_ci        self.push('354 End data with <CR><LF>.<CR><LF>')
6257db96d56Sopenharmony_ci
6267db96d56Sopenharmony_ci    # Commands that have not been implemented
6277db96d56Sopenharmony_ci    def smtp_EXPN(self, arg):
6287db96d56Sopenharmony_ci        self.push('502 EXPN not implemented')
6297db96d56Sopenharmony_ci
6307db96d56Sopenharmony_ci
6317db96d56Sopenharmony_ciclass SMTPServer(asyncore.dispatcher):
6327db96d56Sopenharmony_ci    # SMTPChannel class to use for managing client connections
6337db96d56Sopenharmony_ci    channel_class = SMTPChannel
6347db96d56Sopenharmony_ci
6357db96d56Sopenharmony_ci    def __init__(self, localaddr, remoteaddr,
6367db96d56Sopenharmony_ci                 data_size_limit=DATA_SIZE_DEFAULT, map=None,
6377db96d56Sopenharmony_ci                 enable_SMTPUTF8=False, decode_data=False):
6387db96d56Sopenharmony_ci        self._localaddr = localaddr
6397db96d56Sopenharmony_ci        self._remoteaddr = remoteaddr
6407db96d56Sopenharmony_ci        self.data_size_limit = data_size_limit
6417db96d56Sopenharmony_ci        self.enable_SMTPUTF8 = enable_SMTPUTF8
6427db96d56Sopenharmony_ci        self._decode_data = decode_data
6437db96d56Sopenharmony_ci        if enable_SMTPUTF8 and decode_data:
6447db96d56Sopenharmony_ci            raise ValueError("decode_data and enable_SMTPUTF8 cannot"
6457db96d56Sopenharmony_ci                             " be set to True at the same time")
6467db96d56Sopenharmony_ci        asyncore.dispatcher.__init__(self, map=map)
6477db96d56Sopenharmony_ci        try:
6487db96d56Sopenharmony_ci            gai_results = socket.getaddrinfo(*localaddr,
6497db96d56Sopenharmony_ci                                             type=socket.SOCK_STREAM)
6507db96d56Sopenharmony_ci            self.create_socket(gai_results[0][0], gai_results[0][1])
6517db96d56Sopenharmony_ci            # try to re-use a server port if possible
6527db96d56Sopenharmony_ci            self.set_reuse_addr()
6537db96d56Sopenharmony_ci            self.bind(localaddr)
6547db96d56Sopenharmony_ci            self.listen(5)
6557db96d56Sopenharmony_ci        except:
6567db96d56Sopenharmony_ci            self.close()
6577db96d56Sopenharmony_ci            raise
6587db96d56Sopenharmony_ci        else:
6597db96d56Sopenharmony_ci            print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
6607db96d56Sopenharmony_ci                self.__class__.__name__, time.ctime(time.time()),
6617db96d56Sopenharmony_ci                localaddr, remoteaddr), file=DEBUGSTREAM)
6627db96d56Sopenharmony_ci
6637db96d56Sopenharmony_ci    def handle_accepted(self, conn, addr):
6647db96d56Sopenharmony_ci        print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
6657db96d56Sopenharmony_ci        channel = self.channel_class(self,
6667db96d56Sopenharmony_ci                                     conn,
6677db96d56Sopenharmony_ci                                     addr,
6687db96d56Sopenharmony_ci                                     self.data_size_limit,
6697db96d56Sopenharmony_ci                                     self._map,
6707db96d56Sopenharmony_ci                                     self.enable_SMTPUTF8,
6717db96d56Sopenharmony_ci                                     self._decode_data)
6727db96d56Sopenharmony_ci
6737db96d56Sopenharmony_ci    # API for "doing something useful with the message"
6747db96d56Sopenharmony_ci    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
6757db96d56Sopenharmony_ci        """Override this abstract method to handle messages from the client.
6767db96d56Sopenharmony_ci
6777db96d56Sopenharmony_ci        peer is a tuple containing (ipaddr, port) of the client that made the
6787db96d56Sopenharmony_ci        socket connection to our smtp port.
6797db96d56Sopenharmony_ci
6807db96d56Sopenharmony_ci        mailfrom is the raw address the client claims the message is coming
6817db96d56Sopenharmony_ci        from.
6827db96d56Sopenharmony_ci
6837db96d56Sopenharmony_ci        rcpttos is a list of raw addresses the client wishes to deliver the
6847db96d56Sopenharmony_ci        message to.
6857db96d56Sopenharmony_ci
6867db96d56Sopenharmony_ci        data is a string containing the entire full text of the message,
6877db96d56Sopenharmony_ci        headers (if supplied) and all.  It has been `de-transparencied'
6887db96d56Sopenharmony_ci        according to RFC 821, Section 4.5.2.  In other words, a line
6897db96d56Sopenharmony_ci        containing a `.' followed by other text has had the leading dot
6907db96d56Sopenharmony_ci        removed.
6917db96d56Sopenharmony_ci
6927db96d56Sopenharmony_ci        kwargs is a dictionary containing additional information.  It is
6937db96d56Sopenharmony_ci        empty if decode_data=True was given as init parameter, otherwise
6947db96d56Sopenharmony_ci        it will contain the following keys:
6957db96d56Sopenharmony_ci            'mail_options': list of parameters to the mail command.  All
6967db96d56Sopenharmony_ci                            elements are uppercase strings.  Example:
6977db96d56Sopenharmony_ci                            ['BODY=8BITMIME', 'SMTPUTF8'].
6987db96d56Sopenharmony_ci            'rcpt_options': same, for the rcpt command.
6997db96d56Sopenharmony_ci
7007db96d56Sopenharmony_ci        This function should return None for a normal `250 Ok' response;
7017db96d56Sopenharmony_ci        otherwise, it should return the desired response string in RFC 821
7027db96d56Sopenharmony_ci        format.
7037db96d56Sopenharmony_ci
7047db96d56Sopenharmony_ci        """
7057db96d56Sopenharmony_ci        raise NotImplementedError
7067db96d56Sopenharmony_ci
7077db96d56Sopenharmony_ci
7087db96d56Sopenharmony_ciclass DebuggingServer(SMTPServer):
7097db96d56Sopenharmony_ci
7107db96d56Sopenharmony_ci    def _print_message_content(self, peer, data):
7117db96d56Sopenharmony_ci        inheaders = 1
7127db96d56Sopenharmony_ci        lines = data.splitlines()
7137db96d56Sopenharmony_ci        for line in lines:
7147db96d56Sopenharmony_ci            # headers first
7157db96d56Sopenharmony_ci            if inheaders and not line:
7167db96d56Sopenharmony_ci                peerheader = 'X-Peer: ' + peer[0]
7177db96d56Sopenharmony_ci                if not isinstance(data, str):
7187db96d56Sopenharmony_ci                    # decoded_data=false; make header match other binary output
7197db96d56Sopenharmony_ci                    peerheader = repr(peerheader.encode('utf-8'))
7207db96d56Sopenharmony_ci                print(peerheader)
7217db96d56Sopenharmony_ci                inheaders = 0
7227db96d56Sopenharmony_ci            if not isinstance(data, str):
7237db96d56Sopenharmony_ci                # Avoid spurious 'str on bytes instance' warning.
7247db96d56Sopenharmony_ci                line = repr(line)
7257db96d56Sopenharmony_ci            print(line)
7267db96d56Sopenharmony_ci
7277db96d56Sopenharmony_ci    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
7287db96d56Sopenharmony_ci        print('---------- MESSAGE FOLLOWS ----------')
7297db96d56Sopenharmony_ci        if kwargs:
7307db96d56Sopenharmony_ci            if kwargs.get('mail_options'):
7317db96d56Sopenharmony_ci                print('mail options: %s' % kwargs['mail_options'])
7327db96d56Sopenharmony_ci            if kwargs.get('rcpt_options'):
7337db96d56Sopenharmony_ci                print('rcpt options: %s\n' % kwargs['rcpt_options'])
7347db96d56Sopenharmony_ci        self._print_message_content(peer, data)
7357db96d56Sopenharmony_ci        print('------------ END MESSAGE ------------')
7367db96d56Sopenharmony_ci
7377db96d56Sopenharmony_ci
7387db96d56Sopenharmony_ciclass PureProxy(SMTPServer):
7397db96d56Sopenharmony_ci    def __init__(self, *args, **kwargs):
7407db96d56Sopenharmony_ci        if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:
7417db96d56Sopenharmony_ci            raise ValueError("PureProxy does not support SMTPUTF8.")
7427db96d56Sopenharmony_ci        super(PureProxy, self).__init__(*args, **kwargs)
7437db96d56Sopenharmony_ci
7447db96d56Sopenharmony_ci    def process_message(self, peer, mailfrom, rcpttos, data):
7457db96d56Sopenharmony_ci        lines = data.split('\n')
7467db96d56Sopenharmony_ci        # Look for the last header
7477db96d56Sopenharmony_ci        i = 0
7487db96d56Sopenharmony_ci        for line in lines:
7497db96d56Sopenharmony_ci            if not line:
7507db96d56Sopenharmony_ci                break
7517db96d56Sopenharmony_ci            i += 1
7527db96d56Sopenharmony_ci        lines.insert(i, 'X-Peer: %s' % peer[0])
7537db96d56Sopenharmony_ci        data = NEWLINE.join(lines)
7547db96d56Sopenharmony_ci        refused = self._deliver(mailfrom, rcpttos, data)
7557db96d56Sopenharmony_ci        # TBD: what to do with refused addresses?
7567db96d56Sopenharmony_ci        print('we got some refusals:', refused, file=DEBUGSTREAM)
7577db96d56Sopenharmony_ci
7587db96d56Sopenharmony_ci    def _deliver(self, mailfrom, rcpttos, data):
7597db96d56Sopenharmony_ci        import smtplib
7607db96d56Sopenharmony_ci        refused = {}
7617db96d56Sopenharmony_ci        try:
7627db96d56Sopenharmony_ci            s = smtplib.SMTP()
7637db96d56Sopenharmony_ci            s.connect(self._remoteaddr[0], self._remoteaddr[1])
7647db96d56Sopenharmony_ci            try:
7657db96d56Sopenharmony_ci                refused = s.sendmail(mailfrom, rcpttos, data)
7667db96d56Sopenharmony_ci            finally:
7677db96d56Sopenharmony_ci                s.quit()
7687db96d56Sopenharmony_ci        except smtplib.SMTPRecipientsRefused as e:
7697db96d56Sopenharmony_ci            print('got SMTPRecipientsRefused', file=DEBUGSTREAM)
7707db96d56Sopenharmony_ci            refused = e.recipients
7717db96d56Sopenharmony_ci        except (OSError, smtplib.SMTPException) as e:
7727db96d56Sopenharmony_ci            print('got', e.__class__, file=DEBUGSTREAM)
7737db96d56Sopenharmony_ci            # All recipients were refused.  If the exception had an associated
7747db96d56Sopenharmony_ci            # error code, use it.  Otherwise,fake it with a non-triggering
7757db96d56Sopenharmony_ci            # exception code.
7767db96d56Sopenharmony_ci            errcode = getattr(e, 'smtp_code', -1)
7777db96d56Sopenharmony_ci            errmsg = getattr(e, 'smtp_error', 'ignore')
7787db96d56Sopenharmony_ci            for r in rcpttos:
7797db96d56Sopenharmony_ci                refused[r] = (errcode, errmsg)
7807db96d56Sopenharmony_ci        return refused
7817db96d56Sopenharmony_ci
7827db96d56Sopenharmony_ci
7837db96d56Sopenharmony_ciclass Options:
7847db96d56Sopenharmony_ci    setuid = True
7857db96d56Sopenharmony_ci    classname = 'PureProxy'
7867db96d56Sopenharmony_ci    size_limit = None
7877db96d56Sopenharmony_ci    enable_SMTPUTF8 = False
7887db96d56Sopenharmony_ci
7897db96d56Sopenharmony_ci
7907db96d56Sopenharmony_cidef parseargs():
7917db96d56Sopenharmony_ci    global DEBUGSTREAM
7927db96d56Sopenharmony_ci    try:
7937db96d56Sopenharmony_ci        opts, args = getopt.getopt(
7947db96d56Sopenharmony_ci            sys.argv[1:], 'nVhc:s:du',
7957db96d56Sopenharmony_ci            ['class=', 'nosetuid', 'version', 'help', 'size=', 'debug',
7967db96d56Sopenharmony_ci             'smtputf8'])
7977db96d56Sopenharmony_ci    except getopt.error as e:
7987db96d56Sopenharmony_ci        usage(1, e)
7997db96d56Sopenharmony_ci
8007db96d56Sopenharmony_ci    options = Options()
8017db96d56Sopenharmony_ci    for opt, arg in opts:
8027db96d56Sopenharmony_ci        if opt in ('-h', '--help'):
8037db96d56Sopenharmony_ci            usage(0)
8047db96d56Sopenharmony_ci        elif opt in ('-V', '--version'):
8057db96d56Sopenharmony_ci            print(__version__)
8067db96d56Sopenharmony_ci            sys.exit(0)
8077db96d56Sopenharmony_ci        elif opt in ('-n', '--nosetuid'):
8087db96d56Sopenharmony_ci            options.setuid = False
8097db96d56Sopenharmony_ci        elif opt in ('-c', '--class'):
8107db96d56Sopenharmony_ci            options.classname = arg
8117db96d56Sopenharmony_ci        elif opt in ('-d', '--debug'):
8127db96d56Sopenharmony_ci            DEBUGSTREAM = sys.stderr
8137db96d56Sopenharmony_ci        elif opt in ('-u', '--smtputf8'):
8147db96d56Sopenharmony_ci            options.enable_SMTPUTF8 = True
8157db96d56Sopenharmony_ci        elif opt in ('-s', '--size'):
8167db96d56Sopenharmony_ci            try:
8177db96d56Sopenharmony_ci                int_size = int(arg)
8187db96d56Sopenharmony_ci                options.size_limit = int_size
8197db96d56Sopenharmony_ci            except:
8207db96d56Sopenharmony_ci                print('Invalid size: ' + arg, file=sys.stderr)
8217db96d56Sopenharmony_ci                sys.exit(1)
8227db96d56Sopenharmony_ci
8237db96d56Sopenharmony_ci    # parse the rest of the arguments
8247db96d56Sopenharmony_ci    if len(args) < 1:
8257db96d56Sopenharmony_ci        localspec = 'localhost:8025'
8267db96d56Sopenharmony_ci        remotespec = 'localhost:25'
8277db96d56Sopenharmony_ci    elif len(args) < 2:
8287db96d56Sopenharmony_ci        localspec = args[0]
8297db96d56Sopenharmony_ci        remotespec = 'localhost:25'
8307db96d56Sopenharmony_ci    elif len(args) < 3:
8317db96d56Sopenharmony_ci        localspec = args[0]
8327db96d56Sopenharmony_ci        remotespec = args[1]
8337db96d56Sopenharmony_ci    else:
8347db96d56Sopenharmony_ci        usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
8357db96d56Sopenharmony_ci
8367db96d56Sopenharmony_ci    # split into host/port pairs
8377db96d56Sopenharmony_ci    i = localspec.find(':')
8387db96d56Sopenharmony_ci    if i < 0:
8397db96d56Sopenharmony_ci        usage(1, 'Bad local spec: %s' % localspec)
8407db96d56Sopenharmony_ci    options.localhost = localspec[:i]
8417db96d56Sopenharmony_ci    try:
8427db96d56Sopenharmony_ci        options.localport = int(localspec[i+1:])
8437db96d56Sopenharmony_ci    except ValueError:
8447db96d56Sopenharmony_ci        usage(1, 'Bad local port: %s' % localspec)
8457db96d56Sopenharmony_ci    i = remotespec.find(':')
8467db96d56Sopenharmony_ci    if i < 0:
8477db96d56Sopenharmony_ci        usage(1, 'Bad remote spec: %s' % remotespec)
8487db96d56Sopenharmony_ci    options.remotehost = remotespec[:i]
8497db96d56Sopenharmony_ci    try:
8507db96d56Sopenharmony_ci        options.remoteport = int(remotespec[i+1:])
8517db96d56Sopenharmony_ci    except ValueError:
8527db96d56Sopenharmony_ci        usage(1, 'Bad remote port: %s' % remotespec)
8537db96d56Sopenharmony_ci    return options
8547db96d56Sopenharmony_ci
8557db96d56Sopenharmony_ci
8567db96d56Sopenharmony_ciif __name__ == '__main__':
8577db96d56Sopenharmony_ci    options = parseargs()
8587db96d56Sopenharmony_ci    # Become nobody
8597db96d56Sopenharmony_ci    classname = options.classname
8607db96d56Sopenharmony_ci    if "." in classname:
8617db96d56Sopenharmony_ci        lastdot = classname.rfind(".")
8627db96d56Sopenharmony_ci        mod = __import__(classname[:lastdot], globals(), locals(), [""])
8637db96d56Sopenharmony_ci        classname = classname[lastdot+1:]
8647db96d56Sopenharmony_ci    else:
8657db96d56Sopenharmony_ci        import __main__ as mod
8667db96d56Sopenharmony_ci    class_ = getattr(mod, classname)
8677db96d56Sopenharmony_ci    proxy = class_((options.localhost, options.localport),
8687db96d56Sopenharmony_ci                   (options.remotehost, options.remoteport),
8697db96d56Sopenharmony_ci                   options.size_limit, enable_SMTPUTF8=options.enable_SMTPUTF8)
8707db96d56Sopenharmony_ci    if options.setuid:
8717db96d56Sopenharmony_ci        try:
8727db96d56Sopenharmony_ci            import pwd
8737db96d56Sopenharmony_ci        except ImportError:
8747db96d56Sopenharmony_ci            print('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)
8757db96d56Sopenharmony_ci            sys.exit(1)
8767db96d56Sopenharmony_ci        nobody = pwd.getpwnam('nobody')[2]
8777db96d56Sopenharmony_ci        try:
8787db96d56Sopenharmony_ci            os.setuid(nobody)
8797db96d56Sopenharmony_ci        except PermissionError:
8807db96d56Sopenharmony_ci            print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)
8817db96d56Sopenharmony_ci            sys.exit(1)
8827db96d56Sopenharmony_ci    try:
8837db96d56Sopenharmony_ci        asyncore.loop()
8847db96d56Sopenharmony_ci    except KeyboardInterrupt:
8857db96d56Sopenharmony_ci        pass
886