[Python-modules-commits] [python-aiosmtpd] 01/03: New upstream version 1.1
Pierre-Elliott Bécue
peb-guest at moszumanska.debian.org
Thu Jul 6 21:06:58 UTC 2017
This is an automated email from the git hooks/post-receive script.
peb-guest pushed a commit to branch master
in repository python-aiosmtpd.
commit 9eb16b8caa08ad14068c661a14d9d43a94f0f306
Author: Pierre-Elliott Bécue <becue at crans.org>
Date: Thu Jul 6 22:44:53 2017 +0200
New upstream version 1.1
---
.appveyor.yml | 5 -
.coverage.ini | 1 -
.travis.yml | 2 -
MANIFEST.in | 2 +-
PKG-INFO | 10 +-
README.rst | 9 +-
_static/.ignore | 0
aiosmtpd.egg-info/PKG-INFO | 10 +-
aiosmtpd.egg-info/SOURCES.txt | 5 +-
aiosmtpd/controller.py | 33 ++--
aiosmtpd/docs/.gitignore | 1 -
aiosmtpd/docs/Makefile | 177 -----------------
aiosmtpd/docs/NEWS.rst | 27 +++
aiosmtpd/docs/controller.rst | 12 +-
aiosmtpd/docs/handlers.rst | 2 +-
aiosmtpd/docs/manpage.rst | 66 +++++++
aiosmtpd/docs/migrating.rst | 6 +-
aiosmtpd/docs/smtp.rst | 19 +-
aiosmtpd/handlers.py | 20 +-
aiosmtpd/lmtp.py | 21 +-
aiosmtpd/main.py | 13 +-
aiosmtpd/smtp.py | 411 +++++++++++++++++++---------------------
aiosmtpd/tests/test_handlers.py | 46 ++---
aiosmtpd/tests/test_lmtp.py | 10 +
aiosmtpd/tests/test_main.py | 164 ++++++++--------
aiosmtpd/tests/test_smtp.py | 159 +++++++++++++---
aiosmtpd/tests/test_smtps.py | 62 ++++++
aiosmtpd/tests/test_starttls.py | 45 ++---
setup.cfg | 3 +-
setup.py | 7 +-
tox.ini | 8 +-
31 files changed, 693 insertions(+), 663 deletions(-)
diff --git a/.appveyor.yml b/.appveyor.yml
index 65f6768..194621e 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -7,13 +7,8 @@ environment:
# For Python versions available on Appveyor, see
# http://www.appveyor.com/docs/installed-software#python
- - PYTHON: "C:\\Python34"
- INTERP: "py34"
- PYTHON: "C:\\Python35"
INTERP: "py35"
- - PYTHON: "C:\\Python34-x64"
- INTERP: "py34"
- DISTUTILS_USE_SDK: "1"
- PYTHON: "C:\\Python35-x64"
INTERP: "py35"
- PYTHON: "C:\\Python36-x64"
diff --git a/.coverage.ini b/.coverage.ini
index 01f7db9..0bdcb4e 100644
--- a/.coverage.ini
+++ b/.coverage.ini
@@ -15,5 +15,4 @@ source =
[report]
exclude_lines =
pragma: nocover
- pragma: no${INTERP}
pragma: no${PLATFORM}
diff --git a/.travis.yml b/.travis.yml
index a4511c7..6f1e947 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,8 +5,6 @@ install:
- pip install -r aiosmtpd.egg-info/requires.txt
matrix:
include:
- - python: "3.4"
- env: INTERP=py34 PYTHONASYNCIODEBUG=1
- python: "3.5"
env: INTERP=py35 PYTHONASYNCIODEBUG=1
- python: "3.6"
diff --git a/MANIFEST.in b/MANIFEST.in
index 1b19070..6c44541 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
include *.py MANIFEST.in
global-include *.txt *.rst *.ini *.yml *.cfg *.crt *.key
-exclude .gitignore
+global-exclude .gitignore
prune build
diff --git a/PKG-INFO b/PKG-INFO
index f78f704..beb4413 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,9 +1,9 @@
Metadata-Version: 1.1
Name: aiosmtpd
-Version: 1.0
+Version: 1.1
Summary: aiosmtpd - asyncio based SMTP server
-Home-page: https://github.com/aio-libs/aiosmtpd
-Author: https://github.com/aio-libs
+Home-page: http://aiosmtpd.readthedocs.io/
+Author: UNKNOWN
Author-email: UNKNOWN
License: http://www.apache.org/licenses/LICENSE-2.0
Description: This is a server for SMTP and related protocols, similar in utility to the
@@ -13,8 +13,6 @@ Keywords: email
Platform: UNKNOWN
Classifier: License :: OSI Approved
Classifier: Intended Audience :: Developers
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
-Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Communications :: Email :: Mail Transport Agents
Classifier: Framework :: AsyncIO
diff --git a/README.rst b/README.rst
index 7789d13..2d1db4d 100644
--- a/README.rst
+++ b/README.rst
@@ -25,10 +25,8 @@ protocols.
Requirements
============
-You need at least Python 3.4 to use this library. Python 3.3 might work if
-you install the standalone `asyncio <https://pypi.python.org/pypi/asyncio>`__
-library, but this combination is untested and unsupported. Python 3.5 or
-newer is required for SSL support. Both Windows and \*nix are supported.
+You need at least Python 3.5 to use this library. Both Windows and \*nix are
+supported.
License
@@ -74,7 +72,7 @@ Developing
==========
You'll need the `tox <https://pypi.python.org/pypi/tox>`__ tool to run the
-test suite for Python 3.4, 3.5, and 3.6. Once you've got that, run::
+test suite for Python 3. Once you've got that, run::
$ tox
@@ -107,6 +105,7 @@ Contents
aiosmtpd/docs/lmtp
aiosmtpd/docs/handlers
aiosmtpd/docs/migrating
+ aiosmtpd/docs/manpage
aiosmtpd/docs/NEWS
diff --git a/_static/.ignore b/_static/.ignore
deleted file mode 100644
index e69de29..0000000
diff --git a/aiosmtpd.egg-info/PKG-INFO b/aiosmtpd.egg-info/PKG-INFO
index f78f704..beb4413 100644
--- a/aiosmtpd.egg-info/PKG-INFO
+++ b/aiosmtpd.egg-info/PKG-INFO
@@ -1,9 +1,9 @@
Metadata-Version: 1.1
Name: aiosmtpd
-Version: 1.0
+Version: 1.1
Summary: aiosmtpd - asyncio based SMTP server
-Home-page: https://github.com/aio-libs/aiosmtpd
-Author: https://github.com/aio-libs
+Home-page: http://aiosmtpd.readthedocs.io/
+Author: UNKNOWN
Author-email: UNKNOWN
License: http://www.apache.org/licenses/LICENSE-2.0
Description: This is a server for SMTP and related protocols, similar in utility to the
@@ -13,8 +13,6 @@ Keywords: email
Platform: UNKNOWN
Classifier: License :: OSI Approved
Classifier: Intended Audience :: Developers
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
-Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Communications :: Email :: Mail Transport Agents
Classifier: Framework :: AsyncIO
diff --git a/aiosmtpd.egg-info/SOURCES.txt b/aiosmtpd.egg-info/SOURCES.txt
index 5573d7f..56c4a33 100644
--- a/aiosmtpd.egg-info/SOURCES.txt
+++ b/aiosmtpd.egg-info/SOURCES.txt
@@ -9,7 +9,6 @@ setup.py
setup_helpers.py
tox.ini
unittest.cfg
-_static/.ignore
aiosmtpd/__init__.py
aiosmtpd/__main__.py
aiosmtpd/controller.py
@@ -23,8 +22,6 @@ aiosmtpd.egg-info/dependency_links.txt
aiosmtpd.egg-info/entry_points.txt
aiosmtpd.egg-info/requires.txt
aiosmtpd.egg-info/top_level.txt
-aiosmtpd/docs/.gitignore
-aiosmtpd/docs/Makefile
aiosmtpd/docs/NEWS.rst
aiosmtpd/docs/__init__.py
aiosmtpd/docs/cli.rst
@@ -33,6 +30,7 @@ aiosmtpd/docs/controller.rst
aiosmtpd/docs/handlers.rst
aiosmtpd/docs/intro.rst
aiosmtpd/docs/lmtp.rst
+aiosmtpd/docs/manpage.rst
aiosmtpd/docs/migrating.rst
aiosmtpd/docs/smtp.rst
aiosmtpd/testing/__init__.py
@@ -43,6 +41,7 @@ aiosmtpd/tests/test_lmtp.py
aiosmtpd/tests/test_main.py
aiosmtpd/tests/test_server.py
aiosmtpd/tests/test_smtp.py
+aiosmtpd/tests/test_smtps.py
aiosmtpd/tests/test_starttls.py
aiosmtpd/tests/certs/__init__.py
aiosmtpd/tests/certs/server.crt
diff --git a/aiosmtpd/controller.py b/aiosmtpd/controller.py
index 8771cb1..c529ff9 100644
--- a/aiosmtpd/controller.py
+++ b/aiosmtpd/controller.py
@@ -1,42 +1,26 @@
import os
-import socket
import asyncio
import threading
from aiosmtpd.smtp import SMTP
from public import public
-try:
- from socket import socketpair
-except ImportError: # pragma: nocover
- from asyncio.windows_utils import socketpair
-
@public
class Controller:
def __init__(self, handler, loop=None, hostname=None, port=8025, *,
- ready_timeout=1.0, enable_SMTPUTF8=True):
+ ready_timeout=1.0, enable_SMTPUTF8=True, ssl_context=None):
self.handler = handler
self.hostname = '::1' if hostname is None else hostname
self.port = port
self.enable_SMTPUTF8 = enable_SMTPUTF8
+ self.ssl_context = ssl_context
self.loop = asyncio.new_event_loop() if loop is None else loop
self.server = None
self._thread = None
self._thread_exception = None
self.ready_timeout = os.getenv(
'AIOSMTPD_CONTROLLER_TIMEOUT', ready_timeout)
- # For exiting the loop.
- self._rsock, self._wsock = socketpair()
- self.loop.add_reader(self._rsock, self._reader)
-
- def _reader(self):
- self.loop.remove_reader(self._rsock)
- self.loop.stop()
- for task in asyncio.Task.all_tasks(self.loop):
- task.cancel()
- self._rsock.close()
- self._wsock.close()
def factory(self):
"""Allow subclasses to customize the handler/server creation."""
@@ -47,8 +31,9 @@ class Controller:
try:
self.server = self.loop.run_until_complete(
self.loop.create_server(
- self.factory, host=self.hostname, port=self.port))
- except socket.error as error:
+ self.factory, host=self.hostname, port=self.port,
+ ssl=self.ssl_context))
+ except Exception as error:
self._thread_exception = error
return
self.loop.call_soon(ready_event.set)
@@ -69,7 +54,13 @@ class Controller:
if self._thread_exception is not None:
raise self._thread_exception
+ def _stop(self):
+ self.loop.stop()
+ for task in asyncio.Task.all_tasks(self.loop):
+ task.cancel()
+
def stop(self):
assert self._thread is not None, 'SMTP daemon not running'
- self._wsock.send(b'x')
+ self.loop.call_soon_threadsafe(self._stop)
self._thread.join()
+ self._thread = None
diff --git a/aiosmtpd/docs/.gitignore b/aiosmtpd/docs/.gitignore
deleted file mode 100644
index e35d885..0000000
--- a/aiosmtpd/docs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-_build
diff --git a/aiosmtpd/docs/Makefile b/aiosmtpd/docs/Makefile
deleted file mode 100644
index be5d54d..0000000
--- a/aiosmtpd/docs/Makefile
+++ /dev/null
@@ -1,177 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = _build
-
-# User-friendly check for sphinx-build
-ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
-endif
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
-
-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " texinfo to make Texinfo files"
- @echo " info to make Texinfo files and run them through makeinfo"
- @echo " gettext to make PO message catalogs"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " xml to make Docutils-native XML files"
- @echo " pseudoxml to make pseudoxml-XML files for display purposes"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/aiosmtpd.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/aiosmtpd.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/aiosmtpd"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/aiosmtpd"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-latexpdfja:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through platex and dvipdfmx..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo
- @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
- @echo "Run \`make' in that directory to run these through makeinfo" \
- "(use \`make info' here to do that automatically)."
-
-info:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo "Running Texinfo files through makeinfo..."
- make -C $(BUILDDIR)/texinfo info
- @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
- $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
- @echo
- @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
-
-xml:
- $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
- @echo
- @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
-
-pseudoxml:
- $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
- @echo
- @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/aiosmtpd/docs/NEWS.rst b/aiosmtpd/docs/NEWS.rst
index 66084eb..5260f73 100644
--- a/aiosmtpd/docs/NEWS.rst
+++ b/aiosmtpd/docs/NEWS.rst
@@ -2,6 +2,33 @@
NEWS for aiosmtpd
===================
+1.1 (2017-07-06)
+================
+* Drop support for Python 3.4.
+* As per RFC 5321, §4.1.4, multiple ``HELO`` / ``EHLO`` commands in the same
+ session are semantically equivalent to ``RSET``. (Closes #78)
+* As per RFC 5321, $4.1.1.9, ``NOOP`` takes an optional argument, which is
+ ignored. **API BREAK** If you have a handler that implements
+ ``handle_NOOP()``, it previously took zero arguments but now requires a
+ single argument. (Closes #107)
+* The command line options ``--version`` / ``-v`` has been added to print the
+ package's current version number. (Closes #111)
+* General improvements in the ``Controller`` class. (Closes #104)
+* When aiosmtpd handles a ``STARTTLS`` it must arrange for the original
+ transport to be closed when the wrapped transport is closed. This fixes a
+ hidden exception which occurs when an EOF is received on the original
+ tranport after the connection is lost. (Closes #83)
+* Widen the catch of ``ConnectionResetError`` and ``CancelledError`` to also
+ catch such errors from handler methods. (Closes #110)
+* Added a manpage for the ``aiosmtpd`` command line script. (Closes #116)
+* Added much better support for the ``HELP``. There's a new decorator called
+ ``@syntax()`` which you can use in derived classes to decorate ``smtp_*()``
+ methods. These then show up in ``HELP`` responses. This also fixes
+ ``HELP`` responses for the ``LMTP`` subclass. (Closes #113)
+* The ``Controller`` class now takes an optional keyword argument
+ ``ssl_context`` which is passed directly to the asyncio ``create_server()``
+ call.
+
1.0 (2017-05-15)
================
* Release.
diff --git a/aiosmtpd/docs/controller.rst b/aiosmtpd/docs/controller.rst
index 72e860c..ca134b6 100644
--- a/aiosmtpd/docs/controller.rst
+++ b/aiosmtpd/docs/controller.rst
@@ -30,15 +30,13 @@ to the console. Start by implementing a handler as follows::
>>> import asyncio
>>> class ExampleHandler:
- ... @asyncio.coroutine
- ... def handle_RCPT(self, server, session, envelope, address, rcpt_options):
+ ... async def handle_RCPT(self, server, session, envelope, address, rcpt_options):
... if not address.endswith('@example.com'):
... return '550 not relaying to that domain'
... envelope.rcpt_tos.append(address)
... return '250 OK'
...
- ... @asyncio.coroutine
- ... def handle_DATA(self, server, session, envelope):
+ ... async def handle_DATA(self, server, session, envelope):
... print('Message from %s' % envelope.mail_from)
... print('Message for %s' % envelope.rcpt_tos)
... print('Message data:\n')
@@ -163,7 +161,7 @@ The EHLO response does not include the ``SMTPUTF8`` ESMTP option.
Controller API
==============
-.. class:: Controller(handler, loop=None, hostname=None, port=8025, *, ready_timeout=1.0, enable_SMTPUTF8=True)
+.. class:: Controller(handler, loop=None, hostname=None, port=8025, *, ready_timeout=1.0, enable_SMTPUTF8=True, ssl_context=None)
*handler* is an instance of a :ref:`handler <handlers>` class.
@@ -184,6 +182,10 @@ Controller API
option is returned to the client in response to ``EHLO``, and UTF-8 content
is accepted.
+ *ssl_context* is a ``SSLContext`` that will be used by the loops
+ server and is passed directly to :meth:`AbstractEventLoop.create_server`
+ method.
+
.. attribute:: handler
The instance of the event *handler* passed to the constructor.
diff --git a/aiosmtpd/docs/handlers.rst b/aiosmtpd/docs/handlers.rst
index ea1bfa7..6a1657e 100644
--- a/aiosmtpd/docs/handlers.rst
+++ b/aiosmtpd/docs/handlers.rst
@@ -52,7 +52,7 @@ The following hooks are currently defined:
``server.push(status)`` before returning ``250 HELP`` as the final
response.
-``handle_NOOP(server, session, envelope)``
+``handle_NOOP(server, session, envelope, arg)``
Called during ``NOOP``.
``handle_QUIT(server, session, envelope)``
diff --git a/aiosmtpd/docs/manpage.rst b/aiosmtpd/docs/manpage.rst
new file mode 100644
index 0000000..ddf099a
--- /dev/null
+++ b/aiosmtpd/docs/manpage.rst
@@ -0,0 +1,66 @@
+==========
+ aiosmtpd
+==========
+
+-----------------------------------------------------
+Provide a Simple Mail Transfer Procotol (SMTP) server
+-----------------------------------------------------
+
+:Author: The aiosmtpd developers
+:Date: 2017-07-01
+:Copyright: 2015-2017 The aiosmtpd developrs
+:Version: 1.1
+:Manual section: 1
+
+
+SYNOPSIS
+========
+
+aiosmtpd [options]
+
+
+Description
+===========
+
+This program provides an RFC 5321 compliant SMTP server that supports
+customizable extensions.
+
+
+OPTIONS
+=======
+-h, --help
+ Show this help message and exit
+
+-v, --version
+ Show program's version number and exit.
+
+-n, --nosetuid
+ 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).
+
+-c CLASSPATH, --class CLASSPATH
+ Use the given class (as a Python dotted import path) as the handler class
+ for SMTP events. This class can process received messages and do other
+ actions during the SMTP dialog. If not give, this uses a debugging
+ handler by default.
+
+ When given all remaining positional arguments are passed as arguments to
+ the class's ``@classmethod from_cli()`` method, which should do any
+ appropriate type conversion, and then return an instance of the handler
+ class.
+
+-s SIZE, --size SIZE
+ Restrict the total size of the incoming message to SIZE number of bytes
+ via the RFC 1870 SIZE extension. Defaults to 33554432 bytes.
+
+-u, --smtputf8
+ Enable the SMTPUTF8 extension and behave as an RFC 6531 SMTP proxy.
+
+-d, --debug
+ Increase debugging output.
+
+-l [HOST:PORT], --listen [HOST:PORT]
+ Optional host and port to listen on. If the PORT part is not given, then
+ port 8025 is used. If only :PORT is given, then localhost is used for the
+ hostname. If neither are given, localhost:8025 is used.
diff --git a/aiosmtpd/docs/migrating.rst b/aiosmtpd/docs/migrating.rst
index 5f09cb6..fdd7ab2 100644
--- a/aiosmtpd/docs/migrating.rst
+++ b/aiosmtpd/docs/migrating.rst
@@ -30,8 +30,7 @@ the ``handle_DATA()`` method::
from aiosmtpd.controller import Controller
class CustomHandler:
- @asyncio.coroutine
- def handle_DATA(self, server, session, envelope):
+ async def handle_DATA(self, server, session, envelope):
peer = session.peer
mail_from = envelope.mail_from
rcpt_tos = envelope.rcpt_tos
@@ -55,7 +54,6 @@ Important differences to note:
* Unlike ``process_message()`` in smtpd, ``handle_DATA()`` **must** return
an SMTP response code for the sender such as ``"250 OK"``.
* ``handle_DATA()`` must be a coroutine function, which means it must be
- declared with ``@asyncio.coroutine`` (or ``async def`` for Python 3.5 and
- newer).
+ declared with ``async def``.
* ``controller.start()`` runs the SMTP server in a separate thread and can be
stopped again by calling ``controller.stop()``.
diff --git a/aiosmtpd/docs/smtp.rst b/aiosmtpd/docs/smtp.rst
index 215a198..de2aebc 100644
--- a/aiosmtpd/docs/smtp.rst
+++ b/aiosmtpd/docs/smtp.rst
@@ -28,11 +28,11 @@ All methods implementing ``SMTP`` commands are prefixed with ``smtp_``; they
must also be coroutines. Here's how you could implement this use case::
>>> import asyncio
- >>> from aiosmtpd.smtp import SMTP as Server
+ >>> from aiosmtpd.smtp import SMTP as Server, syntax
>>> class MyServer(Server):
- ... @asyncio.coroutine
- ... def smtp_PING(self, arg):
- ... yield from self.push('259 Pong')
+ ... @syntax('PING [ignored]')
+ ... async def smtp_PING(self, arg):
+ ... await self.push('259 Pong')
Now let's run this server in a controller::
@@ -62,6 +62,17 @@ command, we have to use the lower level interface to talk to it.
>>> message
b'Pong'
+Because we prefixed the ``smtp_PING()`` method with the ``@syntax()``
+decorator, the command shows up in the ``HELP`` output.
+
+ >>> print(client.help().decode('utf-8'))
+ Supported commands: DATA EHLO HELO HELP MAIL NOOP PING QUIT RCPT RSET VRFY
+
+And we can get more detailed help on the new command.
+
+ >>> print(client.help('PING').decode('utf-8'))
+ Syntax: PING [ignored]
+
Server hooks
============
diff --git a/aiosmtpd/handlers.py b/aiosmtpd/handlers.py
index 30a1c56..0107c76 100644
--- a/aiosmtpd/handlers.py
+++ b/aiosmtpd/handlers.py
@@ -65,8 +65,7 @@ class Debugging:
line = line.decode('utf-8', 'replace')
print(line, file=self.stream)
- @asyncio.coroutine
- def handle_DATA(self, server, session, envelope):
+ async def handle_DATA(self, server, session, envelope):
print('---------- MESSAGE FOLLOWS ----------', file=self.stream)
# Yes, actually test for truthiness since it's possible for either the
# keywords to be missing, or for their values to be empty lists.
@@ -92,8 +91,7 @@ class Proxy:
self._hostname = remote_hostname
self._port = remote_port
- @asyncio.coroutine
- def handle_DATA(self, server, session, envelope):
+ async def handle_DATA(self, server, session, envelope):
if isinstance(envelope.content, str):
content = envelope.original_content
else:
@@ -108,8 +106,7 @@ class Proxy:
break
i += 1
peer = session.peer[0].encode('ascii')
- # XXX Python 3.4 does not support %-interpolation for bytes objects.
- lines.insert(i, b'X-Peer: ' + peer + ending)
+ lines.insert(i, b'X-Peer: %s%s' % (peer, ending))
data = EMPTYBYTES.join(lines)
refused = self._deliver(envelope.mail_from, envelope.rcpt_tos, data)
# TBD: what to do with refused addresses?
@@ -154,8 +151,7 @@ class Message:
def __init__(self, message_class=None):
self.message_class = message_class
- @asyncio.coroutine
- def handle_DATA(self, server, session, envelope):
+ async def handle_DATA(self, server, session, envelope):
envelope = self.prepare_message(session, envelope)
self.handle_message(envelope)
return '250 OK'
@@ -185,14 +181,12 @@ class AsyncMessage(Message):
super().__init__(message_class)
self.loop = loop or asyncio.get_event_loop()
- @asyncio.coroutine
- def handle_DATA(self, server, session, envelope):
+ async def handle_DATA(self, server, session, envelope):
message = self.prepare_message(session, envelope)
- yield from self.handle_message(message)
+ await self.handle_message(message)
return '250 OK'
- @asyncio.coroutine
- def handle_message(self, message):
+ async def handle_message(self, message):
raise NotImplementedError # pragma: nocover
diff --git a/aiosmtpd/lmtp.py b/aiosmtpd/lmtp.py
index 0e29913..53ed087 100644
--- a/aiosmtpd/lmtp.py
+++ b/aiosmtpd/lmtp.py
@@ -1,22 +1,19 @@
-import asyncio
-
-from aiosmtpd.smtp import SMTP
+from aiosmtpd.smtp import SMTP, syntax
from public import public
@public
class LMTP(SMTP):
- @asyncio.coroutine
- def smtp_LHLO(self, arg):
+ @syntax('LHLO hostname')
+ async def smtp_LHLO(self, arg):
"""The LMTP greeting, used instead of HELO/EHLO."""
- yield from super().smtp_HELO(arg)
+ await super().smtp_HELO(arg)
+ self.show_smtp_greeting = False
- @asyncio.coroutine
- def smtp_HELO(self, arg):
+ async def smtp_HELO(self, arg):
"""HELO is not a valid LMTP command."""
- yield from self.push('500 Error: command "HELO" not recognized')
+ await self.push('500 Error: command "HELO" not recognized')
- @asyncio.coroutine
- def smtp_EHLO(self, arg):
+ async def smtp_EHLO(self, arg):
"""EHLO is not a valid LMTP command."""
- yield from self.push('500 Error: command "EHLO" not recognized')
+ await self.push('500 Error: command "EHLO" not recognized')
diff --git a/aiosmtpd/main.py b/aiosmtpd/main.py
index 40fca4c..5b92daa 100644
--- a/aiosmtpd/main.py
+++ b/aiosmtpd/main.py
@@ -4,8 +4,9 @@ import signal
import asyncio
import logging
-from aiosmtpd.smtp import DATA_SIZE_DEFAULT, SMTP
+from aiosmtpd.smtp import DATA_SIZE_DEFAULT, SMTP, __version__
from argparse import ArgumentParser
+from contextlib import suppress
from functools import partial
from importlib import import_module
from public import public
@@ -29,9 +30,12 @@ def parseargs(args=None):
prog=PROGRAM,
description='An RFC 5321 SMTP server with extensions.')
parser.add_argument(
+ '-v', '--version', action='version',
+ version='%(prog)s {}'.format(__version__))
+ parser.add_argument(
'-n', '--nosetuid',
dest='setuid', default=True, action='store_false',
- help="""This program generally tries to setuid `nobody', unless this
+ help="""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).""")
parser.add_argument(
@@ -134,7 +138,10 @@ def main(args=None):
server = loop.run_until_complete(
loop.create_server(factory, host=args.host, port=args.port))
- loop.add_signal_handler(signal.SIGINT, loop.stop)
+ # Signal handlers are only supported on *nix, so just ignore the failure
+ # to set this on Windows.
+ with suppress(NotImplementedError):
+ loop.add_signal_handler(signal.SIGINT, loop.stop)
log.info('Starting asyncio loop')
try:
diff --git a/aiosmtpd/smtp.py b/aiosmtpd/smtp.py
index 60d2b4c..5830645 100644
--- a/aiosmtpd/smtp.py
+++ b/aiosmtpd/smtp.py
@@ -1,24 +1,17 @@
+import ssl
import socket
import asyncio
import logging
import collections
+from asyncio import sslproto
from email._header_value_parser import get_addr_spec, get_angle_addr
from email.errors import HeaderParseError
from public import public
from warnings import warn
-try:
- import ssl
- from asyncio import sslproto
-except ImportError: # pragma: nocover
- _has_ssl = False
-else: # pragma: nocover
- _has_ssl = sslproto and hasattr(ssl, 'MemoryBIO')
-
-
-__version__ = '1.0'
+__version__ = '1.1'
__ident__ = 'Python SMTP {}'.format(__version__)
log = logging.getLogger('mail.log')
@@ -58,6 +51,15 @@ def make_loop():
return asyncio.get_event_loop()
+def syntax(text, extended=None, when=None):
+ def decorator(f):
+ f.__smtp_syntax__ = text
+ f.__smtp_syntax_extended__ = extended
+ f.__smtp_syntax_when__ = when
+ return f
+ return decorator
+
+
@public
class SMTP(asyncio.StreamReaderProtocol):
command_size_limit = 512
@@ -97,6 +99,7 @@ class SMTP(asyncio.StreamReaderProtocol):
self.require_starttls = tls_context and require_starttls
self._tls_handshake_okay = True
self._tls_protocol = None
+ self._original_transport = None
self.session = None
self.envelope = None
self.transport = None
@@ -108,12 +111,11 @@ class SMTP(asyncio.StreamReaderProtocol):
def _create_envelope(self):
return Envelope()
- @asyncio.coroutine
- def _call_handler_hook(self, command, *args):
+ async def _call_handler_hook(self, command, *args):
hook = getattr(self.event_handler, 'handle_' + command, None)
if hook is None:
return MISSING
- status = yield from hook(self, self.session, self.envelope, *args)
+ status = await hook(self, self.session, self.envelope, *args)
return status
@property
@@ -128,15 +130,14 @@ class SMTP(asyncio.StreamReaderProtocol):
self._set_rset_state()
self.session = self._create_session()
self.session.peer = transport.get_extra_info('peername')
- is_instance = (_has_ssl and
- isinstance(transport, sslproto._SSLProtocolTransport))
- if self.transport is not None and is_instance: # pragma: nopy34
+ seen_starttls = (self._original_transport is not None)
+ if self.transport is not None and seen_starttls:
# It is STARTTLS connection over normal connection.
self._reader._transport = transport
self._writer._transport = transport
self.transport = transport
- # Do SSL certificate checking as rfc3207 part 4.1 says.
- # Why _extra is protected attribute?
+ # Do SSL certificate checking as rfc3207 part 4.1 says. Why is
+ # _extra a protected attribute?
self.session.ssl = self._tls_protocol._extra
handler = getattr(self.event_handler, 'handle_STARTTLS', None)
if handler is None:
@@ -154,21 +155,38 @@ class SMTP(asyncio.StreamReaderProtocol):
def connection_lost(self, error):
log.info('%r connection lost', self.session.peer)
+ # If STARTTLS was issued, then our transport is the SSL protocol
+ # transport, and we need to close the original transport explicitly,
+ # otherwise an unexpected eof_received() will be called *after* the
+ # connection_lost(). At that point the stream reader will already be
+ # destroyed and we'll get a traceback in super().eof_received() below.
+ if self._original_transport is not None:
+ self._original_transport.close()
super().connection_lost(error)
- self._writer.close()
+ self._handler_coroutine.cancel()
self.transport = None
+ def eof_received(self):
+ log.info('%r EOF received', self.session.peer)
+ self._handler_coroutine.cancel()
+ if self.session.ssl is not None: # pragma: nomswin
+ # If STARTTLS was issued, return False, because True has no effect
+ # on an SSL transport and raises a warning. Our superclass has no
+ # way of knowing we switched to SSL so it might return True.
+ #
+ # This entire method seems not to be called during any of the
+ # starttls tests on Windows. I don't really know why, but it
+ # causes these lines to fail coverage, hence the `nomswin` pragma
+ # above.
+ return False
+ return super().eof_received()
+
def _client_connected_cb(self, reader, writer):
# This is redundant since we subclass StreamReaderProtocol, but I like
# the shorter names.
self._reader = reader
self._writer = writer
- def eof_received(self):
- log.info('%r EOF received', self.session.peer)
- self._handler_coroutine.cancel()
- return super().eof_received()
-
def _set_post_data_state(self):
"""Reset state variables to their post-DATA state."""
self.envelope = self._create_envelope()
@@ -177,18 +195,16 @@ class SMTP(asyncio.StreamReaderProtocol):
"""Reset all state variables except the greeting."""
self._set_post_data_state()
- @asyncio.coroutine
- def push(self, status):
+ async def push(self, status):
response = bytes(
status + '\r\n', 'utf-8' if self.enable_SMTPUTF8 else 'ascii')
self._writer.write(response)
log.debug(response)
- yield from self._writer.drain()
+ await self._writer.drain()
- @asyncio.coroutine
... 1479 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-aiosmtpd.git
More information about the Python-modules-commits
mailing list