xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/smtpd.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker#! /usr/bin/env python3
2*cda5da8dSAndroid Build Coastguard Worker"""An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.
3*cda5da8dSAndroid Build Coastguard Worker
4*cda5da8dSAndroid Build Coastguard WorkerUsage: %(program)s [options] [localhost:localport [remotehost:remoteport]]
5*cda5da8dSAndroid Build Coastguard Worker
6*cda5da8dSAndroid Build Coastguard WorkerOptions:
7*cda5da8dSAndroid Build Coastguard Worker
8*cda5da8dSAndroid Build Coastguard Worker    --nosetuid
9*cda5da8dSAndroid Build Coastguard Worker    -n
10*cda5da8dSAndroid Build Coastguard Worker        This program generally tries to setuid `nobody', unless this flag is
11*cda5da8dSAndroid Build Coastguard Worker        set.  The setuid call will fail if this program is not run as root (in
12*cda5da8dSAndroid Build Coastguard Worker        which case, use this flag).
13*cda5da8dSAndroid Build Coastguard Worker
14*cda5da8dSAndroid Build Coastguard Worker    --version
15*cda5da8dSAndroid Build Coastguard Worker    -V
16*cda5da8dSAndroid Build Coastguard Worker        Print the version number and exit.
17*cda5da8dSAndroid Build Coastguard Worker
18*cda5da8dSAndroid Build Coastguard Worker    --class classname
19*cda5da8dSAndroid Build Coastguard Worker    -c classname
20*cda5da8dSAndroid Build Coastguard Worker        Use `classname' as the concrete SMTP proxy class.  Uses `PureProxy' by
21*cda5da8dSAndroid Build Coastguard Worker        default.
22*cda5da8dSAndroid Build Coastguard Worker
23*cda5da8dSAndroid Build Coastguard Worker    --size limit
24*cda5da8dSAndroid Build Coastguard Worker    -s limit
25*cda5da8dSAndroid Build Coastguard Worker        Restrict the total size of the incoming message to "limit" number of
26*cda5da8dSAndroid Build Coastguard Worker        bytes via the RFC 1870 SIZE extension.  Defaults to 33554432 bytes.
27*cda5da8dSAndroid Build Coastguard Worker
28*cda5da8dSAndroid Build Coastguard Worker    --smtputf8
29*cda5da8dSAndroid Build Coastguard Worker    -u
30*cda5da8dSAndroid Build Coastguard Worker        Enable the SMTPUTF8 extension and behave as an RFC 6531 smtp proxy.
31*cda5da8dSAndroid Build Coastguard Worker
32*cda5da8dSAndroid Build Coastguard Worker    --debug
33*cda5da8dSAndroid Build Coastguard Worker    -d
34*cda5da8dSAndroid Build Coastguard Worker        Turn on debugging prints.
35*cda5da8dSAndroid Build Coastguard Worker
36*cda5da8dSAndroid Build Coastguard Worker    --help
37*cda5da8dSAndroid Build Coastguard Worker    -h
38*cda5da8dSAndroid Build Coastguard Worker        Print this message and exit.
39*cda5da8dSAndroid Build Coastguard Worker
40*cda5da8dSAndroid Build Coastguard WorkerVersion: %(__version__)s
41*cda5da8dSAndroid Build Coastguard Worker
42*cda5da8dSAndroid Build Coastguard WorkerIf localhost is not given then `localhost' is used, and if localport is not
43*cda5da8dSAndroid Build Coastguard Workergiven then 8025 is used.  If remotehost is not given then `localhost' is used,
44*cda5da8dSAndroid Build Coastguard Workerand if remoteport is not given, then 25 is used.
45*cda5da8dSAndroid Build Coastguard Worker"""
46*cda5da8dSAndroid Build Coastguard Worker
47*cda5da8dSAndroid Build Coastguard Worker# Overview:
48*cda5da8dSAndroid Build Coastguard Worker#
49*cda5da8dSAndroid Build Coastguard Worker# This file implements the minimal SMTP protocol as defined in RFC 5321.  It
50*cda5da8dSAndroid Build Coastguard Worker# has a hierarchy of classes which implement the backend functionality for the
51*cda5da8dSAndroid Build Coastguard Worker# smtpd.  A number of classes are provided:
52*cda5da8dSAndroid Build Coastguard Worker#
53*cda5da8dSAndroid Build Coastguard Worker#   SMTPServer - the base class for the backend.  Raises NotImplementedError
54*cda5da8dSAndroid Build Coastguard Worker#   if you try to use it.
55*cda5da8dSAndroid Build Coastguard Worker#
56*cda5da8dSAndroid Build Coastguard Worker#   DebuggingServer - simply prints each message it receives on stdout.
57*cda5da8dSAndroid Build Coastguard Worker#
58*cda5da8dSAndroid Build Coastguard Worker#   PureProxy - Proxies all messages to a real smtpd which does final
59*cda5da8dSAndroid Build Coastguard Worker#   delivery.  One known problem with this class is that it doesn't handle
60*cda5da8dSAndroid Build Coastguard Worker#   SMTP errors from the backend server at all.  This should be fixed
61*cda5da8dSAndroid Build Coastguard Worker#   (contributions are welcome!).
62*cda5da8dSAndroid Build Coastguard Worker#
63*cda5da8dSAndroid Build Coastguard Worker#
64*cda5da8dSAndroid Build Coastguard Worker# Author: Barry Warsaw <[email protected]>
65*cda5da8dSAndroid Build Coastguard Worker#
66*cda5da8dSAndroid Build Coastguard Worker# TODO:
67*cda5da8dSAndroid Build Coastguard Worker#
68*cda5da8dSAndroid Build Coastguard Worker# - support mailbox delivery
69*cda5da8dSAndroid Build Coastguard Worker# - alias files
70*cda5da8dSAndroid Build Coastguard Worker# - Handle more ESMTP extensions
71*cda5da8dSAndroid Build Coastguard Worker# - handle error codes from the backend smtpd
72*cda5da8dSAndroid Build Coastguard Worker
73*cda5da8dSAndroid Build Coastguard Workerimport sys
74*cda5da8dSAndroid Build Coastguard Workerimport os
75*cda5da8dSAndroid Build Coastguard Workerimport errno
76*cda5da8dSAndroid Build Coastguard Workerimport getopt
77*cda5da8dSAndroid Build Coastguard Workerimport time
78*cda5da8dSAndroid Build Coastguard Workerimport socket
79*cda5da8dSAndroid Build Coastguard Workerimport collections
80*cda5da8dSAndroid Build Coastguard Workerfrom warnings import _deprecated, warn
81*cda5da8dSAndroid Build Coastguard Workerfrom email._header_value_parser import get_addr_spec, get_angle_addr
82*cda5da8dSAndroid Build Coastguard Worker
83*cda5da8dSAndroid Build Coastguard Worker__all__ = [
84*cda5da8dSAndroid Build Coastguard Worker    "SMTPChannel", "SMTPServer", "DebuggingServer", "PureProxy",
85*cda5da8dSAndroid Build Coastguard Worker]
86*cda5da8dSAndroid Build Coastguard Worker
87*cda5da8dSAndroid Build Coastguard Worker_DEPRECATION_MSG = ('The {name} module is deprecated and unmaintained and will '
88*cda5da8dSAndroid Build Coastguard Worker                    'be removed in Python {remove}.  Please see aiosmtpd '
89*cda5da8dSAndroid Build Coastguard Worker                    '(https://aiosmtpd.readthedocs.io/) for the recommended '
90*cda5da8dSAndroid Build Coastguard Worker                    'replacement.')
91*cda5da8dSAndroid Build Coastguard Worker_deprecated(__name__, _DEPRECATION_MSG, remove=(3, 12))
92*cda5da8dSAndroid Build Coastguard Worker
93*cda5da8dSAndroid Build Coastguard Worker
94*cda5da8dSAndroid Build Coastguard Worker# These are imported after the above warning so that users get the correct
95*cda5da8dSAndroid Build Coastguard Worker# deprecation warning.
96*cda5da8dSAndroid Build Coastguard Workerimport asyncore
97*cda5da8dSAndroid Build Coastguard Workerimport asynchat
98*cda5da8dSAndroid Build Coastguard Worker
99*cda5da8dSAndroid Build Coastguard Worker
100*cda5da8dSAndroid Build Coastguard Workerprogram = sys.argv[0]
101*cda5da8dSAndroid Build Coastguard Worker__version__ = 'Python SMTP proxy version 0.3'
102*cda5da8dSAndroid Build Coastguard Worker
103*cda5da8dSAndroid Build Coastguard Worker
104*cda5da8dSAndroid Build Coastguard Workerclass Devnull:
105*cda5da8dSAndroid Build Coastguard Worker    def write(self, msg): pass
106*cda5da8dSAndroid Build Coastguard Worker    def flush(self): pass
107*cda5da8dSAndroid Build Coastguard Worker
108*cda5da8dSAndroid Build Coastguard Worker
109*cda5da8dSAndroid Build Coastguard WorkerDEBUGSTREAM = Devnull()
110*cda5da8dSAndroid Build Coastguard WorkerNEWLINE = '\n'
111*cda5da8dSAndroid Build Coastguard WorkerCOMMASPACE = ', '
112*cda5da8dSAndroid Build Coastguard WorkerDATA_SIZE_DEFAULT = 33554432
113*cda5da8dSAndroid Build Coastguard Worker
114*cda5da8dSAndroid Build Coastguard Worker
115*cda5da8dSAndroid Build Coastguard Workerdef usage(code, msg=''):
116*cda5da8dSAndroid Build Coastguard Worker    print(__doc__ % globals(), file=sys.stderr)
117*cda5da8dSAndroid Build Coastguard Worker    if msg:
118*cda5da8dSAndroid Build Coastguard Worker        print(msg, file=sys.stderr)
119*cda5da8dSAndroid Build Coastguard Worker    sys.exit(code)
120*cda5da8dSAndroid Build Coastguard Worker
121*cda5da8dSAndroid Build Coastguard Worker
122*cda5da8dSAndroid Build Coastguard Workerclass SMTPChannel(asynchat.async_chat):
123*cda5da8dSAndroid Build Coastguard Worker    COMMAND = 0
124*cda5da8dSAndroid Build Coastguard Worker    DATA = 1
125*cda5da8dSAndroid Build Coastguard Worker
126*cda5da8dSAndroid Build Coastguard Worker    command_size_limit = 512
127*cda5da8dSAndroid Build Coastguard Worker    command_size_limits = collections.defaultdict(lambda x=command_size_limit: x)
128*cda5da8dSAndroid Build Coastguard Worker
129*cda5da8dSAndroid Build Coastguard Worker    @property
130*cda5da8dSAndroid Build Coastguard Worker    def max_command_size_limit(self):
131*cda5da8dSAndroid Build Coastguard Worker        try:
132*cda5da8dSAndroid Build Coastguard Worker            return max(self.command_size_limits.values())
133*cda5da8dSAndroid Build Coastguard Worker        except ValueError:
134*cda5da8dSAndroid Build Coastguard Worker            return self.command_size_limit
135*cda5da8dSAndroid Build Coastguard Worker
136*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
137*cda5da8dSAndroid Build Coastguard Worker                 map=None, enable_SMTPUTF8=False, decode_data=False):
138*cda5da8dSAndroid Build Coastguard Worker        asynchat.async_chat.__init__(self, conn, map=map)
139*cda5da8dSAndroid Build Coastguard Worker        self.smtp_server = server
140*cda5da8dSAndroid Build Coastguard Worker        self.conn = conn
141*cda5da8dSAndroid Build Coastguard Worker        self.addr = addr
142*cda5da8dSAndroid Build Coastguard Worker        self.data_size_limit = data_size_limit
143*cda5da8dSAndroid Build Coastguard Worker        self.enable_SMTPUTF8 = enable_SMTPUTF8
144*cda5da8dSAndroid Build Coastguard Worker        self._decode_data = decode_data
145*cda5da8dSAndroid Build Coastguard Worker        if enable_SMTPUTF8 and decode_data:
146*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("decode_data and enable_SMTPUTF8 cannot"
147*cda5da8dSAndroid Build Coastguard Worker                             " be set to True at the same time")
148*cda5da8dSAndroid Build Coastguard Worker        if decode_data:
149*cda5da8dSAndroid Build Coastguard Worker            self._emptystring = ''
150*cda5da8dSAndroid Build Coastguard Worker            self._linesep = '\r\n'
151*cda5da8dSAndroid Build Coastguard Worker            self._dotsep = '.'
152*cda5da8dSAndroid Build Coastguard Worker            self._newline = NEWLINE
153*cda5da8dSAndroid Build Coastguard Worker        else:
154*cda5da8dSAndroid Build Coastguard Worker            self._emptystring = b''
155*cda5da8dSAndroid Build Coastguard Worker            self._linesep = b'\r\n'
156*cda5da8dSAndroid Build Coastguard Worker            self._dotsep = ord(b'.')
157*cda5da8dSAndroid Build Coastguard Worker            self._newline = b'\n'
158*cda5da8dSAndroid Build Coastguard Worker        self._set_rset_state()
159*cda5da8dSAndroid Build Coastguard Worker        self.seen_greeting = ''
160*cda5da8dSAndroid Build Coastguard Worker        self.extended_smtp = False
161*cda5da8dSAndroid Build Coastguard Worker        self.command_size_limits.clear()
162*cda5da8dSAndroid Build Coastguard Worker        self.fqdn = socket.getfqdn()
163*cda5da8dSAndroid Build Coastguard Worker        try:
164*cda5da8dSAndroid Build Coastguard Worker            self.peer = conn.getpeername()
165*cda5da8dSAndroid Build Coastguard Worker        except OSError as err:
166*cda5da8dSAndroid Build Coastguard Worker            # a race condition  may occur if the other end is closing
167*cda5da8dSAndroid Build Coastguard Worker            # before we can get the peername
168*cda5da8dSAndroid Build Coastguard Worker            self.close()
169*cda5da8dSAndroid Build Coastguard Worker            if err.errno != errno.ENOTCONN:
170*cda5da8dSAndroid Build Coastguard Worker                raise
171*cda5da8dSAndroid Build Coastguard Worker            return
172*cda5da8dSAndroid Build Coastguard Worker        print('Peer:', repr(self.peer), file=DEBUGSTREAM)
173*cda5da8dSAndroid Build Coastguard Worker        self.push('220 %s %s' % (self.fqdn, __version__))
174*cda5da8dSAndroid Build Coastguard Worker
175*cda5da8dSAndroid Build Coastguard Worker    def _set_post_data_state(self):
176*cda5da8dSAndroid Build Coastguard Worker        """Reset state variables to their post-DATA state."""
177*cda5da8dSAndroid Build Coastguard Worker        self.smtp_state = self.COMMAND
178*cda5da8dSAndroid Build Coastguard Worker        self.mailfrom = None
179*cda5da8dSAndroid Build Coastguard Worker        self.rcpttos = []
180*cda5da8dSAndroid Build Coastguard Worker        self.require_SMTPUTF8 = False
181*cda5da8dSAndroid Build Coastguard Worker        self.num_bytes = 0
182*cda5da8dSAndroid Build Coastguard Worker        self.set_terminator(b'\r\n')
183*cda5da8dSAndroid Build Coastguard Worker
184*cda5da8dSAndroid Build Coastguard Worker    def _set_rset_state(self):
185*cda5da8dSAndroid Build Coastguard Worker        """Reset all state variables except the greeting."""
186*cda5da8dSAndroid Build Coastguard Worker        self._set_post_data_state()
187*cda5da8dSAndroid Build Coastguard Worker        self.received_data = ''
188*cda5da8dSAndroid Build Coastguard Worker        self.received_lines = []
189*cda5da8dSAndroid Build Coastguard Worker
190*cda5da8dSAndroid Build Coastguard Worker
191*cda5da8dSAndroid Build Coastguard Worker    # properties for backwards-compatibility
192*cda5da8dSAndroid Build Coastguard Worker    @property
193*cda5da8dSAndroid Build Coastguard Worker    def __server(self):
194*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __server attribute on SMTPChannel is deprecated, "
195*cda5da8dSAndroid Build Coastguard Worker            "use 'smtp_server' instead", DeprecationWarning, 2)
196*cda5da8dSAndroid Build Coastguard Worker        return self.smtp_server
197*cda5da8dSAndroid Build Coastguard Worker    @__server.setter
198*cda5da8dSAndroid Build Coastguard Worker    def __server(self, value):
199*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __server attribute on SMTPChannel is deprecated, "
200*cda5da8dSAndroid Build Coastguard Worker            "set 'smtp_server' instead", DeprecationWarning, 2)
201*cda5da8dSAndroid Build Coastguard Worker        self.smtp_server = value
202*cda5da8dSAndroid Build Coastguard Worker
203*cda5da8dSAndroid Build Coastguard Worker    @property
204*cda5da8dSAndroid Build Coastguard Worker    def __line(self):
205*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __line attribute on SMTPChannel is deprecated, "
206*cda5da8dSAndroid Build Coastguard Worker            "use 'received_lines' instead", DeprecationWarning, 2)
207*cda5da8dSAndroid Build Coastguard Worker        return self.received_lines
208*cda5da8dSAndroid Build Coastguard Worker    @__line.setter
209*cda5da8dSAndroid Build Coastguard Worker    def __line(self, value):
210*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __line attribute on SMTPChannel is deprecated, "
211*cda5da8dSAndroid Build Coastguard Worker            "set 'received_lines' instead", DeprecationWarning, 2)
212*cda5da8dSAndroid Build Coastguard Worker        self.received_lines = value
213*cda5da8dSAndroid Build Coastguard Worker
214*cda5da8dSAndroid Build Coastguard Worker    @property
215*cda5da8dSAndroid Build Coastguard Worker    def __state(self):
216*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __state attribute on SMTPChannel is deprecated, "
217*cda5da8dSAndroid Build Coastguard Worker            "use 'smtp_state' instead", DeprecationWarning, 2)
218*cda5da8dSAndroid Build Coastguard Worker        return self.smtp_state
219*cda5da8dSAndroid Build Coastguard Worker    @__state.setter
220*cda5da8dSAndroid Build Coastguard Worker    def __state(self, value):
221*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __state attribute on SMTPChannel is deprecated, "
222*cda5da8dSAndroid Build Coastguard Worker            "set 'smtp_state' instead", DeprecationWarning, 2)
223*cda5da8dSAndroid Build Coastguard Worker        self.smtp_state = value
224*cda5da8dSAndroid Build Coastguard Worker
225*cda5da8dSAndroid Build Coastguard Worker    @property
226*cda5da8dSAndroid Build Coastguard Worker    def __greeting(self):
227*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __greeting attribute on SMTPChannel is deprecated, "
228*cda5da8dSAndroid Build Coastguard Worker            "use 'seen_greeting' instead", DeprecationWarning, 2)
229*cda5da8dSAndroid Build Coastguard Worker        return self.seen_greeting
230*cda5da8dSAndroid Build Coastguard Worker    @__greeting.setter
231*cda5da8dSAndroid Build Coastguard Worker    def __greeting(self, value):
232*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __greeting attribute on SMTPChannel is deprecated, "
233*cda5da8dSAndroid Build Coastguard Worker            "set 'seen_greeting' instead", DeprecationWarning, 2)
234*cda5da8dSAndroid Build Coastguard Worker        self.seen_greeting = value
235*cda5da8dSAndroid Build Coastguard Worker
236*cda5da8dSAndroid Build Coastguard Worker    @property
237*cda5da8dSAndroid Build Coastguard Worker    def __mailfrom(self):
238*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __mailfrom attribute on SMTPChannel is deprecated, "
239*cda5da8dSAndroid Build Coastguard Worker            "use 'mailfrom' instead", DeprecationWarning, 2)
240*cda5da8dSAndroid Build Coastguard Worker        return self.mailfrom
241*cda5da8dSAndroid Build Coastguard Worker    @__mailfrom.setter
242*cda5da8dSAndroid Build Coastguard Worker    def __mailfrom(self, value):
243*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __mailfrom attribute on SMTPChannel is deprecated, "
244*cda5da8dSAndroid Build Coastguard Worker            "set 'mailfrom' instead", DeprecationWarning, 2)
245*cda5da8dSAndroid Build Coastguard Worker        self.mailfrom = value
246*cda5da8dSAndroid Build Coastguard Worker
247*cda5da8dSAndroid Build Coastguard Worker    @property
248*cda5da8dSAndroid Build Coastguard Worker    def __rcpttos(self):
249*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __rcpttos attribute on SMTPChannel is deprecated, "
250*cda5da8dSAndroid Build Coastguard Worker            "use 'rcpttos' instead", DeprecationWarning, 2)
251*cda5da8dSAndroid Build Coastguard Worker        return self.rcpttos
252*cda5da8dSAndroid Build Coastguard Worker    @__rcpttos.setter
253*cda5da8dSAndroid Build Coastguard Worker    def __rcpttos(self, value):
254*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __rcpttos attribute on SMTPChannel is deprecated, "
255*cda5da8dSAndroid Build Coastguard Worker            "set 'rcpttos' instead", DeprecationWarning, 2)
256*cda5da8dSAndroid Build Coastguard Worker        self.rcpttos = value
257*cda5da8dSAndroid Build Coastguard Worker
258*cda5da8dSAndroid Build Coastguard Worker    @property
259*cda5da8dSAndroid Build Coastguard Worker    def __data(self):
260*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __data attribute on SMTPChannel is deprecated, "
261*cda5da8dSAndroid Build Coastguard Worker            "use 'received_data' instead", DeprecationWarning, 2)
262*cda5da8dSAndroid Build Coastguard Worker        return self.received_data
263*cda5da8dSAndroid Build Coastguard Worker    @__data.setter
264*cda5da8dSAndroid Build Coastguard Worker    def __data(self, value):
265*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __data attribute on SMTPChannel is deprecated, "
266*cda5da8dSAndroid Build Coastguard Worker            "set 'received_data' instead", DeprecationWarning, 2)
267*cda5da8dSAndroid Build Coastguard Worker        self.received_data = value
268*cda5da8dSAndroid Build Coastguard Worker
269*cda5da8dSAndroid Build Coastguard Worker    @property
270*cda5da8dSAndroid Build Coastguard Worker    def __fqdn(self):
271*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __fqdn attribute on SMTPChannel is deprecated, "
272*cda5da8dSAndroid Build Coastguard Worker            "use 'fqdn' instead", DeprecationWarning, 2)
273*cda5da8dSAndroid Build Coastguard Worker        return self.fqdn
274*cda5da8dSAndroid Build Coastguard Worker    @__fqdn.setter
275*cda5da8dSAndroid Build Coastguard Worker    def __fqdn(self, value):
276*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __fqdn attribute on SMTPChannel is deprecated, "
277*cda5da8dSAndroid Build Coastguard Worker            "set 'fqdn' instead", DeprecationWarning, 2)
278*cda5da8dSAndroid Build Coastguard Worker        self.fqdn = value
279*cda5da8dSAndroid Build Coastguard Worker
280*cda5da8dSAndroid Build Coastguard Worker    @property
281*cda5da8dSAndroid Build Coastguard Worker    def __peer(self):
282*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __peer attribute on SMTPChannel is deprecated, "
283*cda5da8dSAndroid Build Coastguard Worker            "use 'peer' instead", DeprecationWarning, 2)
284*cda5da8dSAndroid Build Coastguard Worker        return self.peer
285*cda5da8dSAndroid Build Coastguard Worker    @__peer.setter
286*cda5da8dSAndroid Build Coastguard Worker    def __peer(self, value):
287*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __peer attribute on SMTPChannel is deprecated, "
288*cda5da8dSAndroid Build Coastguard Worker            "set 'peer' instead", DeprecationWarning, 2)
289*cda5da8dSAndroid Build Coastguard Worker        self.peer = value
290*cda5da8dSAndroid Build Coastguard Worker
291*cda5da8dSAndroid Build Coastguard Worker    @property
292*cda5da8dSAndroid Build Coastguard Worker    def __conn(self):
293*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __conn attribute on SMTPChannel is deprecated, "
294*cda5da8dSAndroid Build Coastguard Worker            "use 'conn' instead", DeprecationWarning, 2)
295*cda5da8dSAndroid Build Coastguard Worker        return self.conn
296*cda5da8dSAndroid Build Coastguard Worker    @__conn.setter
297*cda5da8dSAndroid Build Coastguard Worker    def __conn(self, value):
298*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __conn attribute on SMTPChannel is deprecated, "
299*cda5da8dSAndroid Build Coastguard Worker            "set 'conn' instead", DeprecationWarning, 2)
300*cda5da8dSAndroid Build Coastguard Worker        self.conn = value
301*cda5da8dSAndroid Build Coastguard Worker
302*cda5da8dSAndroid Build Coastguard Worker    @property
303*cda5da8dSAndroid Build Coastguard Worker    def __addr(self):
304*cda5da8dSAndroid Build Coastguard Worker        warn("Access to __addr attribute on SMTPChannel is deprecated, "
305*cda5da8dSAndroid Build Coastguard Worker            "use 'addr' instead", DeprecationWarning, 2)
306*cda5da8dSAndroid Build Coastguard Worker        return self.addr
307*cda5da8dSAndroid Build Coastguard Worker    @__addr.setter
308*cda5da8dSAndroid Build Coastguard Worker    def __addr(self, value):
309*cda5da8dSAndroid Build Coastguard Worker        warn("Setting __addr attribute on SMTPChannel is deprecated, "
310*cda5da8dSAndroid Build Coastguard Worker            "set 'addr' instead", DeprecationWarning, 2)
311*cda5da8dSAndroid Build Coastguard Worker        self.addr = value
312*cda5da8dSAndroid Build Coastguard Worker
313*cda5da8dSAndroid Build Coastguard Worker    # Overrides base class for convenience.
314*cda5da8dSAndroid Build Coastguard Worker    def push(self, msg):
315*cda5da8dSAndroid Build Coastguard Worker        asynchat.async_chat.push(self, bytes(
316*cda5da8dSAndroid Build Coastguard Worker            msg + '\r\n', 'utf-8' if self.require_SMTPUTF8 else 'ascii'))
317*cda5da8dSAndroid Build Coastguard Worker
318*cda5da8dSAndroid Build Coastguard Worker    # Implementation of base class abstract method
319*cda5da8dSAndroid Build Coastguard Worker    def collect_incoming_data(self, data):
320*cda5da8dSAndroid Build Coastguard Worker        limit = None
321*cda5da8dSAndroid Build Coastguard Worker        if self.smtp_state == self.COMMAND:
322*cda5da8dSAndroid Build Coastguard Worker            limit = self.max_command_size_limit
323*cda5da8dSAndroid Build Coastguard Worker        elif self.smtp_state == self.DATA:
324*cda5da8dSAndroid Build Coastguard Worker            limit = self.data_size_limit
325*cda5da8dSAndroid Build Coastguard Worker        if limit and self.num_bytes > limit:
326*cda5da8dSAndroid Build Coastguard Worker            return
327*cda5da8dSAndroid Build Coastguard Worker        elif limit:
328*cda5da8dSAndroid Build Coastguard Worker            self.num_bytes += len(data)
329*cda5da8dSAndroid Build Coastguard Worker        if self._decode_data:
330*cda5da8dSAndroid Build Coastguard Worker            self.received_lines.append(str(data, 'utf-8'))
331*cda5da8dSAndroid Build Coastguard Worker        else:
332*cda5da8dSAndroid Build Coastguard Worker            self.received_lines.append(data)
333*cda5da8dSAndroid Build Coastguard Worker
334*cda5da8dSAndroid Build Coastguard Worker    # Implementation of base class abstract method
335*cda5da8dSAndroid Build Coastguard Worker    def found_terminator(self):
336*cda5da8dSAndroid Build Coastguard Worker        line = self._emptystring.join(self.received_lines)
337*cda5da8dSAndroid Build Coastguard Worker        print('Data:', repr(line), file=DEBUGSTREAM)
338*cda5da8dSAndroid Build Coastguard Worker        self.received_lines = []
339*cda5da8dSAndroid Build Coastguard Worker        if self.smtp_state == self.COMMAND:
340*cda5da8dSAndroid Build Coastguard Worker            sz, self.num_bytes = self.num_bytes, 0
341*cda5da8dSAndroid Build Coastguard Worker            if not line:
342*cda5da8dSAndroid Build Coastguard Worker                self.push('500 Error: bad syntax')
343*cda5da8dSAndroid Build Coastguard Worker                return
344*cda5da8dSAndroid Build Coastguard Worker            if not self._decode_data:
345*cda5da8dSAndroid Build Coastguard Worker                line = str(line, 'utf-8')
346*cda5da8dSAndroid Build Coastguard Worker            i = line.find(' ')
347*cda5da8dSAndroid Build Coastguard Worker            if i < 0:
348*cda5da8dSAndroid Build Coastguard Worker                command = line.upper()
349*cda5da8dSAndroid Build Coastguard Worker                arg = None
350*cda5da8dSAndroid Build Coastguard Worker            else:
351*cda5da8dSAndroid Build Coastguard Worker                command = line[:i].upper()
352*cda5da8dSAndroid Build Coastguard Worker                arg = line[i+1:].strip()
353*cda5da8dSAndroid Build Coastguard Worker            max_sz = (self.command_size_limits[command]
354*cda5da8dSAndroid Build Coastguard Worker                        if self.extended_smtp else self.command_size_limit)
355*cda5da8dSAndroid Build Coastguard Worker            if sz > max_sz:
356*cda5da8dSAndroid Build Coastguard Worker                self.push('500 Error: line too long')
357*cda5da8dSAndroid Build Coastguard Worker                return
358*cda5da8dSAndroid Build Coastguard Worker            method = getattr(self, 'smtp_' + command, None)
359*cda5da8dSAndroid Build Coastguard Worker            if not method:
360*cda5da8dSAndroid Build Coastguard Worker                self.push('500 Error: command "%s" not recognized' % command)
361*cda5da8dSAndroid Build Coastguard Worker                return
362*cda5da8dSAndroid Build Coastguard Worker            method(arg)
363*cda5da8dSAndroid Build Coastguard Worker            return
364*cda5da8dSAndroid Build Coastguard Worker        else:
365*cda5da8dSAndroid Build Coastguard Worker            if self.smtp_state != self.DATA:
366*cda5da8dSAndroid Build Coastguard Worker                self.push('451 Internal confusion')
367*cda5da8dSAndroid Build Coastguard Worker                self.num_bytes = 0
368*cda5da8dSAndroid Build Coastguard Worker                return
369*cda5da8dSAndroid Build Coastguard Worker            if self.data_size_limit and self.num_bytes > self.data_size_limit:
370*cda5da8dSAndroid Build Coastguard Worker                self.push('552 Error: Too much mail data')
371*cda5da8dSAndroid Build Coastguard Worker                self.num_bytes = 0
372*cda5da8dSAndroid Build Coastguard Worker                return
373*cda5da8dSAndroid Build Coastguard Worker            # Remove extraneous carriage returns and de-transparency according
374*cda5da8dSAndroid Build Coastguard Worker            # to RFC 5321, Section 4.5.2.
375*cda5da8dSAndroid Build Coastguard Worker            data = []
376*cda5da8dSAndroid Build Coastguard Worker            for text in line.split(self._linesep):
377*cda5da8dSAndroid Build Coastguard Worker                if text and text[0] == self._dotsep:
378*cda5da8dSAndroid Build Coastguard Worker                    data.append(text[1:])
379*cda5da8dSAndroid Build Coastguard Worker                else:
380*cda5da8dSAndroid Build Coastguard Worker                    data.append(text)
381*cda5da8dSAndroid Build Coastguard Worker            self.received_data = self._newline.join(data)
382*cda5da8dSAndroid Build Coastguard Worker            args = (self.peer, self.mailfrom, self.rcpttos, self.received_data)
383*cda5da8dSAndroid Build Coastguard Worker            kwargs = {}
384*cda5da8dSAndroid Build Coastguard Worker            if not self._decode_data:
385*cda5da8dSAndroid Build Coastguard Worker                kwargs = {
386*cda5da8dSAndroid Build Coastguard Worker                    'mail_options': self.mail_options,
387*cda5da8dSAndroid Build Coastguard Worker                    'rcpt_options': self.rcpt_options,
388*cda5da8dSAndroid Build Coastguard Worker                }
389*cda5da8dSAndroid Build Coastguard Worker            status = self.smtp_server.process_message(*args, **kwargs)
390*cda5da8dSAndroid Build Coastguard Worker            self._set_post_data_state()
391*cda5da8dSAndroid Build Coastguard Worker            if not status:
392*cda5da8dSAndroid Build Coastguard Worker                self.push('250 OK')
393*cda5da8dSAndroid Build Coastguard Worker            else:
394*cda5da8dSAndroid Build Coastguard Worker                self.push(status)
395*cda5da8dSAndroid Build Coastguard Worker
396*cda5da8dSAndroid Build Coastguard Worker    # SMTP and ESMTP commands
397*cda5da8dSAndroid Build Coastguard Worker    def smtp_HELO(self, arg):
398*cda5da8dSAndroid Build Coastguard Worker        if not arg:
399*cda5da8dSAndroid Build Coastguard Worker            self.push('501 Syntax: HELO hostname')
400*cda5da8dSAndroid Build Coastguard Worker            return
401*cda5da8dSAndroid Build Coastguard Worker        # See issue #21783 for a discussion of this behavior.
402*cda5da8dSAndroid Build Coastguard Worker        if self.seen_greeting:
403*cda5da8dSAndroid Build Coastguard Worker            self.push('503 Duplicate HELO/EHLO')
404*cda5da8dSAndroid Build Coastguard Worker            return
405*cda5da8dSAndroid Build Coastguard Worker        self._set_rset_state()
406*cda5da8dSAndroid Build Coastguard Worker        self.seen_greeting = arg
407*cda5da8dSAndroid Build Coastguard Worker        self.push('250 %s' % self.fqdn)
408*cda5da8dSAndroid Build Coastguard Worker
409*cda5da8dSAndroid Build Coastguard Worker    def smtp_EHLO(self, arg):
410*cda5da8dSAndroid Build Coastguard Worker        if not arg:
411*cda5da8dSAndroid Build Coastguard Worker            self.push('501 Syntax: EHLO hostname')
412*cda5da8dSAndroid Build Coastguard Worker            return
413*cda5da8dSAndroid Build Coastguard Worker        # See issue #21783 for a discussion of this behavior.
414*cda5da8dSAndroid Build Coastguard Worker        if self.seen_greeting:
415*cda5da8dSAndroid Build Coastguard Worker            self.push('503 Duplicate HELO/EHLO')
416*cda5da8dSAndroid Build Coastguard Worker            return
417*cda5da8dSAndroid Build Coastguard Worker        self._set_rset_state()
418*cda5da8dSAndroid Build Coastguard Worker        self.seen_greeting = arg
419*cda5da8dSAndroid Build Coastguard Worker        self.extended_smtp = True
420*cda5da8dSAndroid Build Coastguard Worker        self.push('250-%s' % self.fqdn)
421*cda5da8dSAndroid Build Coastguard Worker        if self.data_size_limit:
422*cda5da8dSAndroid Build Coastguard Worker            self.push('250-SIZE %s' % self.data_size_limit)
423*cda5da8dSAndroid Build Coastguard Worker            self.command_size_limits['MAIL'] += 26
424*cda5da8dSAndroid Build Coastguard Worker        if not self._decode_data:
425*cda5da8dSAndroid Build Coastguard Worker            self.push('250-8BITMIME')
426*cda5da8dSAndroid Build Coastguard Worker        if self.enable_SMTPUTF8:
427*cda5da8dSAndroid Build Coastguard Worker            self.push('250-SMTPUTF8')
428*cda5da8dSAndroid Build Coastguard Worker            self.command_size_limits['MAIL'] += 10
429*cda5da8dSAndroid Build Coastguard Worker        self.push('250 HELP')
430*cda5da8dSAndroid Build Coastguard Worker
431*cda5da8dSAndroid Build Coastguard Worker    def smtp_NOOP(self, arg):
432*cda5da8dSAndroid Build Coastguard Worker        if arg:
433*cda5da8dSAndroid Build Coastguard Worker            self.push('501 Syntax: NOOP')
434*cda5da8dSAndroid Build Coastguard Worker        else:
435*cda5da8dSAndroid Build Coastguard Worker            self.push('250 OK')
436*cda5da8dSAndroid Build Coastguard Worker
437*cda5da8dSAndroid Build Coastguard Worker    def smtp_QUIT(self, arg):
438*cda5da8dSAndroid Build Coastguard Worker        # args is ignored
439*cda5da8dSAndroid Build Coastguard Worker        self.push('221 Bye')
440*cda5da8dSAndroid Build Coastguard Worker        self.close_when_done()
441*cda5da8dSAndroid Build Coastguard Worker
442*cda5da8dSAndroid Build Coastguard Worker    def _strip_command_keyword(self, keyword, arg):
443*cda5da8dSAndroid Build Coastguard Worker        keylen = len(keyword)
444*cda5da8dSAndroid Build Coastguard Worker        if arg[:keylen].upper() == keyword:
445*cda5da8dSAndroid Build Coastguard Worker            return arg[keylen:].strip()
446*cda5da8dSAndroid Build Coastguard Worker        return ''
447*cda5da8dSAndroid Build Coastguard Worker
448*cda5da8dSAndroid Build Coastguard Worker    def _getaddr(self, arg):
449*cda5da8dSAndroid Build Coastguard Worker        if not arg:
450*cda5da8dSAndroid Build Coastguard Worker            return '', ''
451*cda5da8dSAndroid Build Coastguard Worker        if arg.lstrip().startswith('<'):
452*cda5da8dSAndroid Build Coastguard Worker            address, rest = get_angle_addr(arg)
453*cda5da8dSAndroid Build Coastguard Worker        else:
454*cda5da8dSAndroid Build Coastguard Worker            address, rest = get_addr_spec(arg)
455*cda5da8dSAndroid Build Coastguard Worker        if not address:
456*cda5da8dSAndroid Build Coastguard Worker            return address, rest
457*cda5da8dSAndroid Build Coastguard Worker        return address.addr_spec, rest
458*cda5da8dSAndroid Build Coastguard Worker
459*cda5da8dSAndroid Build Coastguard Worker    def _getparams(self, params):
460*cda5da8dSAndroid Build Coastguard Worker        # Return params as dictionary. Return None if not all parameters
461*cda5da8dSAndroid Build Coastguard Worker        # appear to be syntactically valid according to RFC 1869.
462*cda5da8dSAndroid Build Coastguard Worker        result = {}
463*cda5da8dSAndroid Build Coastguard Worker        for param in params:
464*cda5da8dSAndroid Build Coastguard Worker            param, eq, value = param.partition('=')
465*cda5da8dSAndroid Build Coastguard Worker            if not param.isalnum() or eq and not value:
466*cda5da8dSAndroid Build Coastguard Worker                return None
467*cda5da8dSAndroid Build Coastguard Worker            result[param] = value if eq else True
468*cda5da8dSAndroid Build Coastguard Worker        return result
469*cda5da8dSAndroid Build Coastguard Worker
470*cda5da8dSAndroid Build Coastguard Worker    def smtp_HELP(self, arg):
471*cda5da8dSAndroid Build Coastguard Worker        if arg:
472*cda5da8dSAndroid Build Coastguard Worker            extended = ' [SP <mail-parameters>]'
473*cda5da8dSAndroid Build Coastguard Worker            lc_arg = arg.upper()
474*cda5da8dSAndroid Build Coastguard Worker            if lc_arg == 'EHLO':
475*cda5da8dSAndroid Build Coastguard Worker                self.push('250 Syntax: EHLO hostname')
476*cda5da8dSAndroid Build Coastguard Worker            elif lc_arg == 'HELO':
477*cda5da8dSAndroid Build Coastguard Worker                self.push('250 Syntax: HELO hostname')
478*cda5da8dSAndroid Build Coastguard Worker            elif lc_arg == 'MAIL':
479*cda5da8dSAndroid Build Coastguard Worker                msg = '250 Syntax: MAIL FROM: <address>'
480*cda5da8dSAndroid Build Coastguard Worker                if self.extended_smtp:
481*cda5da8dSAndroid Build Coastguard Worker                    msg += extended
482*cda5da8dSAndroid Build Coastguard Worker                self.push(msg)
483*cda5da8dSAndroid Build Coastguard Worker            elif lc_arg == 'RCPT':
484*cda5da8dSAndroid Build Coastguard Worker                msg = '250 Syntax: RCPT TO: <address>'
485*cda5da8dSAndroid Build Coastguard Worker                if self.extended_smtp:
486*cda5da8dSAndroid Build Coastguard Worker                    msg += extended
487*cda5da8dSAndroid Build Coastguard Worker                self.push(msg)
488*cda5da8dSAndroid Build Coastguard Worker            elif lc_arg == 'DATA':
489*cda5da8dSAndroid Build Coastguard Worker                self.push('250 Syntax: DATA')
490*cda5da8dSAndroid Build Coastguard Worker            elif lc_arg == 'RSET':
491*cda5da8dSAndroid Build Coastguard Worker                self.push('250 Syntax: RSET')
492*cda5da8dSAndroid Build Coastguard Worker            elif lc_arg == 'NOOP':
493*cda5da8dSAndroid Build Coastguard Worker                self.push('250 Syntax: NOOP')
494*cda5da8dSAndroid Build Coastguard Worker            elif lc_arg == 'QUIT':
495*cda5da8dSAndroid Build Coastguard Worker                self.push('250 Syntax: QUIT')
496*cda5da8dSAndroid Build Coastguard Worker            elif lc_arg == 'VRFY':
497*cda5da8dSAndroid Build Coastguard Worker                self.push('250 Syntax: VRFY <address>')
498*cda5da8dSAndroid Build Coastguard Worker            else:
499*cda5da8dSAndroid Build Coastguard Worker                self.push('501 Supported commands: EHLO HELO MAIL RCPT '
500*cda5da8dSAndroid Build Coastguard Worker                          'DATA RSET NOOP QUIT VRFY')
501*cda5da8dSAndroid Build Coastguard Worker        else:
502*cda5da8dSAndroid Build Coastguard Worker            self.push('250 Supported commands: EHLO HELO MAIL RCPT DATA '
503*cda5da8dSAndroid Build Coastguard Worker                      'RSET NOOP QUIT VRFY')
504*cda5da8dSAndroid Build Coastguard Worker
505*cda5da8dSAndroid Build Coastguard Worker    def smtp_VRFY(self, arg):
506*cda5da8dSAndroid Build Coastguard Worker        if arg:
507*cda5da8dSAndroid Build Coastguard Worker            address, params = self._getaddr(arg)
508*cda5da8dSAndroid Build Coastguard Worker            if address:
509*cda5da8dSAndroid Build Coastguard Worker                self.push('252 Cannot VRFY user, but will accept message '
510*cda5da8dSAndroid Build Coastguard Worker                          'and attempt delivery')
511*cda5da8dSAndroid Build Coastguard Worker            else:
512*cda5da8dSAndroid Build Coastguard Worker                self.push('502 Could not VRFY %s' % arg)
513*cda5da8dSAndroid Build Coastguard Worker        else:
514*cda5da8dSAndroid Build Coastguard Worker            self.push('501 Syntax: VRFY <address>')
515*cda5da8dSAndroid Build Coastguard Worker
516*cda5da8dSAndroid Build Coastguard Worker    def smtp_MAIL(self, arg):
517*cda5da8dSAndroid Build Coastguard Worker        if not self.seen_greeting:
518*cda5da8dSAndroid Build Coastguard Worker            self.push('503 Error: send HELO first')
519*cda5da8dSAndroid Build Coastguard Worker            return
520*cda5da8dSAndroid Build Coastguard Worker        print('===> MAIL', arg, file=DEBUGSTREAM)
521*cda5da8dSAndroid Build Coastguard Worker        syntaxerr = '501 Syntax: MAIL FROM: <address>'
522*cda5da8dSAndroid Build Coastguard Worker        if self.extended_smtp:
523*cda5da8dSAndroid Build Coastguard Worker            syntaxerr += ' [SP <mail-parameters>]'
524*cda5da8dSAndroid Build Coastguard Worker        if arg is None:
525*cda5da8dSAndroid Build Coastguard Worker            self.push(syntaxerr)
526*cda5da8dSAndroid Build Coastguard Worker            return
527*cda5da8dSAndroid Build Coastguard Worker        arg = self._strip_command_keyword('FROM:', arg)
528*cda5da8dSAndroid Build Coastguard Worker        address, params = self._getaddr(arg)
529*cda5da8dSAndroid Build Coastguard Worker        if not address:
530*cda5da8dSAndroid Build Coastguard Worker            self.push(syntaxerr)
531*cda5da8dSAndroid Build Coastguard Worker            return
532*cda5da8dSAndroid Build Coastguard Worker        if not self.extended_smtp and params:
533*cda5da8dSAndroid Build Coastguard Worker            self.push(syntaxerr)
534*cda5da8dSAndroid Build Coastguard Worker            return
535*cda5da8dSAndroid Build Coastguard Worker        if self.mailfrom:
536*cda5da8dSAndroid Build Coastguard Worker            self.push('503 Error: nested MAIL command')
537*cda5da8dSAndroid Build Coastguard Worker            return
538*cda5da8dSAndroid Build Coastguard Worker        self.mail_options = params.upper().split()
539*cda5da8dSAndroid Build Coastguard Worker        params = self._getparams(self.mail_options)
540*cda5da8dSAndroid Build Coastguard Worker        if params is None:
541*cda5da8dSAndroid Build Coastguard Worker            self.push(syntaxerr)
542*cda5da8dSAndroid Build Coastguard Worker            return
543*cda5da8dSAndroid Build Coastguard Worker        if not self._decode_data:
544*cda5da8dSAndroid Build Coastguard Worker            body = params.pop('BODY', '7BIT')
545*cda5da8dSAndroid Build Coastguard Worker            if body not in ['7BIT', '8BITMIME']:
546*cda5da8dSAndroid Build Coastguard Worker                self.push('501 Error: BODY can only be one of 7BIT, 8BITMIME')
547*cda5da8dSAndroid Build Coastguard Worker                return
548*cda5da8dSAndroid Build Coastguard Worker        if self.enable_SMTPUTF8:
549*cda5da8dSAndroid Build Coastguard Worker            smtputf8 = params.pop('SMTPUTF8', False)
550*cda5da8dSAndroid Build Coastguard Worker            if smtputf8 is True:
551*cda5da8dSAndroid Build Coastguard Worker                self.require_SMTPUTF8 = True
552*cda5da8dSAndroid Build Coastguard Worker            elif smtputf8 is not False:
553*cda5da8dSAndroid Build Coastguard Worker                self.push('501 Error: SMTPUTF8 takes no arguments')
554*cda5da8dSAndroid Build Coastguard Worker                return
555*cda5da8dSAndroid Build Coastguard Worker        size = params.pop('SIZE', None)
556*cda5da8dSAndroid Build Coastguard Worker        if size:
557*cda5da8dSAndroid Build Coastguard Worker            if not size.isdigit():
558*cda5da8dSAndroid Build Coastguard Worker                self.push(syntaxerr)
559*cda5da8dSAndroid Build Coastguard Worker                return
560*cda5da8dSAndroid Build Coastguard Worker            elif self.data_size_limit and int(size) > self.data_size_limit:
561*cda5da8dSAndroid Build Coastguard Worker                self.push('552 Error: message size exceeds fixed maximum message size')
562*cda5da8dSAndroid Build Coastguard Worker                return
563*cda5da8dSAndroid Build Coastguard Worker        if len(params.keys()) > 0:
564*cda5da8dSAndroid Build Coastguard Worker            self.push('555 MAIL FROM parameters not recognized or not implemented')
565*cda5da8dSAndroid Build Coastguard Worker            return
566*cda5da8dSAndroid Build Coastguard Worker        self.mailfrom = address
567*cda5da8dSAndroid Build Coastguard Worker        print('sender:', self.mailfrom, file=DEBUGSTREAM)
568*cda5da8dSAndroid Build Coastguard Worker        self.push('250 OK')
569*cda5da8dSAndroid Build Coastguard Worker
570*cda5da8dSAndroid Build Coastguard Worker    def smtp_RCPT(self, arg):
571*cda5da8dSAndroid Build Coastguard Worker        if not self.seen_greeting:
572*cda5da8dSAndroid Build Coastguard Worker            self.push('503 Error: send HELO first');
573*cda5da8dSAndroid Build Coastguard Worker            return
574*cda5da8dSAndroid Build Coastguard Worker        print('===> RCPT', arg, file=DEBUGSTREAM)
575*cda5da8dSAndroid Build Coastguard Worker        if not self.mailfrom:
576*cda5da8dSAndroid Build Coastguard Worker            self.push('503 Error: need MAIL command')
577*cda5da8dSAndroid Build Coastguard Worker            return
578*cda5da8dSAndroid Build Coastguard Worker        syntaxerr = '501 Syntax: RCPT TO: <address>'
579*cda5da8dSAndroid Build Coastguard Worker        if self.extended_smtp:
580*cda5da8dSAndroid Build Coastguard Worker            syntaxerr += ' [SP <mail-parameters>]'
581*cda5da8dSAndroid Build Coastguard Worker        if arg is None:
582*cda5da8dSAndroid Build Coastguard Worker            self.push(syntaxerr)
583*cda5da8dSAndroid Build Coastguard Worker            return
584*cda5da8dSAndroid Build Coastguard Worker        arg = self._strip_command_keyword('TO:', arg)
585*cda5da8dSAndroid Build Coastguard Worker        address, params = self._getaddr(arg)
586*cda5da8dSAndroid Build Coastguard Worker        if not address:
587*cda5da8dSAndroid Build Coastguard Worker            self.push(syntaxerr)
588*cda5da8dSAndroid Build Coastguard Worker            return
589*cda5da8dSAndroid Build Coastguard Worker        if not self.extended_smtp and params:
590*cda5da8dSAndroid Build Coastguard Worker            self.push(syntaxerr)
591*cda5da8dSAndroid Build Coastguard Worker            return
592*cda5da8dSAndroid Build Coastguard Worker        self.rcpt_options = params.upper().split()
593*cda5da8dSAndroid Build Coastguard Worker        params = self._getparams(self.rcpt_options)
594*cda5da8dSAndroid Build Coastguard Worker        if params is None:
595*cda5da8dSAndroid Build Coastguard Worker            self.push(syntaxerr)
596*cda5da8dSAndroid Build Coastguard Worker            return
597*cda5da8dSAndroid Build Coastguard Worker        # XXX currently there are no options we recognize.
598*cda5da8dSAndroid Build Coastguard Worker        if len(params.keys()) > 0:
599*cda5da8dSAndroid Build Coastguard Worker            self.push('555 RCPT TO parameters not recognized or not implemented')
600*cda5da8dSAndroid Build Coastguard Worker            return
601*cda5da8dSAndroid Build Coastguard Worker        self.rcpttos.append(address)
602*cda5da8dSAndroid Build Coastguard Worker        print('recips:', self.rcpttos, file=DEBUGSTREAM)
603*cda5da8dSAndroid Build Coastguard Worker        self.push('250 OK')
604*cda5da8dSAndroid Build Coastguard Worker
605*cda5da8dSAndroid Build Coastguard Worker    def smtp_RSET(self, arg):
606*cda5da8dSAndroid Build Coastguard Worker        if arg:
607*cda5da8dSAndroid Build Coastguard Worker            self.push('501 Syntax: RSET')
608*cda5da8dSAndroid Build Coastguard Worker            return
609*cda5da8dSAndroid Build Coastguard Worker        self._set_rset_state()
610*cda5da8dSAndroid Build Coastguard Worker        self.push('250 OK')
611*cda5da8dSAndroid Build Coastguard Worker
612*cda5da8dSAndroid Build Coastguard Worker    def smtp_DATA(self, arg):
613*cda5da8dSAndroid Build Coastguard Worker        if not self.seen_greeting:
614*cda5da8dSAndroid Build Coastguard Worker            self.push('503 Error: send HELO first');
615*cda5da8dSAndroid Build Coastguard Worker            return
616*cda5da8dSAndroid Build Coastguard Worker        if not self.rcpttos:
617*cda5da8dSAndroid Build Coastguard Worker            self.push('503 Error: need RCPT command')
618*cda5da8dSAndroid Build Coastguard Worker            return
619*cda5da8dSAndroid Build Coastguard Worker        if arg:
620*cda5da8dSAndroid Build Coastguard Worker            self.push('501 Syntax: DATA')
621*cda5da8dSAndroid Build Coastguard Worker            return
622*cda5da8dSAndroid Build Coastguard Worker        self.smtp_state = self.DATA
623*cda5da8dSAndroid Build Coastguard Worker        self.set_terminator(b'\r\n.\r\n')
624*cda5da8dSAndroid Build Coastguard Worker        self.push('354 End data with <CR><LF>.<CR><LF>')
625*cda5da8dSAndroid Build Coastguard Worker
626*cda5da8dSAndroid Build Coastguard Worker    # Commands that have not been implemented
627*cda5da8dSAndroid Build Coastguard Worker    def smtp_EXPN(self, arg):
628*cda5da8dSAndroid Build Coastguard Worker        self.push('502 EXPN not implemented')
629*cda5da8dSAndroid Build Coastguard Worker
630*cda5da8dSAndroid Build Coastguard Worker
631*cda5da8dSAndroid Build Coastguard Workerclass SMTPServer(asyncore.dispatcher):
632*cda5da8dSAndroid Build Coastguard Worker    # SMTPChannel class to use for managing client connections
633*cda5da8dSAndroid Build Coastguard Worker    channel_class = SMTPChannel
634*cda5da8dSAndroid Build Coastguard Worker
635*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, localaddr, remoteaddr,
636*cda5da8dSAndroid Build Coastguard Worker                 data_size_limit=DATA_SIZE_DEFAULT, map=None,
637*cda5da8dSAndroid Build Coastguard Worker                 enable_SMTPUTF8=False, decode_data=False):
638*cda5da8dSAndroid Build Coastguard Worker        self._localaddr = localaddr
639*cda5da8dSAndroid Build Coastguard Worker        self._remoteaddr = remoteaddr
640*cda5da8dSAndroid Build Coastguard Worker        self.data_size_limit = data_size_limit
641*cda5da8dSAndroid Build Coastguard Worker        self.enable_SMTPUTF8 = enable_SMTPUTF8
642*cda5da8dSAndroid Build Coastguard Worker        self._decode_data = decode_data
643*cda5da8dSAndroid Build Coastguard Worker        if enable_SMTPUTF8 and decode_data:
644*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("decode_data and enable_SMTPUTF8 cannot"
645*cda5da8dSAndroid Build Coastguard Worker                             " be set to True at the same time")
646*cda5da8dSAndroid Build Coastguard Worker        asyncore.dispatcher.__init__(self, map=map)
647*cda5da8dSAndroid Build Coastguard Worker        try:
648*cda5da8dSAndroid Build Coastguard Worker            gai_results = socket.getaddrinfo(*localaddr,
649*cda5da8dSAndroid Build Coastguard Worker                                             type=socket.SOCK_STREAM)
650*cda5da8dSAndroid Build Coastguard Worker            self.create_socket(gai_results[0][0], gai_results[0][1])
651*cda5da8dSAndroid Build Coastguard Worker            # try to re-use a server port if possible
652*cda5da8dSAndroid Build Coastguard Worker            self.set_reuse_addr()
653*cda5da8dSAndroid Build Coastguard Worker            self.bind(localaddr)
654*cda5da8dSAndroid Build Coastguard Worker            self.listen(5)
655*cda5da8dSAndroid Build Coastguard Worker        except:
656*cda5da8dSAndroid Build Coastguard Worker            self.close()
657*cda5da8dSAndroid Build Coastguard Worker            raise
658*cda5da8dSAndroid Build Coastguard Worker        else:
659*cda5da8dSAndroid Build Coastguard Worker            print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
660*cda5da8dSAndroid Build Coastguard Worker                self.__class__.__name__, time.ctime(time.time()),
661*cda5da8dSAndroid Build Coastguard Worker                localaddr, remoteaddr), file=DEBUGSTREAM)
662*cda5da8dSAndroid Build Coastguard Worker
663*cda5da8dSAndroid Build Coastguard Worker    def handle_accepted(self, conn, addr):
664*cda5da8dSAndroid Build Coastguard Worker        print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
665*cda5da8dSAndroid Build Coastguard Worker        channel = self.channel_class(self,
666*cda5da8dSAndroid Build Coastguard Worker                                     conn,
667*cda5da8dSAndroid Build Coastguard Worker                                     addr,
668*cda5da8dSAndroid Build Coastguard Worker                                     self.data_size_limit,
669*cda5da8dSAndroid Build Coastguard Worker                                     self._map,
670*cda5da8dSAndroid Build Coastguard Worker                                     self.enable_SMTPUTF8,
671*cda5da8dSAndroid Build Coastguard Worker                                     self._decode_data)
672*cda5da8dSAndroid Build Coastguard Worker
673*cda5da8dSAndroid Build Coastguard Worker    # API for "doing something useful with the message"
674*cda5da8dSAndroid Build Coastguard Worker    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
675*cda5da8dSAndroid Build Coastguard Worker        """Override this abstract method to handle messages from the client.
676*cda5da8dSAndroid Build Coastguard Worker
677*cda5da8dSAndroid Build Coastguard Worker        peer is a tuple containing (ipaddr, port) of the client that made the
678*cda5da8dSAndroid Build Coastguard Worker        socket connection to our smtp port.
679*cda5da8dSAndroid Build Coastguard Worker
680*cda5da8dSAndroid Build Coastguard Worker        mailfrom is the raw address the client claims the message is coming
681*cda5da8dSAndroid Build Coastguard Worker        from.
682*cda5da8dSAndroid Build Coastguard Worker
683*cda5da8dSAndroid Build Coastguard Worker        rcpttos is a list of raw addresses the client wishes to deliver the
684*cda5da8dSAndroid Build Coastguard Worker        message to.
685*cda5da8dSAndroid Build Coastguard Worker
686*cda5da8dSAndroid Build Coastguard Worker        data is a string containing the entire full text of the message,
687*cda5da8dSAndroid Build Coastguard Worker        headers (if supplied) and all.  It has been `de-transparencied'
688*cda5da8dSAndroid Build Coastguard Worker        according to RFC 821, Section 4.5.2.  In other words, a line
689*cda5da8dSAndroid Build Coastguard Worker        containing a `.' followed by other text has had the leading dot
690*cda5da8dSAndroid Build Coastguard Worker        removed.
691*cda5da8dSAndroid Build Coastguard Worker
692*cda5da8dSAndroid Build Coastguard Worker        kwargs is a dictionary containing additional information.  It is
693*cda5da8dSAndroid Build Coastguard Worker        empty if decode_data=True was given as init parameter, otherwise
694*cda5da8dSAndroid Build Coastguard Worker        it will contain the following keys:
695*cda5da8dSAndroid Build Coastguard Worker            'mail_options': list of parameters to the mail command.  All
696*cda5da8dSAndroid Build Coastguard Worker                            elements are uppercase strings.  Example:
697*cda5da8dSAndroid Build Coastguard Worker                            ['BODY=8BITMIME', 'SMTPUTF8'].
698*cda5da8dSAndroid Build Coastguard Worker            'rcpt_options': same, for the rcpt command.
699*cda5da8dSAndroid Build Coastguard Worker
700*cda5da8dSAndroid Build Coastguard Worker        This function should return None for a normal `250 Ok' response;
701*cda5da8dSAndroid Build Coastguard Worker        otherwise, it should return the desired response string in RFC 821
702*cda5da8dSAndroid Build Coastguard Worker        format.
703*cda5da8dSAndroid Build Coastguard Worker
704*cda5da8dSAndroid Build Coastguard Worker        """
705*cda5da8dSAndroid Build Coastguard Worker        raise NotImplementedError
706*cda5da8dSAndroid Build Coastguard Worker
707*cda5da8dSAndroid Build Coastguard Worker
708*cda5da8dSAndroid Build Coastguard Workerclass DebuggingServer(SMTPServer):
709*cda5da8dSAndroid Build Coastguard Worker
710*cda5da8dSAndroid Build Coastguard Worker    def _print_message_content(self, peer, data):
711*cda5da8dSAndroid Build Coastguard Worker        inheaders = 1
712*cda5da8dSAndroid Build Coastguard Worker        lines = data.splitlines()
713*cda5da8dSAndroid Build Coastguard Worker        for line in lines:
714*cda5da8dSAndroid Build Coastguard Worker            # headers first
715*cda5da8dSAndroid Build Coastguard Worker            if inheaders and not line:
716*cda5da8dSAndroid Build Coastguard Worker                peerheader = 'X-Peer: ' + peer[0]
717*cda5da8dSAndroid Build Coastguard Worker                if not isinstance(data, str):
718*cda5da8dSAndroid Build Coastguard Worker                    # decoded_data=false; make header match other binary output
719*cda5da8dSAndroid Build Coastguard Worker                    peerheader = repr(peerheader.encode('utf-8'))
720*cda5da8dSAndroid Build Coastguard Worker                print(peerheader)
721*cda5da8dSAndroid Build Coastguard Worker                inheaders = 0
722*cda5da8dSAndroid Build Coastguard Worker            if not isinstance(data, str):
723*cda5da8dSAndroid Build Coastguard Worker                # Avoid spurious 'str on bytes instance' warning.
724*cda5da8dSAndroid Build Coastguard Worker                line = repr(line)
725*cda5da8dSAndroid Build Coastguard Worker            print(line)
726*cda5da8dSAndroid Build Coastguard Worker
727*cda5da8dSAndroid Build Coastguard Worker    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
728*cda5da8dSAndroid Build Coastguard Worker        print('---------- MESSAGE FOLLOWS ----------')
729*cda5da8dSAndroid Build Coastguard Worker        if kwargs:
730*cda5da8dSAndroid Build Coastguard Worker            if kwargs.get('mail_options'):
731*cda5da8dSAndroid Build Coastguard Worker                print('mail options: %s' % kwargs['mail_options'])
732*cda5da8dSAndroid Build Coastguard Worker            if kwargs.get('rcpt_options'):
733*cda5da8dSAndroid Build Coastguard Worker                print('rcpt options: %s\n' % kwargs['rcpt_options'])
734*cda5da8dSAndroid Build Coastguard Worker        self._print_message_content(peer, data)
735*cda5da8dSAndroid Build Coastguard Worker        print('------------ END MESSAGE ------------')
736*cda5da8dSAndroid Build Coastguard Worker
737*cda5da8dSAndroid Build Coastguard Worker
738*cda5da8dSAndroid Build Coastguard Workerclass PureProxy(SMTPServer):
739*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, *args, **kwargs):
740*cda5da8dSAndroid Build Coastguard Worker        if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:
741*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("PureProxy does not support SMTPUTF8.")
742*cda5da8dSAndroid Build Coastguard Worker        super(PureProxy, self).__init__(*args, **kwargs)
743*cda5da8dSAndroid Build Coastguard Worker
744*cda5da8dSAndroid Build Coastguard Worker    def process_message(self, peer, mailfrom, rcpttos, data):
745*cda5da8dSAndroid Build Coastguard Worker        lines = data.split('\n')
746*cda5da8dSAndroid Build Coastguard Worker        # Look for the last header
747*cda5da8dSAndroid Build Coastguard Worker        i = 0
748*cda5da8dSAndroid Build Coastguard Worker        for line in lines:
749*cda5da8dSAndroid Build Coastguard Worker            if not line:
750*cda5da8dSAndroid Build Coastguard Worker                break
751*cda5da8dSAndroid Build Coastguard Worker            i += 1
752*cda5da8dSAndroid Build Coastguard Worker        lines.insert(i, 'X-Peer: %s' % peer[0])
753*cda5da8dSAndroid Build Coastguard Worker        data = NEWLINE.join(lines)
754*cda5da8dSAndroid Build Coastguard Worker        refused = self._deliver(mailfrom, rcpttos, data)
755*cda5da8dSAndroid Build Coastguard Worker        # TBD: what to do with refused addresses?
756*cda5da8dSAndroid Build Coastguard Worker        print('we got some refusals:', refused, file=DEBUGSTREAM)
757*cda5da8dSAndroid Build Coastguard Worker
758*cda5da8dSAndroid Build Coastguard Worker    def _deliver(self, mailfrom, rcpttos, data):
759*cda5da8dSAndroid Build Coastguard Worker        import smtplib
760*cda5da8dSAndroid Build Coastguard Worker        refused = {}
761*cda5da8dSAndroid Build Coastguard Worker        try:
762*cda5da8dSAndroid Build Coastguard Worker            s = smtplib.SMTP()
763*cda5da8dSAndroid Build Coastguard Worker            s.connect(self._remoteaddr[0], self._remoteaddr[1])
764*cda5da8dSAndroid Build Coastguard Worker            try:
765*cda5da8dSAndroid Build Coastguard Worker                refused = s.sendmail(mailfrom, rcpttos, data)
766*cda5da8dSAndroid Build Coastguard Worker            finally:
767*cda5da8dSAndroid Build Coastguard Worker                s.quit()
768*cda5da8dSAndroid Build Coastguard Worker        except smtplib.SMTPRecipientsRefused as e:
769*cda5da8dSAndroid Build Coastguard Worker            print('got SMTPRecipientsRefused', file=DEBUGSTREAM)
770*cda5da8dSAndroid Build Coastguard Worker            refused = e.recipients
771*cda5da8dSAndroid Build Coastguard Worker        except (OSError, smtplib.SMTPException) as e:
772*cda5da8dSAndroid Build Coastguard Worker            print('got', e.__class__, file=DEBUGSTREAM)
773*cda5da8dSAndroid Build Coastguard Worker            # All recipients were refused.  If the exception had an associated
774*cda5da8dSAndroid Build Coastguard Worker            # error code, use it.  Otherwise,fake it with a non-triggering
775*cda5da8dSAndroid Build Coastguard Worker            # exception code.
776*cda5da8dSAndroid Build Coastguard Worker            errcode = getattr(e, 'smtp_code', -1)
777*cda5da8dSAndroid Build Coastguard Worker            errmsg = getattr(e, 'smtp_error', 'ignore')
778*cda5da8dSAndroid Build Coastguard Worker            for r in rcpttos:
779*cda5da8dSAndroid Build Coastguard Worker                refused[r] = (errcode, errmsg)
780*cda5da8dSAndroid Build Coastguard Worker        return refused
781*cda5da8dSAndroid Build Coastguard Worker
782*cda5da8dSAndroid Build Coastguard Worker
783*cda5da8dSAndroid Build Coastguard Workerclass Options:
784*cda5da8dSAndroid Build Coastguard Worker    setuid = True
785*cda5da8dSAndroid Build Coastguard Worker    classname = 'PureProxy'
786*cda5da8dSAndroid Build Coastguard Worker    size_limit = None
787*cda5da8dSAndroid Build Coastguard Worker    enable_SMTPUTF8 = False
788*cda5da8dSAndroid Build Coastguard Worker
789*cda5da8dSAndroid Build Coastguard Worker
790*cda5da8dSAndroid Build Coastguard Workerdef parseargs():
791*cda5da8dSAndroid Build Coastguard Worker    global DEBUGSTREAM
792*cda5da8dSAndroid Build Coastguard Worker    try:
793*cda5da8dSAndroid Build Coastguard Worker        opts, args = getopt.getopt(
794*cda5da8dSAndroid Build Coastguard Worker            sys.argv[1:], 'nVhc:s:du',
795*cda5da8dSAndroid Build Coastguard Worker            ['class=', 'nosetuid', 'version', 'help', 'size=', 'debug',
796*cda5da8dSAndroid Build Coastguard Worker             'smtputf8'])
797*cda5da8dSAndroid Build Coastguard Worker    except getopt.error as e:
798*cda5da8dSAndroid Build Coastguard Worker        usage(1, e)
799*cda5da8dSAndroid Build Coastguard Worker
800*cda5da8dSAndroid Build Coastguard Worker    options = Options()
801*cda5da8dSAndroid Build Coastguard Worker    for opt, arg in opts:
802*cda5da8dSAndroid Build Coastguard Worker        if opt in ('-h', '--help'):
803*cda5da8dSAndroid Build Coastguard Worker            usage(0)
804*cda5da8dSAndroid Build Coastguard Worker        elif opt in ('-V', '--version'):
805*cda5da8dSAndroid Build Coastguard Worker            print(__version__)
806*cda5da8dSAndroid Build Coastguard Worker            sys.exit(0)
807*cda5da8dSAndroid Build Coastguard Worker        elif opt in ('-n', '--nosetuid'):
808*cda5da8dSAndroid Build Coastguard Worker            options.setuid = False
809*cda5da8dSAndroid Build Coastguard Worker        elif opt in ('-c', '--class'):
810*cda5da8dSAndroid Build Coastguard Worker            options.classname = arg
811*cda5da8dSAndroid Build Coastguard Worker        elif opt in ('-d', '--debug'):
812*cda5da8dSAndroid Build Coastguard Worker            DEBUGSTREAM = sys.stderr
813*cda5da8dSAndroid Build Coastguard Worker        elif opt in ('-u', '--smtputf8'):
814*cda5da8dSAndroid Build Coastguard Worker            options.enable_SMTPUTF8 = True
815*cda5da8dSAndroid Build Coastguard Worker        elif opt in ('-s', '--size'):
816*cda5da8dSAndroid Build Coastguard Worker            try:
817*cda5da8dSAndroid Build Coastguard Worker                int_size = int(arg)
818*cda5da8dSAndroid Build Coastguard Worker                options.size_limit = int_size
819*cda5da8dSAndroid Build Coastguard Worker            except:
820*cda5da8dSAndroid Build Coastguard Worker                print('Invalid size: ' + arg, file=sys.stderr)
821*cda5da8dSAndroid Build Coastguard Worker                sys.exit(1)
822*cda5da8dSAndroid Build Coastguard Worker
823*cda5da8dSAndroid Build Coastguard Worker    # parse the rest of the arguments
824*cda5da8dSAndroid Build Coastguard Worker    if len(args) < 1:
825*cda5da8dSAndroid Build Coastguard Worker        localspec = 'localhost:8025'
826*cda5da8dSAndroid Build Coastguard Worker        remotespec = 'localhost:25'
827*cda5da8dSAndroid Build Coastguard Worker    elif len(args) < 2:
828*cda5da8dSAndroid Build Coastguard Worker        localspec = args[0]
829*cda5da8dSAndroid Build Coastguard Worker        remotespec = 'localhost:25'
830*cda5da8dSAndroid Build Coastguard Worker    elif len(args) < 3:
831*cda5da8dSAndroid Build Coastguard Worker        localspec = args[0]
832*cda5da8dSAndroid Build Coastguard Worker        remotespec = args[1]
833*cda5da8dSAndroid Build Coastguard Worker    else:
834*cda5da8dSAndroid Build Coastguard Worker        usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
835*cda5da8dSAndroid Build Coastguard Worker
836*cda5da8dSAndroid Build Coastguard Worker    # split into host/port pairs
837*cda5da8dSAndroid Build Coastguard Worker    i = localspec.find(':')
838*cda5da8dSAndroid Build Coastguard Worker    if i < 0:
839*cda5da8dSAndroid Build Coastguard Worker        usage(1, 'Bad local spec: %s' % localspec)
840*cda5da8dSAndroid Build Coastguard Worker    options.localhost = localspec[:i]
841*cda5da8dSAndroid Build Coastguard Worker    try:
842*cda5da8dSAndroid Build Coastguard Worker        options.localport = int(localspec[i+1:])
843*cda5da8dSAndroid Build Coastguard Worker    except ValueError:
844*cda5da8dSAndroid Build Coastguard Worker        usage(1, 'Bad local port: %s' % localspec)
845*cda5da8dSAndroid Build Coastguard Worker    i = remotespec.find(':')
846*cda5da8dSAndroid Build Coastguard Worker    if i < 0:
847*cda5da8dSAndroid Build Coastguard Worker        usage(1, 'Bad remote spec: %s' % remotespec)
848*cda5da8dSAndroid Build Coastguard Worker    options.remotehost = remotespec[:i]
849*cda5da8dSAndroid Build Coastguard Worker    try:
850*cda5da8dSAndroid Build Coastguard Worker        options.remoteport = int(remotespec[i+1:])
851*cda5da8dSAndroid Build Coastguard Worker    except ValueError:
852*cda5da8dSAndroid Build Coastguard Worker        usage(1, 'Bad remote port: %s' % remotespec)
853*cda5da8dSAndroid Build Coastguard Worker    return options
854*cda5da8dSAndroid Build Coastguard Worker
855*cda5da8dSAndroid Build Coastguard Worker
856*cda5da8dSAndroid Build Coastguard Workerif __name__ == '__main__':
857*cda5da8dSAndroid Build Coastguard Worker    options = parseargs()
858*cda5da8dSAndroid Build Coastguard Worker    # Become nobody
859*cda5da8dSAndroid Build Coastguard Worker    classname = options.classname
860*cda5da8dSAndroid Build Coastguard Worker    if "." in classname:
861*cda5da8dSAndroid Build Coastguard Worker        lastdot = classname.rfind(".")
862*cda5da8dSAndroid Build Coastguard Worker        mod = __import__(classname[:lastdot], globals(), locals(), [""])
863*cda5da8dSAndroid Build Coastguard Worker        classname = classname[lastdot+1:]
864*cda5da8dSAndroid Build Coastguard Worker    else:
865*cda5da8dSAndroid Build Coastguard Worker        import __main__ as mod
866*cda5da8dSAndroid Build Coastguard Worker    class_ = getattr(mod, classname)
867*cda5da8dSAndroid Build Coastguard Worker    proxy = class_((options.localhost, options.localport),
868*cda5da8dSAndroid Build Coastguard Worker                   (options.remotehost, options.remoteport),
869*cda5da8dSAndroid Build Coastguard Worker                   options.size_limit, enable_SMTPUTF8=options.enable_SMTPUTF8)
870*cda5da8dSAndroid Build Coastguard Worker    if options.setuid:
871*cda5da8dSAndroid Build Coastguard Worker        try:
872*cda5da8dSAndroid Build Coastguard Worker            import pwd
873*cda5da8dSAndroid Build Coastguard Worker        except ImportError:
874*cda5da8dSAndroid Build Coastguard Worker            print('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)
875*cda5da8dSAndroid Build Coastguard Worker            sys.exit(1)
876*cda5da8dSAndroid Build Coastguard Worker        nobody = pwd.getpwnam('nobody')[2]
877*cda5da8dSAndroid Build Coastguard Worker        try:
878*cda5da8dSAndroid Build Coastguard Worker            os.setuid(nobody)
879*cda5da8dSAndroid Build Coastguard Worker        except PermissionError:
880*cda5da8dSAndroid Build Coastguard Worker            print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)
881*cda5da8dSAndroid Build Coastguard Worker            sys.exit(1)
882*cda5da8dSAndroid Build Coastguard Worker    try:
883*cda5da8dSAndroid Build Coastguard Worker        asyncore.loop()
884*cda5da8dSAndroid Build Coastguard Worker    except KeyboardInterrupt:
885*cda5da8dSAndroid Build Coastguard Worker        pass
886