summaryrefslogtreecommitdiffstats
path: root/Lib/test/support
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2021-12-07 11:31:04 (GMT)
committerGitHub <noreply@github.com>2021-12-07 11:31:04 (GMT)
commitcf7eaa4617295747ee5646c4e2b7e7a16d7c64ab (patch)
treeb09b7bcc7b2cdfcae0b8291f6ec867dbb8337246 /Lib/test/support
parent2bf551757e0a7e3cc6ce2ebed2178b82438ac6b5 (diff)
downloadcpython-cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab.zip
cpython-cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab.tar.gz
cpython-cf7eaa4617295747ee5646c4e2b7e7a16d7c64ab.tar.bz2
Revert "bpo-28533: Remove asyncore, asynchat, smtpd modules (GH-29521)" (GH-29951)
This reverts commit 9bf2cbc4c498812e14f20d86acb61c53928a5a57.
Diffstat (limited to 'Lib/test/support')
-rw-r--r--Lib/test/support/_asynchat.py307
-rw-r--r--Lib/test/support/_asyncore.py643
-rwxr-xr-xLib/test/support/_smtpd.py754
3 files changed, 0 insertions, 1704 deletions
diff --git a/Lib/test/support/_asynchat.py b/Lib/test/support/_asynchat.py
deleted file mode 100644
index 941cc1d..0000000
--- a/Lib/test/support/_asynchat.py
+++ /dev/null
@@ -1,307 +0,0 @@
-# -*- Mode: Python; tab-width: 4 -*-
-# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
-# Author: Sam Rushing <rushing@nightmare.com>
-
-# ======================================================================
-# Copyright 1996 by Sam Rushing
-#
-# All Rights Reserved
-#
-# Permission to use, copy, modify, and distribute this software and
-# its documentation for any purpose and without fee is hereby
-# granted, provided that the above copyright notice appear in all
-# copies and that both that copyright notice and this permission
-# notice appear in supporting documentation, and that the name of Sam
-# Rushing not be used in advertising or publicity pertaining to
-# distribution of the software without specific, written prior
-# permission.
-#
-# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
-# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
-# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
-# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# ======================================================================
-
-r"""A class supporting chat-style (command/response) protocols.
-
-This class adds support for 'chat' style protocols - where one side
-sends a 'command', and the other sends a response (examples would be
-the common internet protocols - smtp, nntp, ftp, etc..).
-
-The handle_read() method looks at the input stream for the current
-'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
-for multi-line output), calling self.found_terminator() on its
-receipt.
-
-for example:
-Say you build an async nntp client using this class. At the start
-of the connection, you'll have self.terminator set to '\r\n', in
-order to process the single-line greeting. Just before issuing a
-'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
-command will be accumulated (using your own 'collect_incoming_data'
-method) up to the terminator, and then control will be returned to
-you - by calling your self.found_terminator() method.
-"""
-from test.support import _asyncore as asyncore
-from collections import deque
-
-
-class async_chat(asyncore.dispatcher):
- """This is an abstract class. You must derive from this class, and add
- the two methods collect_incoming_data() and found_terminator()"""
-
- # these are overridable defaults
-
- ac_in_buffer_size = 65536
- ac_out_buffer_size = 65536
-
- # we don't want to enable the use of encoding by default, because that is a
- # sign of an application bug that we don't want to pass silently
-
- use_encoding = 0
- encoding = 'latin-1'
-
- def __init__(self, sock=None, map=None):
- # for string terminator matching
- self.ac_in_buffer = b''
-
- # we use a list here rather than io.BytesIO for a few reasons...
- # del lst[:] is faster than bio.truncate(0)
- # lst = [] is faster than bio.truncate(0)
- self.incoming = []
-
- # we toss the use of the "simple producer" and replace it with
- # a pure deque, which the original fifo was a wrapping of
- self.producer_fifo = deque()
- asyncore.dispatcher.__init__(self, sock, map)
-
- def collect_incoming_data(self, data):
- raise NotImplementedError("must be implemented in subclass")
-
- def _collect_incoming_data(self, data):
- self.incoming.append(data)
-
- def _get_data(self):
- d = b''.join(self.incoming)
- del self.incoming[:]
- return d
-
- def found_terminator(self):
- raise NotImplementedError("must be implemented in subclass")
-
- def set_terminator(self, term):
- """Set the input delimiter.
-
- Can be a fixed string of any length, an integer, or None.
- """
- if isinstance(term, str) and self.use_encoding:
- term = bytes(term, self.encoding)
- elif isinstance(term, int) and term < 0:
- raise ValueError('the number of received bytes must be positive')
- self.terminator = term
-
- def get_terminator(self):
- return self.terminator
-
- # grab some more data from the socket,
- # throw it to the collector method,
- # check for the terminator,
- # if found, transition to the next state.
-
- def handle_read(self):
-
- try:
- data = self.recv(self.ac_in_buffer_size)
- except BlockingIOError:
- return
- except OSError:
- self.handle_error()
- return
-
- if isinstance(data, str) and self.use_encoding:
- data = bytes(str, self.encoding)
- self.ac_in_buffer = self.ac_in_buffer + data
-
- # Continue to search for self.terminator in self.ac_in_buffer,
- # while calling self.collect_incoming_data. The while loop
- # is necessary because we might read several data+terminator
- # combos with a single recv(4096).
-
- while self.ac_in_buffer:
- lb = len(self.ac_in_buffer)
- terminator = self.get_terminator()
- if not terminator:
- # no terminator, collect it all
- self.collect_incoming_data(self.ac_in_buffer)
- self.ac_in_buffer = b''
- elif isinstance(terminator, int):
- # numeric terminator
- n = terminator
- if lb < n:
- self.collect_incoming_data(self.ac_in_buffer)
- self.ac_in_buffer = b''
- self.terminator = self.terminator - lb
- else:
- self.collect_incoming_data(self.ac_in_buffer[:n])
- self.ac_in_buffer = self.ac_in_buffer[n:]
- self.terminator = 0
- self.found_terminator()
- else:
- # 3 cases:
- # 1) end of buffer matches terminator exactly:
- # collect data, transition
- # 2) end of buffer matches some prefix:
- # collect data to the prefix
- # 3) end of buffer does not match any prefix:
- # collect data
- terminator_len = len(terminator)
- index = self.ac_in_buffer.find(terminator)
- if index != -1:
- # we found the terminator
- if index > 0:
- # don't bother reporting the empty string
- # (source of subtle bugs)
- self.collect_incoming_data(self.ac_in_buffer[:index])
- self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
- # This does the Right Thing if the terminator
- # is changed here.
- self.found_terminator()
- else:
- # check for a prefix of the terminator
- index = find_prefix_at_end(self.ac_in_buffer, terminator)
- if index:
- if index != lb:
- # we found a prefix, collect up to the prefix
- self.collect_incoming_data(self.ac_in_buffer[:-index])
- self.ac_in_buffer = self.ac_in_buffer[-index:]
- break
- else:
- # no prefix, collect it all
- self.collect_incoming_data(self.ac_in_buffer)
- self.ac_in_buffer = b''
-
- def handle_write(self):
- self.initiate_send()
-
- def handle_close(self):
- self.close()
-
- def push(self, data):
- if not isinstance(data, (bytes, bytearray, memoryview)):
- raise TypeError('data argument must be byte-ish (%r)',
- type(data))
- sabs = self.ac_out_buffer_size
- if len(data) > sabs:
- for i in range(0, len(data), sabs):
- self.producer_fifo.append(data[i:i+sabs])
- else:
- self.producer_fifo.append(data)
- self.initiate_send()
-
- def push_with_producer(self, producer):
- self.producer_fifo.append(producer)
- self.initiate_send()
-
- def readable(self):
- "predicate for inclusion in the readable for select()"
- # cannot use the old predicate, it violates the claim of the
- # set_terminator method.
-
- # return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
- return 1
-
- def writable(self):
- "predicate for inclusion in the writable for select()"
- return self.producer_fifo or (not self.connected)
-
- def close_when_done(self):
- "automatically close this channel once the outgoing queue is empty"
- self.producer_fifo.append(None)
-
- def initiate_send(self):
- while self.producer_fifo and self.connected:
- first = self.producer_fifo[0]
- # handle empty string/buffer or None entry
- if not first:
- del self.producer_fifo[0]
- if first is None:
- self.handle_close()
- return
-
- # handle classic producer behavior
- obs = self.ac_out_buffer_size
- try:
- data = first[:obs]
- except TypeError:
- data = first.more()
- if data:
- self.producer_fifo.appendleft(data)
- else:
- del self.producer_fifo[0]
- continue
-
- if isinstance(data, str) and self.use_encoding:
- data = bytes(data, self.encoding)
-
- # send the data
- try:
- num_sent = self.send(data)
- except OSError:
- self.handle_error()
- return
-
- if num_sent:
- if num_sent < len(data) or obs < len(first):
- self.producer_fifo[0] = first[num_sent:]
- else:
- del self.producer_fifo[0]
- # we tried to send some actual data
- return
-
- def discard_buffers(self):
- # Emergencies only!
- self.ac_in_buffer = b''
- del self.incoming[:]
- self.producer_fifo.clear()
-
-
-class simple_producer:
-
- def __init__(self, data, buffer_size=512):
- self.data = data
- self.buffer_size = buffer_size
-
- def more(self):
- if len(self.data) > self.buffer_size:
- result = self.data[:self.buffer_size]
- self.data = self.data[self.buffer_size:]
- return result
- else:
- result = self.data
- self.data = b''
- return result
-
-
-# Given 'haystack', see if any prefix of 'needle' is at its end. This
-# assumes an exact match has already been checked. Return the number of
-# characters matched.
-# for example:
-# f_p_a_e("qwerty\r", "\r\n") => 1
-# f_p_a_e("qwertydkjf", "\r\n") => 0
-# f_p_a_e("qwerty\r\n", "\r\n") => <undefined>
-
-# this could maybe be made faster with a computed regex?
-# [answer: no; circa Python-2.0, Jan 2001]
-# new python: 28961/s
-# old python: 18307/s
-# re: 12820/s
-# regex: 14035/s
-
-def find_prefix_at_end(haystack, needle):
- l = len(needle) - 1
- while l and not haystack.endswith(needle[:l]):
- l -= 1
- return l
diff --git a/Lib/test/support/_asyncore.py b/Lib/test/support/_asyncore.py
deleted file mode 100644
index 7863efa..0000000
--- a/Lib/test/support/_asyncore.py
+++ /dev/null
@@ -1,643 +0,0 @@
-# -*- Mode: Python -*-
-# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
-# Author: Sam Rushing <rushing@nightmare.com>
-
-# ======================================================================
-# Copyright 1996 by Sam Rushing
-#
-# All Rights Reserved
-#
-# Permission to use, copy, modify, and distribute this software and
-# its documentation for any purpose and without fee is hereby
-# granted, provided that the above copyright notice appear in all
-# copies and that both that copyright notice and this permission
-# notice appear in supporting documentation, and that the name of Sam
-# Rushing not be used in advertising or publicity pertaining to
-# distribution of the software without specific, written prior
-# permission.
-#
-# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
-# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
-# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
-# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# ======================================================================
-
-"""Basic infrastructure for asynchronous socket service clients and servers.
-
-There are only two ways to have a program on a single processor do "more
-than one thing at a time". Multi-threaded programming is the simplest and
-most popular way to do it, but there is another very different technique,
-that lets you have nearly all the advantages of multi-threading, without
-actually using multiple threads. it's really only practical if your program
-is largely I/O bound. If your program is CPU bound, then pre-emptive
-scheduled threads are probably what you really need. Network servers are
-rarely CPU-bound, however.
-
-If your operating system supports the select() system call in its I/O
-library (and nearly all do), then you can use it to juggle multiple
-communication channels at once; doing other work while your I/O is taking
-place in the "background." Although this strategy can seem strange and
-complex, especially at first, it is in many ways easier to understand and
-control than multi-threaded programming. The module documented here solves
-many of the difficult problems for you, making the task of building
-sophisticated high-performance network servers and clients a snap.
-"""
-
-import select
-import socket
-import sys
-import time
-import warnings
-
-import os
-from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, EINVAL, \
- ENOTCONN, ESHUTDOWN, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \
- errorcode
-
-
-_DISCONNECTED = frozenset({ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE,
- EBADF})
-
-try:
- socket_map
-except NameError:
- socket_map = {}
-
-def _strerror(err):
- try:
- return os.strerror(err)
- except (ValueError, OverflowError, NameError):
- if err in errorcode:
- return errorcode[err]
- return "Unknown error %s" %err
-
-class ExitNow(Exception):
- pass
-
-_reraised_exceptions = (ExitNow, KeyboardInterrupt, SystemExit)
-
-def read(obj):
- try:
- obj.handle_read_event()
- except _reraised_exceptions:
- raise
- except:
- obj.handle_error()
-
-def write(obj):
- try:
- obj.handle_write_event()
- except _reraised_exceptions:
- raise
- except:
- obj.handle_error()
-
-def _exception(obj):
- try:
- obj.handle_expt_event()
- except _reraised_exceptions:
- raise
- except:
- obj.handle_error()
-
-def readwrite(obj, flags):
- try:
- if flags & select.POLLIN:
- obj.handle_read_event()
- if flags & select.POLLOUT:
- obj.handle_write_event()
- if flags & select.POLLPRI:
- obj.handle_expt_event()
- if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
- obj.handle_close()
- except OSError as e:
- if e.errno not in _DISCONNECTED:
- obj.handle_error()
- else:
- obj.handle_close()
- except _reraised_exceptions:
- raise
- except:
- obj.handle_error()
-
-def poll(timeout=0.0, map=None):
- if map is None:
- map = socket_map
- if map:
- r = []; w = []; e = []
- for fd, obj in list(map.items()):
- is_r = obj.readable()
- is_w = obj.writable()
- if is_r:
- r.append(fd)
- # accepting sockets should not be writable
- if is_w and not obj.accepting:
- w.append(fd)
- if is_r or is_w:
- e.append(fd)
- if [] == r == w == e:
- time.sleep(timeout)
- return
-
- r, w, e = select.select(r, w, e, timeout)
-
- for fd in r:
- obj = map.get(fd)
- if obj is None:
- continue
- read(obj)
-
- for fd in w:
- obj = map.get(fd)
- if obj is None:
- continue
- write(obj)
-
- for fd in e:
- obj = map.get(fd)
- if obj is None:
- continue
- _exception(obj)
-
-def poll2(timeout=0.0, map=None):
- # Use the poll() support added to the select module in Python 2.0
- if map is None:
- map = socket_map
- if timeout is not None:
- # timeout is in milliseconds
- timeout = int(timeout*1000)
- pollster = select.poll()
- if map:
- for fd, obj in list(map.items()):
- flags = 0
- if obj.readable():
- flags |= select.POLLIN | select.POLLPRI
- # accepting sockets should not be writable
- if obj.writable() and not obj.accepting:
- flags |= select.POLLOUT
- if flags:
- pollster.register(fd, flags)
-
- r = pollster.poll(timeout)
- for fd, flags in r:
- obj = map.get(fd)
- if obj is None:
- continue
- readwrite(obj, flags)
-
-poll3 = poll2 # Alias for backward compatibility
-
-def loop(timeout=30.0, use_poll=False, map=None, count=None):
- if map is None:
- map = socket_map
-
- if use_poll and hasattr(select, 'poll'):
- poll_fun = poll2
- else:
- poll_fun = poll
-
- if count is None:
- while map:
- poll_fun(timeout, map)
-
- else:
- while map and count > 0:
- poll_fun(timeout, map)
- count = count - 1
-
-class dispatcher:
-
- debug = False
- connected = False
- accepting = False
- connecting = False
- closing = False
- addr = None
- ignore_log_types = frozenset({'warning'})
-
- def __init__(self, sock=None, map=None):
- if map is None:
- self._map = socket_map
- else:
- self._map = map
-
- self._fileno = None
-
- if sock:
- # Set to nonblocking just to make sure for cases where we
- # get a socket from a blocking source.
- sock.setblocking(False)
- self.set_socket(sock, map)
- self.connected = True
- # The constructor no longer requires that the socket
- # passed be connected.
- try:
- self.addr = sock.getpeername()
- except OSError as err:
- if err.errno in (ENOTCONN, EINVAL):
- # To handle the case where we got an unconnected
- # socket.
- self.connected = False
- else:
- # The socket is broken in some unknown way, alert
- # the user and remove it from the map (to prevent
- # polling of broken sockets).
- self.del_channel(map)
- raise
- else:
- self.socket = None
-
- def __repr__(self):
- status = [self.__class__.__module__+"."+self.__class__.__qualname__]
- if self.accepting and self.addr:
- status.append('listening')
- elif self.connected:
- status.append('connected')
- if self.addr is not None:
- try:
- status.append('%s:%d' % self.addr)
- except TypeError:
- status.append(repr(self.addr))
- return '<%s at %#x>' % (' '.join(status), id(self))
-
- def add_channel(self, map=None):
- #self.log_info('adding channel %s' % self)
- if map is None:
- map = self._map
- map[self._fileno] = self
-
- def del_channel(self, map=None):
- fd = self._fileno
- if map is None:
- map = self._map
- if fd in map:
- #self.log_info('closing channel %d:%s' % (fd, self))
- del map[fd]
- self._fileno = None
-
- def create_socket(self, family=socket.AF_INET, type=socket.SOCK_STREAM):
- self.family_and_type = family, type
- sock = socket.socket(family, type)
- sock.setblocking(False)
- self.set_socket(sock)
-
- def set_socket(self, sock, map=None):
- self.socket = sock
- self._fileno = sock.fileno()
- self.add_channel(map)
-
- def set_reuse_addr(self):
- # try to re-use a server port if possible
- try:
- self.socket.setsockopt(
- socket.SOL_SOCKET, socket.SO_REUSEADDR,
- self.socket.getsockopt(socket.SOL_SOCKET,
- socket.SO_REUSEADDR) | 1
- )
- except OSError:
- pass
-
- # ==================================================
- # predicates for select()
- # these are used as filters for the lists of sockets
- # to pass to select().
- # ==================================================
-
- def readable(self):
- return True
-
- def writable(self):
- return True
-
- # ==================================================
- # socket object methods.
- # ==================================================
-
- def listen(self, num):
- self.accepting = True
- if os.name == 'nt' and num > 5:
- num = 5
- return self.socket.listen(num)
-
- def bind(self, addr):
- self.addr = addr
- return self.socket.bind(addr)
-
- def connect(self, address):
- self.connected = False
- self.connecting = True
- err = self.socket.connect_ex(address)
- if err in (EINPROGRESS, EALREADY, EWOULDBLOCK) \
- or err == EINVAL and os.name == 'nt':
- self.addr = address
- return
- if err in (0, EISCONN):
- self.addr = address
- self.handle_connect_event()
- else:
- raise OSError(err, errorcode[err])
-
- def accept(self):
- # XXX can return either an address pair or None
- try:
- conn, addr = self.socket.accept()
- except TypeError:
- return None
- except OSError as why:
- if why.errno in (EWOULDBLOCK, ECONNABORTED, EAGAIN):
- return None
- else:
- raise
- else:
- return conn, addr
-
- def send(self, data):
- try:
- result = self.socket.send(data)
- return result
- except OSError as why:
- if why.errno == EWOULDBLOCK:
- return 0
- elif why.errno in _DISCONNECTED:
- self.handle_close()
- return 0
- else:
- raise
-
- def recv(self, buffer_size):
- try:
- data = self.socket.recv(buffer_size)
- if not data:
- # a closed connection is indicated by signaling
- # a read condition, and having recv() return 0.
- self.handle_close()
- return b''
- else:
- return data
- except OSError as why:
- # winsock sometimes raises ENOTCONN
- if why.errno in _DISCONNECTED:
- self.handle_close()
- return b''
- else:
- raise
-
- def close(self):
- self.connected = False
- self.accepting = False
- self.connecting = False
- self.del_channel()
- if self.socket is not None:
- try:
- self.socket.close()
- except OSError as why:
- if why.errno not in (ENOTCONN, EBADF):
- raise
-
- # log and log_info may be overridden to provide more sophisticated
- # logging and warning methods. In general, log is for 'hit' logging
- # and 'log_info' is for informational, warning and error logging.
-
- def log(self, message):
- sys.stderr.write('log: %s\n' % str(message))
-
- def log_info(self, message, type='info'):
- if type not in self.ignore_log_types:
- print('%s: %s' % (type, message))
-
- def handle_read_event(self):
- if self.accepting:
- # accepting sockets are never connected, they "spawn" new
- # sockets that are connected
- self.handle_accept()
- elif not self.connected:
- if self.connecting:
- self.handle_connect_event()
- self.handle_read()
- else:
- self.handle_read()
-
- def handle_connect_event(self):
- err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
- if err != 0:
- raise OSError(err, _strerror(err))
- self.handle_connect()
- self.connected = True
- self.connecting = False
-
- def handle_write_event(self):
- if self.accepting:
- # Accepting sockets shouldn't get a write event.
- # We will pretend it didn't happen.
- return
-
- if not self.connected:
- if self.connecting:
- self.handle_connect_event()
- self.handle_write()
-
- def handle_expt_event(self):
- # handle_expt_event() is called if there might be an error on the
- # socket, or if there is OOB data
- # check for the error condition first
- err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
- if err != 0:
- # we can get here when select.select() says that there is an
- # exceptional condition on the socket
- # since there is an error, we'll go ahead and close the socket
- # like we would in a subclassed handle_read() that received no
- # data
- self.handle_close()
- else:
- self.handle_expt()
-
- def handle_error(self):
- nil, t, v, tbinfo = compact_traceback()
-
- # sometimes a user repr method will crash.
- try:
- self_repr = repr(self)
- except:
- self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
-
- self.log_info(
- 'uncaptured python exception, closing channel %s (%s:%s %s)' % (
- self_repr,
- t,
- v,
- tbinfo
- ),
- 'error'
- )
- self.handle_close()
-
- def handle_expt(self):
- self.log_info('unhandled incoming priority event', 'warning')
-
- def handle_read(self):
- self.log_info('unhandled read event', 'warning')
-
- def handle_write(self):
- self.log_info('unhandled write event', 'warning')
-
- def handle_connect(self):
- self.log_info('unhandled connect event', 'warning')
-
- def handle_accept(self):
- pair = self.accept()
- if pair is not None:
- self.handle_accepted(*pair)
-
- def handle_accepted(self, sock, addr):
- sock.close()
- self.log_info('unhandled accepted event', 'warning')
-
- def handle_close(self):
- self.log_info('unhandled close event', 'warning')
- self.close()
-
-# ---------------------------------------------------------------------------
-# adds simple buffered output capability, useful for simple clients.
-# [for more sophisticated usage use asynchat.async_chat]
-# ---------------------------------------------------------------------------
-
-class dispatcher_with_send(dispatcher):
-
- def __init__(self, sock=None, map=None):
- dispatcher.__init__(self, sock, map)
- self.out_buffer = b''
-
- def initiate_send(self):
- num_sent = 0
- num_sent = dispatcher.send(self, self.out_buffer[:65536])
- self.out_buffer = self.out_buffer[num_sent:]
-
- def handle_write(self):
- self.initiate_send()
-
- def writable(self):
- return (not self.connected) or len(self.out_buffer)
-
- def send(self, data):
- if self.debug:
- self.log_info('sending %s' % repr(data))
- self.out_buffer = self.out_buffer + data
- self.initiate_send()
-
-# ---------------------------------------------------------------------------
-# used for debugging.
-# ---------------------------------------------------------------------------
-
-def compact_traceback():
- t, v, tb = sys.exc_info()
- tbinfo = []
- if not tb: # Must have a traceback
- raise AssertionError("traceback does not exist")
- while tb:
- tbinfo.append((
- tb.tb_frame.f_code.co_filename,
- tb.tb_frame.f_code.co_name,
- str(tb.tb_lineno)
- ))
- tb = tb.tb_next
-
- # just to be safe
- del tb
-
- file, function, line = tbinfo[-1]
- info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
- return (file, function, line), t, v, info
-
-def close_all(map=None, ignore_all=False):
- if map is None:
- map = socket_map
- for x in list(map.values()):
- try:
- x.close()
- except OSError as x:
- if x.errno == EBADF:
- pass
- elif not ignore_all:
- raise
- except _reraised_exceptions:
- raise
- except:
- if not ignore_all:
- raise
- map.clear()
-
-# Asynchronous File I/O:
-#
-# After a little research (reading man pages on various unixen, and
-# digging through the linux kernel), I've determined that select()
-# isn't meant for doing asynchronous file i/o.
-# Heartening, though - reading linux/mm/filemap.c shows that linux
-# supports asynchronous read-ahead. So _MOST_ of the time, the data
-# will be sitting in memory for us already when we go to read it.
-#
-# What other OS's (besides NT) support async file i/o? [VMS?]
-#
-# Regardless, this is useful for pipes, and stdin/stdout...
-
-if os.name == 'posix':
- class file_wrapper:
- # Here we override just enough to make a file
- # look like a socket for the purposes of asyncore.
- # The passed fd is automatically os.dup()'d
-
- def __init__(self, fd):
- self.fd = os.dup(fd)
-
- def __del__(self):
- if self.fd >= 0:
- warnings.warn("unclosed file %r" % self, ResourceWarning,
- source=self)
- self.close()
-
- def recv(self, *args):
- return os.read(self.fd, *args)
-
- def send(self, *args):
- return os.write(self.fd, *args)
-
- def getsockopt(self, level, optname, buflen=None):
- if (level == socket.SOL_SOCKET and
- optname == socket.SO_ERROR and
- not buflen):
- return 0
- raise NotImplementedError("Only asyncore specific behaviour "
- "implemented.")
-
- read = recv
- write = send
-
- def close(self):
- if self.fd < 0:
- return
- fd = self.fd
- self.fd = -1
- os.close(fd)
-
- def fileno(self):
- return self.fd
-
- class file_dispatcher(dispatcher):
-
- def __init__(self, fd, map=None):
- dispatcher.__init__(self, None, map)
- self.connected = True
- try:
- fd = fd.fileno()
- except AttributeError:
- pass
- self.set_file(fd)
- # set it to non-blocking mode
- os.set_blocking(fd, False)
-
- def set_file(self, fd):
- self.socket = file_wrapper(fd)
- self._fileno = self.socket.fileno()
- self.add_channel()
diff --git a/Lib/test/support/_smtpd.py b/Lib/test/support/_smtpd.py
deleted file mode 100755
index 0e37d08..0000000
--- a/Lib/test/support/_smtpd.py
+++ /dev/null
@@ -1,754 +0,0 @@
-#! /usr/bin/env python3
-"""An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.
-
-Usage: %(program)s [options] [localhost:localport [remotehost:remoteport]]
-
-Options:
-
- --nosetuid
- -n
- This program generally tries to setuid `nobody', unless this flag is
- set. The setuid call will fail if this program is not run as root (in
- which case, use this flag).
-
- --version
- -V
- Print the version number and exit.
-
- --class classname
- -c classname
- Use `classname' as the concrete SMTP proxy class. Uses `PureProxy' by
- default.
-
- --size limit
- -s limit
- Restrict the total size of the incoming message to "limit" number of
- bytes via the RFC 1870 SIZE extension. Defaults to 33554432 bytes.
-
- --smtputf8
- -u
- Enable the SMTPUTF8 extension and behave as an RFC 6531 smtp proxy.
-
- --debug
- -d
- Turn on debugging prints.
-
- --help
- -h
- Print this message and exit.
-
-Version: %(__version__)s
-
-If localhost is not given then `localhost' is used, and if localport is not
-given then 8025 is used. If remotehost is not given then `localhost' is used,
-and if remoteport is not given, then 25 is used.
-"""
-
-# Overview:
-#
-# This file implements the minimal SMTP protocol as defined in RFC 5321. It
-# has a hierarchy of classes which implement the backend functionality for the
-# smtpd. A number of classes are provided:
-#
-# SMTPServer - the base class for the backend. Raises NotImplementedError
-# if you try to use it.
-#
-# DebuggingServer - simply prints each message it receives on stdout.
-#
-# PureProxy - Proxies all messages to a real smtpd which does final
-# delivery. One known problem with this class is that it doesn't handle
-# SMTP errors from the backend server at all. This should be fixed
-# (contributions are welcome!).
-#
-#
-# Author: Barry Warsaw <barry@python.org>
-#
-# TODO:
-#
-# - support mailbox delivery
-# - alias files
-# - Handle more ESMTP extensions
-# - handle error codes from the backend smtpd
-
-import sys
-import os
-import errno
-import getopt
-import time
-import socket
-import collections
-from warnings import warn
-from email._header_value_parser import get_addr_spec, get_angle_addr
-
-from test.support import _asyncore as asyncore
-from test.support import _asynchat as asynchat
-
-__all__ = [
- "SMTPChannel", "SMTPServer", "DebuggingServer", "PureProxy",
-]
-
-
-program = sys.argv[0]
-__version__ = 'Python SMTP proxy version 0.3'
-
-
-class Devnull:
- def write(self, msg): pass
- def flush(self): pass
-
-
-DEBUGSTREAM = Devnull()
-NEWLINE = '\n'
-COMMASPACE = ', '
-DATA_SIZE_DEFAULT = 33554432
-
-
-def usage(code, msg=''):
- print(__doc__ % globals(), file=sys.stderr)
- if msg:
- print(msg, file=sys.stderr)
- sys.exit(code)
-
-
-class SMTPChannel(asynchat.async_chat):
- COMMAND = 0
- DATA = 1
-
- command_size_limit = 512
- command_size_limits = collections.defaultdict(lambda x=command_size_limit: x)
-
- @property
- def max_command_size_limit(self):
- try:
- return max(self.command_size_limits.values())
- except ValueError:
- return self.command_size_limit
-
- def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
- map=None, enable_SMTPUTF8=False, decode_data=False):
- asynchat.async_chat.__init__(self, conn, map=map)
- self.smtp_server = server
- self.conn = conn
- self.addr = addr
- self.data_size_limit = data_size_limit
- self.enable_SMTPUTF8 = enable_SMTPUTF8
- self._decode_data = decode_data
- if enable_SMTPUTF8 and decode_data:
- raise ValueError("decode_data and enable_SMTPUTF8 cannot"
- " be set to True at the same time")
- if decode_data:
- self._emptystring = ''
- self._linesep = '\r\n'
- self._dotsep = '.'
- self._newline = NEWLINE
- else:
- self._emptystring = b''
- self._linesep = b'\r\n'
- self._dotsep = ord(b'.')
- self._newline = b'\n'
- self._set_rset_state()
- self.seen_greeting = ''
- self.extended_smtp = False
- self.command_size_limits.clear()
- self.fqdn = socket.getfqdn()
- try:
- self.peer = conn.getpeername()
- except OSError as err:
- # a race condition may occur if the other end is closing
- # before we can get the peername
- self.close()
- if err.errno != errno.ENOTCONN:
- raise
- return
- print('Peer:', repr(self.peer), file=DEBUGSTREAM)
- self.push('220 %s %s' % (self.fqdn, __version__))
-
- def _set_post_data_state(self):
- """Reset state variables to their post-DATA state."""
- self.smtp_state = self.COMMAND
- self.mailfrom = None
- self.rcpttos = []
- self.require_SMTPUTF8 = False
- self.num_bytes = 0
- self.set_terminator(b'\r\n')
-
- def _set_rset_state(self):
- """Reset all state variables except the greeting."""
- self._set_post_data_state()
- self.received_data = ''
- self.received_lines = []
-
-
- # Overrides base class for convenience.
- def push(self, msg):
- asynchat.async_chat.push(self, bytes(
- msg + '\r\n', 'utf-8' if self.require_SMTPUTF8 else 'ascii'))
-
- # Implementation of base class abstract method
- def collect_incoming_data(self, data):
- limit = None
- if self.smtp_state == self.COMMAND:
- limit = self.max_command_size_limit
- elif self.smtp_state == self.DATA:
- limit = self.data_size_limit
- if limit and self.num_bytes > limit:
- return
- elif limit:
- self.num_bytes += len(data)
- if self._decode_data:
- self.received_lines.append(str(data, 'utf-8'))
- else:
- self.received_lines.append(data)
-
- # Implementation of base class abstract method
- def found_terminator(self):
- line = self._emptystring.join(self.received_lines)
- print('Data:', repr(line), file=DEBUGSTREAM)
- self.received_lines = []
- if self.smtp_state == self.COMMAND:
- sz, self.num_bytes = self.num_bytes, 0
- if not line:
- self.push('500 Error: bad syntax')
- return
- if not self._decode_data:
- line = str(line, 'utf-8')
- i = line.find(' ')
- if i < 0:
- command = line.upper()
- arg = None
- else:
- command = line[:i].upper()
- arg = line[i+1:].strip()
- max_sz = (self.command_size_limits[command]
- if self.extended_smtp else self.command_size_limit)
- if sz > max_sz:
- self.push('500 Error: line too long')
- return
- method = getattr(self, 'smtp_' + command, None)
- if not method:
- self.push('500 Error: command "%s" not recognized' % command)
- return
- method(arg)
- return
- else:
- if self.smtp_state != self.DATA:
- self.push('451 Internal confusion')
- self.num_bytes = 0
- return
- if self.data_size_limit and self.num_bytes > self.data_size_limit:
- self.push('552 Error: Too much mail data')
- self.num_bytes = 0
- return
- # Remove extraneous carriage returns and de-transparency according
- # to RFC 5321, Section 4.5.2.
- data = []
- for text in line.split(self._linesep):
- if text and text[0] == self._dotsep:
- data.append(text[1:])
- else:
- data.append(text)
- self.received_data = self._newline.join(data)
- args = (self.peer, self.mailfrom, self.rcpttos, self.received_data)
- kwargs = {}
- if not self._decode_data:
- kwargs = {
- 'mail_options': self.mail_options,
- 'rcpt_options': self.rcpt_options,
- }
- status = self.smtp_server.process_message(*args, **kwargs)
- self._set_post_data_state()
- if not status:
- self.push('250 OK')
- else:
- self.push(status)
-
- # SMTP and ESMTP commands
- def smtp_HELO(self, arg):
- if not arg:
- self.push('501 Syntax: HELO hostname')
- return
- # See issue #21783 for a discussion of this behavior.
- if self.seen_greeting:
- self.push('503 Duplicate HELO/EHLO')
- return
- self._set_rset_state()
- self.seen_greeting = arg
- self.push('250 %s' % self.fqdn)
-
- def smtp_EHLO(self, arg):
- if not arg:
- self.push('501 Syntax: EHLO hostname')
- return
- # See issue #21783 for a discussion of this behavior.
- if self.seen_greeting:
- self.push('503 Duplicate HELO/EHLO')
- return
- self._set_rset_state()
- self.seen_greeting = arg
- self.extended_smtp = True
- self.push('250-%s' % self.fqdn)
- if self.data_size_limit:
- self.push('250-SIZE %s' % self.data_size_limit)
- self.command_size_limits['MAIL'] += 26
- if not self._decode_data:
- self.push('250-8BITMIME')
- if self.enable_SMTPUTF8:
- self.push('250-SMTPUTF8')
- self.command_size_limits['MAIL'] += 10
- self.push('250 HELP')
-
- def smtp_NOOP(self, arg):
- if arg:
- self.push('501 Syntax: NOOP')
- else:
- self.push('250 OK')
-
- def smtp_QUIT(self, arg):
- # args is ignored
- self.push('221 Bye')
- self.close_when_done()
-
- def _strip_command_keyword(self, keyword, arg):
- keylen = len(keyword)
- if arg[:keylen].upper() == keyword:
- return arg[keylen:].strip()
- return ''
-
- def _getaddr(self, arg):
- if not arg:
- return '', ''
- if arg.lstrip().startswith('<'):
- address, rest = get_angle_addr(arg)
- else:
- address, rest = get_addr_spec(arg)
- if not address:
- return address, rest
- return address.addr_spec, rest
-
- def _getparams(self, params):
- # Return params as dictionary. Return None if not all parameters
- # appear to be syntactically valid according to RFC 1869.
- result = {}
- for param in params:
- param, eq, value = param.partition('=')
- if not param.isalnum() or eq and not value:
- return None
- result[param] = value if eq else True
- return result
-
- def smtp_HELP(self, arg):
- if arg:
- extended = ' [SP <mail-parameters>]'
- lc_arg = arg.upper()
- if lc_arg == 'EHLO':
- self.push('250 Syntax: EHLO hostname')
- elif lc_arg == 'HELO':
- self.push('250 Syntax: HELO hostname')
- elif lc_arg == 'MAIL':
- msg = '250 Syntax: MAIL FROM: <address>'
- if self.extended_smtp:
- msg += extended
- self.push(msg)
- elif lc_arg == 'RCPT':
- msg = '250 Syntax: RCPT TO: <address>'
- if self.extended_smtp:
- msg += extended
- self.push(msg)
- elif lc_arg == 'DATA':
- self.push('250 Syntax: DATA')
- elif lc_arg == 'RSET':
- self.push('250 Syntax: RSET')
- elif lc_arg == 'NOOP':
- self.push('250 Syntax: NOOP')
- elif lc_arg == 'QUIT':
- self.push('250 Syntax: QUIT')
- elif lc_arg == 'VRFY':
- self.push('250 Syntax: VRFY <address>')
- else:
- self.push('501 Supported commands: EHLO HELO MAIL RCPT '
- 'DATA RSET NOOP QUIT VRFY')
- else:
- self.push('250 Supported commands: EHLO HELO MAIL RCPT DATA '
- 'RSET NOOP QUIT VRFY')
-
- def smtp_VRFY(self, arg):
- if arg:
- address, params = self._getaddr(arg)
- if address:
- self.push('252 Cannot VRFY user, but will accept message '
- 'and attempt delivery')
- else:
- self.push('502 Could not VRFY %s' % arg)
- else:
- self.push('501 Syntax: VRFY <address>')
-
- def smtp_MAIL(self, arg):
- if not self.seen_greeting:
- self.push('503 Error: send HELO first')
- return
- print('===> MAIL', arg, file=DEBUGSTREAM)
- syntaxerr = '501 Syntax: MAIL FROM: <address>'
- if self.extended_smtp:
- syntaxerr += ' [SP <mail-parameters>]'
- if arg is None:
- self.push(syntaxerr)
- return
- arg = self._strip_command_keyword('FROM:', arg)
- address, params = self._getaddr(arg)
- if not address:
- self.push(syntaxerr)
- return
- if not self.extended_smtp and params:
- self.push(syntaxerr)
- return
- if self.mailfrom:
- self.push('503 Error: nested MAIL command')
- return
- self.mail_options = params.upper().split()
- params = self._getparams(self.mail_options)
- if params is None:
- self.push(syntaxerr)
- return
- if not self._decode_data:
- body = params.pop('BODY', '7BIT')
- if body not in ['7BIT', '8BITMIME']:
- self.push('501 Error: BODY can only be one of 7BIT, 8BITMIME')
- return
- if self.enable_SMTPUTF8:
- smtputf8 = params.pop('SMTPUTF8', False)
- if smtputf8 is True:
- self.require_SMTPUTF8 = True
- elif smtputf8 is not False:
- self.push('501 Error: SMTPUTF8 takes no arguments')
- return
- size = params.pop('SIZE', None)
- if size:
- if not size.isdigit():
- self.push(syntaxerr)
- return
- elif self.data_size_limit and int(size) > self.data_size_limit:
- self.push('552 Error: message size exceeds fixed maximum message size')
- return
- if len(params.keys()) > 0:
- self.push('555 MAIL FROM parameters not recognized or not implemented')
- return
- self.mailfrom = address
- print('sender:', self.mailfrom, file=DEBUGSTREAM)
- self.push('250 OK')
-
- def smtp_RCPT(self, arg):
- if not self.seen_greeting:
- self.push('503 Error: send HELO first');
- return
- print('===> RCPT', arg, file=DEBUGSTREAM)
- if not self.mailfrom:
- self.push('503 Error: need MAIL command')
- return
- syntaxerr = '501 Syntax: RCPT TO: <address>'
- if self.extended_smtp:
- syntaxerr += ' [SP <mail-parameters>]'
- if arg is None:
- self.push(syntaxerr)
- return
- arg = self._strip_command_keyword('TO:', arg)
- address, params = self._getaddr(arg)
- if not address:
- self.push(syntaxerr)
- return
- if not self.extended_smtp and params:
- self.push(syntaxerr)
- return
- self.rcpt_options = params.upper().split()
- params = self._getparams(self.rcpt_options)
- if params is None:
- self.push(syntaxerr)
- return
- # XXX currently there are no options we recognize.
- if len(params.keys()) > 0:
- self.push('555 RCPT TO parameters not recognized or not implemented')
- return
- self.rcpttos.append(address)
- print('recips:', self.rcpttos, file=DEBUGSTREAM)
- self.push('250 OK')
-
- def smtp_RSET(self, arg):
- if arg:
- self.push('501 Syntax: RSET')
- return
- self._set_rset_state()
- self.push('250 OK')
-
- def smtp_DATA(self, arg):
- if not self.seen_greeting:
- self.push('503 Error: send HELO first');
- return
- if not self.rcpttos:
- self.push('503 Error: need RCPT command')
- return
- if arg:
- self.push('501 Syntax: DATA')
- return
- self.smtp_state = self.DATA
- self.set_terminator(b'\r\n.\r\n')
- self.push('354 End data with <CR><LF>.<CR><LF>')
-
- # Commands that have not been implemented
- def smtp_EXPN(self, arg):
- self.push('502 EXPN not implemented')
-
-
-class SMTPServer(asyncore.dispatcher):
- # SMTPChannel class to use for managing client connections
- channel_class = SMTPChannel
-
- def __init__(self, localaddr, remoteaddr,
- data_size_limit=DATA_SIZE_DEFAULT, map=None,
- enable_SMTPUTF8=False, decode_data=False):
- self._localaddr = localaddr
- self._remoteaddr = remoteaddr
- self.data_size_limit = data_size_limit
- self.enable_SMTPUTF8 = enable_SMTPUTF8
- self._decode_data = decode_data
- if enable_SMTPUTF8 and decode_data:
- raise ValueError("decode_data and enable_SMTPUTF8 cannot"
- " be set to True at the same time")
- asyncore.dispatcher.__init__(self, map=map)
- try:
- gai_results = socket.getaddrinfo(*localaddr,
- type=socket.SOCK_STREAM)
- self.create_socket(gai_results[0][0], gai_results[0][1])
- # try to re-use a server port if possible
- self.set_reuse_addr()
- self.bind(localaddr)
- self.listen(5)
- except:
- self.close()
- raise
- else:
- print('%s started at %s\n\tLocal addr: %s\n\tRemote addr:%s' % (
- self.__class__.__name__, time.ctime(time.time()),
- localaddr, remoteaddr), file=DEBUGSTREAM)
-
- def handle_accepted(self, conn, addr):
- print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
- channel = self.channel_class(self,
- conn,
- addr,
- self.data_size_limit,
- self._map,
- self.enable_SMTPUTF8,
- self._decode_data)
-
- # API for "doing something useful with the message"
- def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
- """Override this abstract method to handle messages from the client.
-
- peer is a tuple containing (ipaddr, port) of the client that made the
- socket connection to our smtp port.
-
- mailfrom is the raw address the client claims the message is coming
- from.
-
- rcpttos is a list of raw addresses the client wishes to deliver the
- message to.
-
- data is a string containing the entire full text of the message,
- headers (if supplied) and all. It has been `de-transparencied'
- according to RFC 821, Section 4.5.2. In other words, a line
- containing a `.' followed by other text has had the leading dot
- removed.
-
- kwargs is a dictionary containing additional information. It is
- empty if decode_data=True was given as init parameter, otherwise
- it will contain the following keys:
- 'mail_options': list of parameters to the mail command. All
- elements are uppercase strings. Example:
- ['BODY=8BITMIME', 'SMTPUTF8'].
- 'rcpt_options': same, for the rcpt command.
-
- This function should return None for a normal `250 Ok' response;
- otherwise, it should return the desired response string in RFC 821
- format.
-
- """
- raise NotImplementedError
-
-
-class DebuggingServer(SMTPServer):
-
- def _print_message_content(self, peer, data):
- inheaders = 1
- lines = data.splitlines()
- for line in lines:
- # headers first
- if inheaders and not line:
- peerheader = 'X-Peer: ' + peer[0]
- if not isinstance(data, str):
- # decoded_data=false; make header match other binary output
- peerheader = repr(peerheader.encode('utf-8'))
- print(peerheader)
- inheaders = 0
- if not isinstance(data, str):
- # Avoid spurious 'str on bytes instance' warning.
- line = repr(line)
- print(line)
-
- def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
- print('---------- MESSAGE FOLLOWS ----------')
- if kwargs:
- if kwargs.get('mail_options'):
- print('mail options: %s' % kwargs['mail_options'])
- if kwargs.get('rcpt_options'):
- print('rcpt options: %s\n' % kwargs['rcpt_options'])
- self._print_message_content(peer, data)
- print('------------ END MESSAGE ------------')
-
-
-class PureProxy(SMTPServer):
- def __init__(self, *args, **kwargs):
- if 'enable_SMTPUTF8' in kwargs and kwargs['enable_SMTPUTF8']:
- raise ValueError("PureProxy does not support SMTPUTF8.")
- super(PureProxy, self).__init__(*args, **kwargs)
-
- def process_message(self, peer, mailfrom, rcpttos, data):
- lines = data.split('\n')
- # Look for the last header
- i = 0
- for line in lines:
- if not line:
- break
- i += 1
- lines.insert(i, 'X-Peer: %s' % peer[0])
- data = NEWLINE.join(lines)
- refused = self._deliver(mailfrom, rcpttos, data)
- # TBD: what to do with refused addresses?
- print('we got some refusals:', refused, file=DEBUGSTREAM)
-
- def _deliver(self, mailfrom, rcpttos, data):
- import smtplib
- refused = {}
- try:
- s = smtplib.SMTP()
- s.connect(self._remoteaddr[0], self._remoteaddr[1])
- try:
- refused = s.sendmail(mailfrom, rcpttos, data)
- finally:
- s.quit()
- except smtplib.SMTPRecipientsRefused as e:
- print('got SMTPRecipientsRefused', file=DEBUGSTREAM)
- refused = e.recipients
- except (OSError, smtplib.SMTPException) as e:
- print('got', e.__class__, file=DEBUGSTREAM)
- # All recipients were refused. If the exception had an associated
- # error code, use it. Otherwise,fake it with a non-triggering
- # exception code.
- errcode = getattr(e, 'smtp_code', -1)
- errmsg = getattr(e, 'smtp_error', 'ignore')
- for r in rcpttos:
- refused[r] = (errcode, errmsg)
- return refused
-
-
-class Options:
- setuid = True
- classname = 'PureProxy'
- size_limit = None
- enable_SMTPUTF8 = False
-
-
-def parseargs():
- global DEBUGSTREAM
- try:
- opts, args = getopt.getopt(
- sys.argv[1:], 'nVhc:s:du',
- ['class=', 'nosetuid', 'version', 'help', 'size=', 'debug',
- 'smtputf8'])
- except getopt.error as e:
- usage(1, e)
-
- options = Options()
- for opt, arg in opts:
- if opt in ('-h', '--help'):
- usage(0)
- elif opt in ('-V', '--version'):
- print(__version__)
- sys.exit(0)
- elif opt in ('-n', '--nosetuid'):
- options.setuid = False
- elif opt in ('-c', '--class'):
- options.classname = arg
- elif opt in ('-d', '--debug'):
- DEBUGSTREAM = sys.stderr
- elif opt in ('-u', '--smtputf8'):
- options.enable_SMTPUTF8 = True
- elif opt in ('-s', '--size'):
- try:
- int_size = int(arg)
- options.size_limit = int_size
- except:
- print('Invalid size: ' + arg, file=sys.stderr)
- sys.exit(1)
-
- # parse the rest of the arguments
- if len(args) < 1:
- localspec = 'localhost:8025'
- remotespec = 'localhost:25'
- elif len(args) < 2:
- localspec = args[0]
- remotespec = 'localhost:25'
- elif len(args) < 3:
- localspec = args[0]
- remotespec = args[1]
- else:
- usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
-
- # split into host/port pairs
- i = localspec.find(':')
- if i < 0:
- usage(1, 'Bad local spec: %s' % localspec)
- options.localhost = localspec[:i]
- try:
- options.localport = int(localspec[i+1:])
- except ValueError:
- usage(1, 'Bad local port: %s' % localspec)
- i = remotespec.find(':')
- if i < 0:
- usage(1, 'Bad remote spec: %s' % remotespec)
- options.remotehost = remotespec[:i]
- try:
- options.remoteport = int(remotespec[i+1:])
- except ValueError:
- usage(1, 'Bad remote port: %s' % remotespec)
- return options
-
-
-if __name__ == '__main__':
- options = parseargs()
- # Become nobody
- classname = options.classname
- if "." in classname:
- lastdot = classname.rfind(".")
- mod = __import__(classname[:lastdot], globals(), locals(), [""])
- classname = classname[lastdot+1:]
- else:
- import __main__ as mod
- class_ = getattr(mod, classname)
- proxy = class_((options.localhost, options.localport),
- (options.remotehost, options.remoteport),
- options.size_limit, enable_SMTPUTF8=options.enable_SMTPUTF8)
- if options.setuid:
- try:
- import pwd
- except ImportError:
- print('Cannot import module "pwd"; try running with -n option.', file=sys.stderr)
- sys.exit(1)
- nobody = pwd.getpwnam('nobody')[2]
- try:
- os.setuid(nobody)
- except PermissionError:
- print('Cannot setuid "nobody"; try running with -n option.', file=sys.stderr)
- sys.exit(1)
- try:
- asyncore.loop()
- except KeyboardInterrupt:
- pass