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