mirror of
https://github.com/python/cpython.git
synced 2026-01-06 15:32:22 +00:00
Implementation for issue 4184
Changes the previously private attributes to make them public, increasing the potential for extending the library in user code. Backward-compatible and documented.
This commit is contained in:
parent
756f547b9a
commit
803ef8a694
2 changed files with 266 additions and 44 deletions
|
|
@ -10,10 +10,14 @@
|
|||
|
||||
|
||||
|
||||
This module offers several classes to implement SMTP servers. One is a generic
|
||||
This module offers several classes to implement SMTP (email) servers.
|
||||
|
||||
Several server implementations are present; one is a generic
|
||||
do-nothing implementation, which can be overridden, while the other two offer
|
||||
specific mail-sending strategies.
|
||||
|
||||
Additionally the SMTPChannel may be extended to implement very specific
|
||||
interaction behaviour with SMTP clients.
|
||||
|
||||
SMTPServer Objects
|
||||
------------------
|
||||
|
|
@ -26,7 +30,6 @@ SMTPServer Objects
|
|||
inherits from :class:`asyncore.dispatcher`, and so will insert itself into
|
||||
:mod:`asyncore`'s event loop on instantiation.
|
||||
|
||||
|
||||
.. method:: process_message(peer, mailfrom, rcpttos, data)
|
||||
|
||||
Raise :exc:`NotImplementedError` exception. Override this in subclasses to
|
||||
|
|
@ -37,6 +40,11 @@ SMTPServer Objects
|
|||
containing the contents of the e-mail (which should be in :rfc:`2822`
|
||||
format).
|
||||
|
||||
.. attribute:: channel_class
|
||||
|
||||
Override this in subclasses to use a custom :class:`SMTPChannel` for
|
||||
managing SMTP clients.
|
||||
|
||||
|
||||
DebuggingServer Objects
|
||||
-----------------------
|
||||
|
|
@ -71,3 +79,91 @@ MailmanProxy Objects
|
|||
running this has a good chance to make you into an open relay, so please be
|
||||
careful.
|
||||
|
||||
SMTPChannel Objects
|
||||
-------------------
|
||||
|
||||
.. class:: SMTPChannel(server, conn, addr)
|
||||
|
||||
Create a new :class:`SMTPChannel` object which manages the communication
|
||||
between the server and a single SMTP client.
|
||||
|
||||
To use a custom SMTPChannel implementation you need to override the
|
||||
:attr:`SMTPServer.channel_class` of your :class:`SMTPServer`.
|
||||
|
||||
The :class:`SMTPChannel` has the following instance variables:
|
||||
|
||||
.. attribute:: smtp_server
|
||||
|
||||
Holds the :class:`SMTPServer` that spawned this channel.
|
||||
|
||||
.. attribute:: conn
|
||||
|
||||
Holds the socket object connecting to the client.
|
||||
|
||||
.. attribute:: addr
|
||||
|
||||
Holds the address of the client, the second value returned by
|
||||
socket.accept()
|
||||
|
||||
.. attribute:: received_lines
|
||||
|
||||
Holds a list of the line strings (decoded using UTF-8) received from
|
||||
the client. The lines have their "\r\n" line ending translated to "\n".
|
||||
|
||||
.. attribute:: smtp_state
|
||||
|
||||
Holds the current state of the channel. This will be either
|
||||
:attr:`COMMAND` initially and then :attr:`DATA` after the client sends
|
||||
a "DATA" line.
|
||||
|
||||
.. attribute:: seen_greeting
|
||||
|
||||
Holds a string containing the greeting sent by the client in its "HELO".
|
||||
|
||||
.. attribute:: mailfrom
|
||||
|
||||
Holds a string containing the address identified in the "MAIL FROM:" line
|
||||
from the client.
|
||||
|
||||
.. attribute:: rcpttos
|
||||
|
||||
Holds a list of strings containing the addresses identified in the
|
||||
"RCPT TO:" lines from the client.
|
||||
|
||||
.. attribute:: received_data
|
||||
|
||||
Holds a string containing all of the data sent by the client during the
|
||||
DATA state, up to but not including the terminating "\r\n.\r\n".
|
||||
|
||||
.. attribute:: fqdn
|
||||
|
||||
Holds the fully-qualified domain name of the server as returned by
|
||||
``socket.getfqdn()``.
|
||||
|
||||
.. attribute:: peer
|
||||
|
||||
Holds the name of the client peer as returned by ``conn.getpeername()``
|
||||
where ``conn`` is :attr:`conn`.
|
||||
|
||||
The :class:`SMTPChannel` operates by invoking methods named ``smtp_<command>``
|
||||
upon reception of a command line from the client. Built into the base
|
||||
:class:`SMTPChannel` class are methods for handling the following commands
|
||||
(and responding to them appropriately):
|
||||
|
||||
======== ===================================================================
|
||||
Command Action taken
|
||||
======== ===================================================================
|
||||
HELO Accepts the greeting from the client and stores it in
|
||||
:attr:`seen_greeting`.
|
||||
NOOP Takes no action.
|
||||
QUIT Closes the connection cleanly.
|
||||
MAIL Accepts the "MAIL FROM:" syntax and stores the supplied address as
|
||||
:attr:`mailfrom`.
|
||||
RCPT Accepts the "RCPT TO:" syntax and stores the supplied addresses in
|
||||
the :attr:`rcpttos` list.
|
||||
RSET Resets the :attr:`mailfrom`, :attr:`rcpttos`, and
|
||||
:attr:`received_data`, but not the greeting.
|
||||
DATA Sets the internal state to :attr:`DATA` and stores remaining lines
|
||||
from the client in :attr:`received_data` until the terminator
|
||||
"\r\n.\r\n" is received.
|
||||
======== ===================================================================
|
||||
210
Lib/smtpd.py
210
Lib/smtpd.py
|
|
@ -78,6 +78,7 @@
|
|||
import socket
|
||||
import asyncore
|
||||
import asynchat
|
||||
from warnings import warn
|
||||
|
||||
__all__ = ["SMTPServer","DebuggingServer","PureProxy","MailmanProxy"]
|
||||
|
||||
|
|
@ -111,35 +112,157 @@ class SMTPChannel(asynchat.async_chat):
|
|||
|
||||
def __init__(self, server, conn, addr):
|
||||
asynchat.async_chat.__init__(self, conn)
|
||||
self.__server = server
|
||||
self.__conn = conn
|
||||
self.__addr = addr
|
||||
self.__line = []
|
||||
self.__state = self.COMMAND
|
||||
self.__greeting = 0
|
||||
self.__mailfrom = None
|
||||
self.__rcpttos = []
|
||||
self.__data = ''
|
||||
self.__fqdn = socket.getfqdn()
|
||||
self.__peer = conn.getpeername()
|
||||
print('Peer:', repr(self.__peer), file=DEBUGSTREAM)
|
||||
self.push('220 %s %s' % (self.__fqdn, __version__))
|
||||
self.smtp_server = server
|
||||
self.conn = conn
|
||||
self.addr = addr
|
||||
self.received_lines = []
|
||||
self.smtp_state = self.COMMAND
|
||||
self.seen_greeting = ''
|
||||
self.mailfrom = None
|
||||
self.rcpttos = []
|
||||
self.received_data = ''
|
||||
self.fqdn = socket.getfqdn()
|
||||
self.peer = conn.getpeername()
|
||||
print('Peer:', repr(self.peer), file=DEBUGSTREAM)
|
||||
self.push('220 %s %s' % (self.fqdn, __version__))
|
||||
self.set_terminator(b'\r\n')
|
||||
|
||||
# properties for backwards-compatibility
|
||||
@property
|
||||
def __server(self):
|
||||
warn("Access to __server attribute on SMTPChannel is deprecated, "
|
||||
"use 'smtp_server' instead", PendingDeprecationWarning, 2)
|
||||
return self.smtp_server
|
||||
@__server.setter
|
||||
def __server(self, value):
|
||||
warn("Setting __server attribute on SMTPChannel is deprecated, "
|
||||
"set 'smtp_server' instead", PendingDeprecationWarning, 2)
|
||||
self.smtp_server = value
|
||||
|
||||
@property
|
||||
def __line(self):
|
||||
warn("Access to __line attribute on SMTPChannel is deprecated, "
|
||||
"use 'received_lines' instead", PendingDeprecationWarning, 2)
|
||||
return self.received_lines
|
||||
@__line.setter
|
||||
def __line(self, value):
|
||||
warn("Setting __line attribute on SMTPChannel is deprecated, "
|
||||
"set 'received_lines' instead", PendingDeprecationWarning, 2)
|
||||
self.received_lines = value
|
||||
|
||||
@property
|
||||
def __state(self):
|
||||
warn("Access to __state attribute on SMTPChannel is deprecated, "
|
||||
"use 'smtp_state' instead", PendingDeprecationWarning, 2)
|
||||
return self.smtp_state
|
||||
@__state.setter
|
||||
def __state(self, value):
|
||||
warn("Setting __state attribute on SMTPChannel is deprecated, "
|
||||
"set 'smtp_state' instead", PendingDeprecationWarning, 2)
|
||||
self.smtp_state = value
|
||||
|
||||
@property
|
||||
def __greeting(self):
|
||||
warn("Access to __greeting attribute on SMTPChannel is deprecated, "
|
||||
"use 'seen_greeting' instead", PendingDeprecationWarning, 2)
|
||||
return self.seen_greeting
|
||||
@__greeting.setter
|
||||
def __greeting(self, value):
|
||||
warn("Setting __greeting attribute on SMTPChannel is deprecated, "
|
||||
"set 'seen_greeting' instead", PendingDeprecationWarning, 2)
|
||||
self.seen_greeting = value
|
||||
|
||||
@property
|
||||
def __mailfrom(self):
|
||||
warn("Access to __mailfrom attribute on SMTPChannel is deprecated, "
|
||||
"use 'mailfrom' instead", PendingDeprecationWarning, 2)
|
||||
return self.mailfrom
|
||||
@__mailfrom.setter
|
||||
def __mailfrom(self, value):
|
||||
warn("Setting __mailfrom attribute on SMTPChannel is deprecated, "
|
||||
"set 'mailfrom' instead", PendingDeprecationWarning, 2)
|
||||
self.mailfrom = value
|
||||
|
||||
@property
|
||||
def __rcpttos(self):
|
||||
warn("Access to __rcpttos attribute on SMTPChannel is deprecated, "
|
||||
"use 'rcpttos' instead", PendingDeprecationWarning, 2)
|
||||
return self.rcpttos
|
||||
@__rcpttos.setter
|
||||
def __rcpttos(self, value):
|
||||
warn("Setting __rcpttos attribute on SMTPChannel is deprecated, "
|
||||
"set 'rcpttos' instead", PendingDeprecationWarning, 2)
|
||||
self.rcpttos = value
|
||||
|
||||
@property
|
||||
def __data(self):
|
||||
warn("Access to __data attribute on SMTPChannel is deprecated, "
|
||||
"use 'received_data' instead", PendingDeprecationWarning, 2)
|
||||
return self.received_data
|
||||
@__data.setter
|
||||
def __data(self, value):
|
||||
warn("Setting __data attribute on SMTPChannel is deprecated, "
|
||||
"set 'received_data' instead", PendingDeprecationWarning, 2)
|
||||
self.received_data = value
|
||||
|
||||
@property
|
||||
def __fqdn(self):
|
||||
warn("Access to __fqdn attribute on SMTPChannel is deprecated, "
|
||||
"use 'fqdn' instead", PendingDeprecationWarning, 2)
|
||||
return self.fqdn
|
||||
@__fqdn.setter
|
||||
def __fqdn(self, value):
|
||||
warn("Setting __fqdn attribute on SMTPChannel is deprecated, "
|
||||
"set 'fqdn' instead", PendingDeprecationWarning, 2)
|
||||
self.fqdn = value
|
||||
|
||||
@property
|
||||
def __peer(self):
|
||||
warn("Access to __peer attribute on SMTPChannel is deprecated, "
|
||||
"use 'peer' instead", PendingDeprecationWarning, 2)
|
||||
return self.peer
|
||||
@__peer.setter
|
||||
def __peer(self, value):
|
||||
warn("Setting __peer attribute on SMTPChannel is deprecated, "
|
||||
"set 'peer' instead", PendingDeprecationWarning, 2)
|
||||
self.peer = value
|
||||
|
||||
@property
|
||||
def __conn(self):
|
||||
warn("Access to __conn attribute on SMTPChannel is deprecated, "
|
||||
"use 'conn' instead", PendingDeprecationWarning, 2)
|
||||
return self.conn
|
||||
@__conn.setter
|
||||
def __conn(self, value):
|
||||
warn("Setting __conn attribute on SMTPChannel is deprecated, "
|
||||
"set 'conn' instead", PendingDeprecationWarning, 2)
|
||||
self.conn = value
|
||||
|
||||
@property
|
||||
def __addr(self):
|
||||
warn("Access to __addr attribute on SMTPChannel is deprecated, "
|
||||
"use 'addr' instead", PendingDeprecationWarning, 2)
|
||||
return self.addr
|
||||
@__addr.setter
|
||||
def __addr(self, value):
|
||||
warn("Setting __addr attribute on SMTPChannel is deprecated, "
|
||||
"set 'addr' instead", PendingDeprecationWarning, 2)
|
||||
self.addr = value
|
||||
|
||||
# Overrides base class for convenience
|
||||
def push(self, msg):
|
||||
asynchat.async_chat.push(self, bytes(msg + '\r\n', 'ascii'))
|
||||
|
||||
# Implementation of base class abstract method
|
||||
def collect_incoming_data(self, data):
|
||||
self.__line.append(str(data, "utf8"))
|
||||
self.received_lines.append(str(data, "utf8"))
|
||||
|
||||
# Implementation of base class abstract method
|
||||
def found_terminator(self):
|
||||
line = EMPTYSTRING.join(self.__line)
|
||||
line = EMPTYSTRING.join(self.received_lines)
|
||||
print('Data:', repr(line), file=DEBUGSTREAM)
|
||||
self.__line = []
|
||||
if self.__state == self.COMMAND:
|
||||
self.received_lines = []
|
||||
if self.smtp_state == self.COMMAND:
|
||||
if not line:
|
||||
self.push('500 Error: bad syntax')
|
||||
return
|
||||
|
|
@ -158,7 +281,7 @@ def found_terminator(self):
|
|||
method(arg)
|
||||
return
|
||||
else:
|
||||
if self.__state != self.DATA:
|
||||
if self.smtp_state != self.DATA:
|
||||
self.push('451 Internal confusion')
|
||||
return
|
||||
# Remove extraneous carriage returns and de-transparency according
|
||||
|
|
@ -169,14 +292,14 @@ def found_terminator(self):
|
|||
data.append(text[1:])
|
||||
else:
|
||||
data.append(text)
|
||||
self.__data = NEWLINE.join(data)
|
||||
status = self.__server.process_message(self.__peer,
|
||||
self.__mailfrom,
|
||||
self.__rcpttos,
|
||||
self.__data)
|
||||
self.__rcpttos = []
|
||||
self.__mailfrom = None
|
||||
self.__state = self.COMMAND
|
||||
self.received_data = NEWLINE.join(data)
|
||||
status = self.__server.process_message(self.peer,
|
||||
self.mailfrom,
|
||||
self.rcpttos,
|
||||
self.received_data)
|
||||
self.rcpttos = []
|
||||
self.mailfrom = None
|
||||
self.smtp_state = self.COMMAND
|
||||
self.set_terminator(b'\r\n')
|
||||
if not status:
|
||||
self.push('250 Ok')
|
||||
|
|
@ -188,11 +311,11 @@ def smtp_HELO(self, arg):
|
|||
if not arg:
|
||||
self.push('501 Syntax: HELO hostname')
|
||||
return
|
||||
if self.__greeting:
|
||||
if self.seen_greeting:
|
||||
self.push('503 Duplicate HELO/EHLO')
|
||||
else:
|
||||
self.__greeting = arg
|
||||
self.push('250 %s' % self.__fqdn)
|
||||
self.seen_greeting = arg
|
||||
self.push('250 %s' % self.fqdn)
|
||||
|
||||
def smtp_NOOP(self, arg):
|
||||
if arg:
|
||||
|
|
@ -225,24 +348,24 @@ def smtp_MAIL(self, arg):
|
|||
if not address:
|
||||
self.push('501 Syntax: MAIL FROM:<address>')
|
||||
return
|
||||
if self.__mailfrom:
|
||||
if self.mailfrom:
|
||||
self.push('503 Error: nested MAIL command')
|
||||
return
|
||||
self.__mailfrom = address
|
||||
print('sender:', self.__mailfrom, file=DEBUGSTREAM)
|
||||
self.mailfrom = address
|
||||
print('sender:', self.mailfrom, file=DEBUGSTREAM)
|
||||
self.push('250 Ok')
|
||||
|
||||
def smtp_RCPT(self, arg):
|
||||
print('===> RCPT', arg, file=DEBUGSTREAM)
|
||||
if not self.__mailfrom:
|
||||
if not self.mailfrom:
|
||||
self.push('503 Error: need MAIL command')
|
||||
return
|
||||
address = self.__getaddr('TO:', arg) if arg else None
|
||||
if not address:
|
||||
self.push('501 Syntax: RCPT TO: <address>')
|
||||
return
|
||||
self.__rcpttos.append(address)
|
||||
print('recips:', self.__rcpttos, file=DEBUGSTREAM)
|
||||
self.rcpttos.append(address)
|
||||
print('recips:', self.rcpttos, file=DEBUGSTREAM)
|
||||
self.push('250 Ok')
|
||||
|
||||
def smtp_RSET(self, arg):
|
||||
|
|
@ -250,26 +373,29 @@ def smtp_RSET(self, arg):
|
|||
self.push('501 Syntax: RSET')
|
||||
return
|
||||
# Resets the sender, recipients, and data, but not the greeting
|
||||
self.__mailfrom = None
|
||||
self.__rcpttos = []
|
||||
self.__data = ''
|
||||
self.__state = self.COMMAND
|
||||
self.mailfrom = None
|
||||
self.rcpttos = []
|
||||
self.received_data = ''
|
||||
self.smtp_state = self.COMMAND
|
||||
self.push('250 Ok')
|
||||
|
||||
def smtp_DATA(self, arg):
|
||||
if not self.__rcpttos:
|
||||
if not self.rcpttos:
|
||||
self.push('503 Error: need RCPT command')
|
||||
return
|
||||
if arg:
|
||||
self.push('501 Syntax: DATA')
|
||||
return
|
||||
self.__state = self.DATA
|
||||
self.smtp_state = self.DATA
|
||||
self.set_terminator(b'\r\n.\r\n')
|
||||
self.push('354 End data with <CR><LF>.<CR><LF>')
|
||||
|
||||
|
||||
|
||||
class SMTPServer(asyncore.dispatcher):
|
||||
# SMTPChannel class to use for managing client connections
|
||||
channel_class = SMTPChannel
|
||||
|
||||
def __init__(self, localaddr, remoteaddr):
|
||||
self._localaddr = localaddr
|
||||
self._remoteaddr = remoteaddr
|
||||
|
|
@ -291,7 +417,7 @@ def __init__(self, localaddr, remoteaddr):
|
|||
def handle_accept(self):
|
||||
conn, addr = self.accept()
|
||||
print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
|
||||
channel = SMTPChannel(self, conn, addr)
|
||||
channel = self.channel_class(self, conn, addr)
|
||||
|
||||
# API for "doing something useful with the message"
|
||||
def process_message(self, peer, mailfrom, rcpttos, data):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue