slimta / python-slimta

Python libraries to send, receive, and queue email.
https://slimta.org/
MIT License
171 stars 43 forks source link

pypi wheel does not match the source, files are out of date/invalid #160

Closed frmdstryr closed 4 years ago

frmdstryr commented 4 years ago

Download the whl and tar.gz source, then look at slimta/smtp/server.py the files are different, the whl does not match the source. I believe this is a bug with setuptools because I ran into it before.

Downloading both and extracting gives:

git diff python-slimta-4.0.9/slimta/ python_slimta-4.0.9-py2.py3-none-any.whl/slimta/ > diff.patch

Gives

diff --git a/python-slimta-4.0.9/slimta/__init__.py b/python-slimta-4.0.9/slimta/__init__.py
deleted file mode 100644
index 2edd7c8..0000000
--- a/python-slimta-4.0.9/slimta/__init__.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (c) 2014 Ian C. Good
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-
-
-__import__('pkg_resources').declare_namespace(__name__)
-
-
-# vim:et:fdm=marker:sts=4:sw=4:ts=4
diff --git a/python-slimta-4.0.9/slimta/edge/__init__.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/edge/__init__.py
index 3bc5552..5fbdd68 100644
--- a/python-slimta-4.0.9/slimta/edge/__init__.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/edge/__init__.py
@@ -97,7 +97,7 @@ class Edge(object):
 class EdgeServer(Edge, gevent.Greenlet):
     """This class implements a :class:`~gevent.Greenlet` serving a
     :class:`~gevent.server.StreamServer` until killed. Connections are accepted
-    on the socket and passed to :meth:`.handle()`, which should be overridden
+    on the socket and passed to :meth:`.handle()`, which should be overriden
     by implementers of this base class. The socket will be closed
     automatically.

@@ -119,10 +119,7 @@ class EdgeServer(Edge, gevent.Greenlet):
     def __init__(self, listener, queue, pool=None, hostname=None):
         super(EdgeServer, self).__init__(queue, hostname)
         spawn = 'default' if pool is None else pool
-        if listener is not None:
-            self.server = StreamServer(listener, self._handle, spawn=spawn)
-        else:
-            self.server = None
+        self.server = StreamServer(listener, self._handle, spawn=spawn)

     def _handle(self, socket, address):
         log.accept(self.server.socket, socket, address)
diff --git a/python-slimta-4.0.9/slimta/edge/wsgi.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/edge/wsgi.py
index a1e09dd..0b282c7 100644
--- a/python-slimta-4.0.9/slimta/edge/wsgi.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/edge/wsgi.py
@@ -68,7 +68,7 @@ from slimta.smtp.reply import Reply
 from slimta.queue import QueueError
 from slimta.relay import RelayError
 from slimta.util.ptrlookup import PtrLookup
-from . import Edge, EdgeServer
+from . import Edge

 __all__ = ['WsgiResponse', 'WsgiEdge', 'WsgiValidators']

@@ -111,17 +111,11 @@ def _build_http_response(smtp_reply):
         return WsgiResponse('500 Internal Server Error', headers)

-class WsgiEdge(EdgeServer, WsgiServer):
+class WsgiEdge(Edge, WsgiServer):
     """This class is intended to be instantiated and used as an app on top of a
     WSGI server engine such as :class:`gevent.pywsgi.WSGIServer`. It will only
     acccept ``POST`` requests that provide a ``message/rfc822`` payload.

-    .. note::
-
-        If ``listener`` is not provided, use
-        :meth:`~slimta.http.WsgiServer.build_server` to build and manage the
-        server manually.
-
     :param queue: |Queue| object used by :meth:`.handoff()` to ensure the
                   envelope is properly queued before acknowledged by the edge
                   service.
@@ -133,13 +127,6 @@ class WsgiEdge(EdgeServer, WsgiServer):
     :param uri_pattern: If given, only URI paths that match the given pattern
                         will be allowed.
     :type uri_pattern: :py:class:`~re.RegexObject` or :py:obj:`str`
-    :param listener: Usually a ``(ip, port)`` tuple defining the interface
-                     and port upon which to listen for connections.
-    :param pool: If given, defines a specific :class:`gevent.pool.Pool` to
-                 use for new greenlets.
-    :param context: Enables SSL encryption on connected sockets using the
-                    information given in the context.
-    :type context: :py:class:`~ssl.SSLContext`

     """

@@ -162,18 +149,13 @@ class WsgiEdge(EdgeServer, WsgiServer):
     ehlo_header = 'X-Ehlo'

     def __init__(self, queue, hostname=None, validator_class=None,
-                 uri_pattern=None, listener=None, pool=None, context=None):
-        super(WsgiEdge, self).__init__(None, queue, hostname=hostname)
+                 uri_pattern=None):
+        super(WsgiEdge, self).__init__(queue, hostname)
         self.validator_class = validator_class
         if isinstance(uri_pattern, str):
             self.uri_pattern = re.compile(uri_pattern)
         else:
             self.uri_pattern = uri_pattern
-        if listener:
-            ssl_args = {'ssl_context': context}
-            self.server = self.build_server(listener, pool, ssl_args)
-        else:
-            self.server = None

     def __call__(self, environ, start_response):
         ptr_lookup = PtrLookup(environ.get('REMOTE_ADDR', '0.0.0.0'))
diff --git a/python-slimta-4.0.9/slimta/http/__init__.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/http/__init__.py
index 360697d..943a819 100644
--- a/python-slimta-4.0.9/slimta/http/__init__.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/http/__init__.py
@@ -32,7 +32,7 @@ from __future__ import absolute_import

 from socket import error as socket_error

-from gevent import socket
+from gevent import socket, ssl

 from slimta.util.pycompat import httplib, urlparse

@@ -46,20 +46,37 @@ class HTTPConnection(httplib.HTTPConnection):

     """

-    def __init__(self, host, port=None, *args, **kwargs):
-        httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
-        self._create_connection = socket.create_connection
+    def connect(self):
+        if hasattr(self, 'source_address') and self.source_address:
+            self.sock = socket.create_connection((self.host, self.port),
+                                                 self.timeout,
+                                                 self.source_address)
+        else:
+            self.sock = socket.create_connection((self.host, self.port),
+                                                 self.timeout)
+        if self._tunnel_host:
+            self._tunnel()

-class HTTPSConnection(httplib.HTTPSConnection):
+class HTTPSConnection(HTTPConnection):
     """Modified version of the :py:class:`httplib.HTTPSConnection` class that
-    uses gevent sockets.
+    uses gevent sockets and the more functional ``tls`` parameter.
+
+    :param tls: This keyword argument contains the keyword arguments passed
+                into :class:`~gevent.ssl.SSLSocket` when the connection is
+                encrypted.

     """

-    def __init__(self, host, port=None, *args, **kwargs):
-        httplib.HTTPSConnection.__init__(self, host, port, *args, **kwargs)
-        self._create_connection = socket.create_connection
+    def __init__(self, host, port=443, tls=None, *args, **kwargs):
+        self.tls = tls or {}
+        port = port or 443
+        HTTPConnection.__init__(self, host, port, *args, **kwargs)
+
+    def connect(self):
+        HTTPConnection.connect(self)
+        self.sock = ssl.SSLSocket(self.sock, **self.tls)
+        self.sock.do_handshake()

     def close(self):
         if self.sock:
@@ -68,18 +85,17 @@ class HTTPSConnection(httplib.HTTPSConnection):
             except socket_error as e:
                 if e.errno != 0:
                     raise
-        httplib.HTTPSConnection.close(self)
+        HTTPConnection.close(self)

-def get_connection(url, context=None):
+def get_connection(url, tls=None):
     """This convenience functions returns a :class:`HTTPConnection` or
     :class:`HTTPSConnection` based on the information contained in URL.

     :param url: URL string to create a connection for. Alternatively, passing
                 in the results of :py:func:`urlparse.urlsplit` works as well.
-    :param context: Used to wrap sockets with SSL encryption, when the URL
-                    scheme is ``https``.
-    :type context: :py:class:`~ssl.SSLContext`
+    :param tls: When the URL scheme is ``https``, this is passed in as the
+                ``tls`` parameter to :class:`HTTPSConnection`.

     """
     if isinstance(url, (str, bytes)):
@@ -87,7 +103,7 @@ def get_connection(url, context=None):
     host = url.netloc or 'localhost'

     if url.scheme == 'https':
-        conn = HTTPSConnection(host, context=context)
+        conn = HTTPSConnection(host, tls=tls)
     else:
         conn = HTTPConnection(host)
     return conn
diff --git a/python-slimta-4.0.9/slimta/http/wsgi.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/http/wsgi.py
index 5722cba..cf300bc 100644
--- a/python-slimta-4.0.9/slimta/http/wsgi.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/http/wsgi.py
@@ -49,7 +49,7 @@ class WsgiServer(object):

     """

-    def build_server(self, listener, pool=None, ssl_args=None):
+    def build_server(self, listener, pool=None, tls=None):
         """Constructs and returns a WSGI server engine, configured to use the
         current object as its application.

@@ -57,16 +57,15 @@ class WsgiServer(object):
                          and port upon which to listen for connections.
         :param pool: If given, defines a specific :class:`gevent.pool.Pool` to
                      use for new greenlets.
-        :param ssl_args: Optional dictionary of TLS settings, causing sockets
-                         to be encrypted on connection. See
-                         :class:`~gevent.pywsgi.WSGIServer` for details.
+        :param tls: Optional dictionary of TLS settings passed directly as
+                    keyword arguments to :class:`gevent.ssl.SSLSocket`.
         :rtype: :class:`gevent.pywsgi.WSGIServer`

         """
         spawn = pool or 'default'
-        ssl_args = ssl_args or {}
+        tls = tls or {}
         return GeventWSGIServer(listener, self, log=sys.stdout, spawn=spawn,
-                                **ssl_args)
+                                **tls)

     def handle(self, environ, start_response):
         """Overridden by sub-classes to handle WSGI requests and generate a
diff --git a/python-slimta-4.0.9/slimta/logging/socket.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/logging/socket.py
index dcb391e..aca70c9 100644
--- a/python-slimta-4.0.9/slimta/logging/socket.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/logging/socket.py
@@ -23,16 +23,11 @@

 from __future__ import absolute_import

-import logging
 from functools import partial

 from gevent.socket import SHUT_WR, SHUT_RD

-__all__ = ['socket_error_log_level', 'SocketLogger']
-
-#: The log level for logging :py:exc:`socket.error` exceptions. The default log
-#: level is ``logging.ERROR``.
-socket_error_log_level = logging.ERROR
+__all__ = ['SocketLogger']

 class SocketLogger(object):
@@ -44,14 +39,10 @@ class SocketLogger(object):

     """

-    def __init__(self, logger):
+    def __init__(self, log):
         from slimta.logging import logline
-        self.logger = logger
-        self.log = partial(logline, logger.debug, 'fd')
-        self.log_error = partial(logline, self._log_error, 'fd')
-
-    def _log_error(self, *args, **kwargs):
-        return self.logger.log(socket_error_log_level, *args, **kwargs)
+        self.log = partial(logline, log.debug, 'fd')
+        self.log_error = partial(logline, log.error, 'fd')

     def send(self, socket, data):
         """Logs a socket :meth:`~socket.socket.send()` operation along with the
@@ -98,18 +89,22 @@ class SocketLogger(object):
         peer = address or socket.getpeername()
         self.log(socket.fileno(), 'connect', peer=peer)

-    def encrypt(self, socket, context):
+    def encrypt(self, socket, tls_args):
         """Logs a socket encryption operation along with the certificate and
         key files used and whether the socket is acting as the client or the
         server.

         :param socket: The socket that was shutdown.
-        :param context: The SSL context that was used.
-        :type context: :py:class:`~ssl.SSLContext`
+        :param tls_args: Keyword rguments passed to the encryption operation.

         """
-        stats = context.session_stats()
-        self.log(socket.fileno(), 'encrypt', **stats)
+        keyfile = tls_args.get('keyfile', None)
+        certfile = tls_args.get('certfile', None)
+        server_side = tls_args.get('server_side', False)
+        self.log(socket.fileno(), 'encrypt',
+                 keyfile=keyfile,
+                 certfile=certfile,
+                 server_side=server_side)

     def shutdown(self, socket, how):
         """Logs a socket :meth:`~socket.socket.shutdown()` operation along
diff --git a/python-slimta-4.0.9/slimta/relay/__init__.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/relay/__init__.py
index efed8cd..2822e9f 100644
--- a/python-slimta-4.0.9/slimta/relay/__init__.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/relay/__init__.py
@@ -94,7 +94,7 @@ class Relay(object):
         return self.attempt(envelope, attempts)

     def attempt(self, envelope, attempts):
-        """This method must be overridden by sub-classes in order to be passed
+        """This method must be overriden by sub-classes in order to be passed
         in to the |Queue| constructor.

         The result of a successful relay attempt is either ``None`` or a
diff --git a/python-slimta-4.0.9/slimta/relay/http.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/relay/http.py
index 3e39d9b..b55da4d 100644
--- a/python-slimta-4.0.9/slimta/relay/http.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/relay/http.py
@@ -37,6 +37,7 @@ import gevent
 from slimta import logging
 from slimta.smtp.reply import Reply
 from slimta.http import get_connection
+from slimta.util import validate_tls
 from slimta.util.pycompat import urlparse
 from . import PermanentRelayError, TransientRelayError
 from .pool import RelayPool, RelayPoolClient
@@ -85,7 +86,7 @@ class HttpRelayClient(RelayPoolClient):
         return headers

     def _new_conn(self):
-        self.conn = get_connection(self.url, self.relay.context)
+        self.conn = get_connection(self.url, self.relay.tls)
         try:
             self.ehlo_as = self.relay.ehlo_as()
         except TypeError:
@@ -172,9 +173,9 @@ class HttpRelay(RelayPool):
     :param pool_size: At most this many simultaneous connections will be open
                       to the destination. If this limit is reached and no
                       connections are idle, new attempts will block.
-    :param context: Used to wrap sockets with SSL encryption, when ``https``
-                    URLs are used, rather than the default context.
-    :type context: :py:class:`~ssl.SSLContext`
+    :param tls: Dictionary of TLS settings passed directly as keyword arguments
+                to :class:`gevent.ssl.SSLSocket`. This parameter is optional
+                unless ``https:`` is given in ``url``.
     :param ehlo_as: The string to send as the EHLO string in a header. Defaults
                     to the FQDN of the system. This may also be given as a
                     function that will be executed with no arguments at the
@@ -201,11 +202,11 @@ class HttpRelay(RelayPool):
     #: The header name used to send the EHLO string.
     ehlo_header = 'X-Ehlo'

-    def __init__(self, url, pool_size=None, context=None, ehlo_as=None,
+    def __init__(self, url, pool_size=None, tls=None, ehlo_as=None,
                  timeout=None, idle_timeout=None):
         super(HttpRelay, self).__init__(pool_size)
         self.url = urlparse.urlsplit(url, 'http')
-        self.context = context
+        self.tls = validate_tls(tls)
         self.ehlo_as = ehlo_as or getfqdn()
         self.timeout = timeout
         self.idle_timeout = idle_timeout
diff --git a/python-slimta-4.0.9/slimta/relay/pool.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/relay/pool.py
index f24661e..d229c6c 100644
--- a/python-slimta-4.0.9/slimta/relay/pool.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/relay/pool.py
@@ -134,11 +134,11 @@ class RelayPoolClient(Greenlet):
             self.idle = False

     def _run(self):
-        """This method must be overridden by sub-classes to handle processing
-        of delivery requests. It should call :meth:`poll` when it is ready for
-        new delivery requests. The result of the delivery attempt should be
-        written to the :class:`~gevent.event.AsyncResult` object provided in
-        the request.
+        """This method must be overriden by sub-classes to handle processing of
+        delivery requests. It should call :meth:`poll` when it is ready for new
+        delivery requests. The result of the delivery attempt should be written
+        to the :class:`~gevent.event.AsyncResult` object provided in the
+        request.

         """
         raise NotImplementedError()
diff --git a/python-slimta-4.0.9/slimta/smtp/io.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/smtp/io.py
index c897c8f..7a33877 100644
--- a/python-slimta-4.0.9/slimta/smtp/io.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/smtp/io.py
@@ -26,7 +26,7 @@ from socket import error as socket_error
 from errno import ECONNRESET, EPIPE
 from io import BytesIO

-from gevent.ssl import SSLSocket, SSLError, create_default_context
+from gevent.ssl import SSLSocket, SSLError

 from slimta import logging
 from . import ConnectionLost, BadReply
@@ -50,9 +50,11 @@ log = logging.getSocketLogger(__name__)

 class IO(object):

-    def __init__(self, socket, address=None):
+    def __init__(self, socket, tls_wrapper=None, address=None):
         self.socket = socket
-        self._address = address
+        self._address = None
+        if tls_wrapper:
+            self._tls_wrapper = tls_wrapper

         self.send_buffer = BytesIO()
         self.recv_buffer = b''
@@ -72,8 +74,6 @@ class IO(object):
         if self.encrypted:
             try:
                 self.socket.unwrap()
-            except ValueError:
-                pass
             except SSLWantReadError:
                 pass
             except socket_error as e:
@@ -102,25 +102,17 @@ class IO(object):
             raise ConnectionLost()
         return data

-    def encrypt_socket_client(self, context=None):
-        hostname = self.address[0]
-        context = context or create_default_context()
-        log.encrypt(self.socket, context)
-        try:
-            self.socket = context.wrap_socket(self.socket,
-                                              server_hostname=hostname)
-            return True
-        except SSLError as exc:
-            log.error(self.socket, exc, self.address)
-            return False
+    def _tls_wrapper(self, socket, tls):
+        sslsock = SSLSocket(socket, **tls)
+        sslsock.do_handshake()
+        return sslsock

-    def encrypt_socket_server(self, context):
-        log.encrypt(self.socket, context)
+    def encrypt_socket(self, tls):
+        log.encrypt(self.socket, tls)
         try:
-            self.socket = context.wrap_socket(self.socket, server_side=True)
+            self.socket = self._tls_wrapper(self.socket, tls)
             return True
-        except SSLError as exc:
-            log.error(self.socket, exc, self.address)
+        except SSLError:
             return False

     def buffered_recv(self):
diff --git a/python-slimta-4.0.9/slimta/smtp/reply.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/smtp/reply.py
index c055621..fbeeee1 100644
--- a/python-slimta-4.0.9/slimta/smtp/reply.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/smtp/reply.py
@@ -124,18 +124,6 @@ class Reply(object):
         """
         return self.code is not None

-    def __contains__(self, substr):
-        """Checks if the full reply string contains the given sub-string.
-
-        :param substr: The sub-string to check for.
-        :rtype: True or False
-
-        """
-        if isinstance(substr, bytes):
-            return substr in bytes(self)
-        else:
-            return substr in str(self)
-
     # Python 2 compat.
     if pycompat.PY2:
         __nonzero__ = __bool__
diff --git a/python-slimta-4.0.9/slimta/smtp/server.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/smtp/server.py
index b70add4..14a76fe 100644
--- a/python-slimta-4.0.9/slimta/smtp/server.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/smtp/server.py
@@ -32,6 +32,7 @@ import re
 from gevent import Timeout
 from pysasl import SASLAuth

+from slimta.util import validate_tls
 from . import SmtpError, ConnectionLost
 from .datareader import DataReader
 from .io import IO
@@ -75,15 +76,18 @@ class Server(object):
                      corresponding SMTP commands are received. These methods
                      can modify the |Reply| before the command response is
                      sent.
-    :param address: The address of the connected client.
     :param auth: If True, enable authentication with default mechanisms. May
                  also be given as a list of SASL mechanism names to support,
                  e.g. ``['PLAIN', 'LOGIN', 'CRAM-MD5']``.
-    :param context: Enables SSL encryption on connected sockets using the
-                    information given in the context.
+    :param tls: Optional dictionary of TLS settings passed directly as
+                keyword arguments to :class:`gevent.ssl.SSLSocket`.
     :param tls_immediately: If True, the socket will be encrypted
-                            immediately on connection rather than advertising
-                            ``STARTTLS``.
+                            immediately.
+    :param tls_wrapper: Optional function that takes a socket and the ``tls``
+                        dictionary, creates a new encrypted socket, performs
+                        the TLS handshake, and returns it. The default uses
+                        :class:`~gevent.ssl.SSLSocket`.
+    :type tls_immediately: True or False
     :param command_timeout: Optional timeout waiting for a command to be
                             sent from the client.
     :param data_timeout: Optional timeout waiting for data to be sent from
@@ -91,13 +95,13 @@ class Server(object):

     """

-    def __init__(self, socket, handlers, address=None, auth=False,
-                 context=None, tls_immediately=False,
+    def __init__(self, socket, handlers, auth=False,
+                 tls=None, tls_immediately=False, tls_wrapper=None,
                  command_timeout=None, data_timeout=None):
         self.handlers = handlers
         self.extensions = Extensions()

-        self.io = IO(socket, address)
+        self.io = IO(socket, tls_wrapper)

         self.bannered = False
         self.have_mailfrom = None
@@ -105,14 +109,11 @@ class Server(object):
         self.ehlo_as = None
         self.authed = False

-        self.context = context
-        self.tls_immediately = tls_immediately
-
         self.extensions.add('8BITMIME')
         self.extensions.add('PIPELINING')
         self.extensions.add('ENHANCEDSTATUSCODES')
         self.extensions.add('SMTPUTF8')
-        if self.context and not tls_immediately:
+        if tls and not tls_immediately:
             self.extensions.add('STARTTLS')
         if auth:
             if isinstance(auth, list):
@@ -122,6 +123,9 @@ class Server(object):
             auth_session = AuthSession(auth_obj, self.io)
             self.extensions.add('AUTH', auth_session)

+        self.tls = validate_tls(tls, server_side=True)
+        self.tls_immediately = tls_immediately
+
         self.command_timeout = command_timeout
         self.data_timeout = data_timeout or command_timeout

@@ -158,7 +162,7 @@ class Server(object):
         self.have_rcptto = None

     def _encrypt_session(self):
-        if not self.io.encrypt_socket_server(self.context):
+        if not self.io.encrypt_socket(self.tls):
             return False
         self._call_custom_handler('TLSHANDSHAKE')
         return True
@@ -186,7 +190,7 @@ class Server(object):
         :raises: :class:`~slimta.smtp.ConnectionLost` or unhandled exceptions.

         """
-        if self.context and self.tls_immediately:
+        if self.tls and self.tls_immediately:
             if not self._encrypt_session():
                 tls_failure.send(self.io, flush=True)
                 return
@@ -252,9 +256,6 @@ class Server(object):
         if not self.bannered:
             bad_sequence.send(self.io)
             return
-        elif not ehlo_as:
-            bad_arguments.send(self.io)
-            return

         ehlo_as = ehlo_as.decode('utf-8')
         reply = Reply('250', 'Hello '+ehlo_as)
@@ -276,15 +277,11 @@ class Server(object):
         if not self.bannered:
             bad_sequence.send(self.io)
             return
-        elif not ehlo_as:
-            bad_arguments.send(self.io)
-            return

         ehlo_as = ehlo_as.decode('utf-8')
         reply = Reply('250', 'Hello '+ehlo_as)
         reply.enhanced_status_code = False
         self._call_custom_handler('HELO', reply, ehlo_as)
-
         reply.send(self.io)
         self._check_close_code(reply)

diff --git a/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/system.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/system.py
new file mode 100644
index 0000000..a30c85d
--- /dev/null
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/system.py
@@ -0,0 +1,156 @@
+# Copyright (c) 2012 Ian C. Good
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+"""Contains functions to simplify the usual daemonization procedures for long-
+running processes.
+
+"""
+
+from __future__ import absolute_import
+
+import os
+import os.path
+import sys
+from pwd import getpwnam
+from grp import getgrnam
+
+__all__ = ['daemonize', 'redirect_stdio', 'drop_privileges', 'PidFile']
+
+
+def daemonize():
+    """Daemonizes the current process using the standard double-fork.
+    This function does not affect standard input, output, or error.
+
+    :returns: The PID of the daemonized process.
+
+    """
+
+    # Fork once.
+    try:
+        pid = os.fork()
+        if pid > 0:
+            os._exit(0)
+    except OSError:
+        return
+
+    # Set some options to detach from the terminal.
+    os.chdir('/')
+    os.setsid()
+    os.umask(0)
+
+    # Fork again.
+    try:
+        pid = os.fork()
+        if pid > 0:
+            os._exit(0)
+    except OSError:
+        return
+
+    os.setsid()
+    return os.getpid()
+
+
+def redirect_stdio(stdout=None, stderr=None, stdin=None):
+    """Redirects standard output, error, and input to the given
+    filenames. Standard output and error are opened in append-mode, and
+    standard input is opened in read-only mode. Leaving any parameter
+    blank leaves that stream alone.
+
+    :param stdout: filename to append the standard output stream into.
+    :param stderr: filename to append the standard error stream into.
+    :param stdin: filename to read from as the standard input stream.
+
+    """
+
+    # Find the OS /dev/null equivalent.
+    nullfile = getattr(os, 'devnull', '/dev/null')
+
+    # Redirect all standard I/O to /dev/null.
+    sys.stdout.flush()
+    sys.stderr.flush()
+    si = open(stdin or nullfile, 'r')
+    so = open(stdout or nullfile, 'a+')
+    se = open(stderr or nullfile, 'a+', 0)
+    os.dup2(si.fileno(), sys.stdin.fileno())
+    os.dup2(so.fileno(), sys.stdout.fileno())
+    os.dup2(se.fileno(), sys.stderr.fileno())
+
+
+def drop_privileges(user=None, group=None):
+    """Uses the system calls :func:`~os.setuid` and :func:`~os.setgid` to drop
+    root privileges to the given user and group. This is useful for security
+    purposes, once root-only ports like 25 are opened.
+
+    :param user: user name (from /etc/passwd) or UID.
+    :param group: group name (from /etc/group) or GID.
+
+    """
+    if group:
+        try:
+            gid = int(group)
+        except ValueError:
+            gid = getgrnam(group).gr_gid
+        os.setgid(gid)
+    if user:
+        try:
+            uid = int(user)
+        except ValueError:
+            uid = getpwnam(user).pw_uid
+        os.setuid(uid)
+
+
+class PidFile(object):
+    """.. versionadded:: 0.3.13
+
+    Context manager which creates a PID file containing the current process id,
+    runs the context, and then removes the PID file.
+
+    An :py:exc:`OSError` exceptions when creating the PID file will be
+    propogated without executing the context.
+
+    :param filename: The filename to use for the PID file. If ``None`` is
+                     given, the context is simply executed with no PID file
+                     created.
+
+    """
+
+    def __init__(self, filename=None):
+        super(PidFile, self).__init__()
+        if not filename:
+            self.filename = None
+        else:
+            self.filename = os.path.abspath(filename)
+
+    def __enter__(self):
+        if self.filename:
+            with open(self.filename, 'w') as pid:
+                pid.write('{0}\n'.format(os.getpid()))
+            return self.filename
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        if self.filename:
+            try:
+                os.unlink(self.filename)
+            except OSError:
+                pass
+
+
+# vim:et:fdm=marker:sts=4:sw=4:ts=4
diff --git a/python-slimta-4.0.9/slimta/util/proxyproto.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/util/proxyproto.py
index 79b157e..e270ede 100644
--- a/python-slimta-4.0.9/slimta/util/proxyproto.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/util/proxyproto.py
@@ -32,7 +32,6 @@ import struct

 from gevent import socket

-from slimta.edge import EdgeServer
 from slimta.logging import getSocketLogger

 __all__ = ['ProxyProtocol', 'ProxyProtocolV1', 'ProxyProtocolV2']
@@ -158,22 +157,6 @@ class ProxyProtocolV1(object):
         log.recv(sock, line)
         return cls.parse_pp_line(line)

-    @classmethod
-    def mixin(cls, edge):
-        """Dynamically mix-in the :class:`ProxyProtocolV1` class as a base of
-        the given edge object. Use with caution.
-
-        :param edge: the edge object to use proxy protocol on.
-        :type edge: :class:`~slimta.edge.EdgeServer`
-        :raises: ValueError
-
-        """
-        if not isinstance(edge, EdgeServer):
-            raise ValueError(edge)
-        old_class = edge.__class__
-        new_class_name = cls.__name__ + old_class.__name__
-        edge.__class__ = type(new_class_name, (cls, old_class), {})
-
     def handle(self, sock, addr):
         """Intercepts calls to :meth:`~slimta.edge.EdgeServer.handle`, reads
         the proxy protocol header, and then resumes the original call.
@@ -226,32 +209,32 @@ class ProxyProtocolV2(object):
     def __parse_pp_data(cls, data):
         assert data[0:12] == b'\r\n\r\n\x00\r\nQUIT\n', \
             'Invalid proxy protocol v2 signature'
-        assert data[12] & 0xf0 == 0x20, 'Invalid proxy protocol version'
+        assert data[13] & 0xf0 == 0x20, 'Invalid proxy protocol version'
         command = cls.__commands.get(data[12] & 0x0f)
         family = cls.__families.get(data[13] & 0xf0)
         protocol = cls.__protocols.get(data[13] & 0x0f)
-        addr_len = struct.unpack('!H', data[14:16])[0]
+        addr_len = struct.unpack('<H', data[14:16])[0]
         return command, family, protocol, addr_len

     @classmethod
     def __parse_pp_addresses(cls, family, addr_data):
         if family == socket.AF_INET:
             src_ip, dst_ip, src_port, dst_port = \
-                struct.unpack('!4s4sHH', addr_data)
+                struct.unpack('<4s4sHH', addr_data)
             src_addr = (socket.inet_ntop(family, src_ip), src_port)
             dst_addr = (socket.inet_ntop(family, dst_ip), dst_port)
             return src_addr, dst_addr
         elif family == socket.AF_INET6:
             src_ip, dst_ip, src_port, dst_port = \
-                struct.unpack('!16s16sHH', addr_data)
+                struct.unpack('<16s16sHH', addr_data)
             src_addr = (socket.inet_ntop(family, src_ip), src_port)
             dst_addr = (socket.inet_ntop(family, dst_ip), dst_port)
             return src_addr, dst_addr
         elif family == socket.AF_UNIX:
-            src_addr, dst_addr = struct.unpack('!108s108s', addr_data)
+            src_addr, dst_addr = struct.unpack('<108s108s', addr_data)
             return src_addr.rstrip(b'\x00'), dst_addr.rstrip(b'\x00')
         else:
-            return unknown_pp_source_address, unknown_pp_dest_address
+            return unknown_pp_source_address,  unknown_pp_dest_address

     @classmethod
     def process_pp_v2(cls, sock, initial):
@@ -266,22 +249,6 @@ class ProxyProtocolV2(object):
         except struct.error:
             raise AssertionError('Invalid proxy protocol data')

-    @classmethod
-    def mixin(cls, edge):
-        """Dynamically mix-in the :class:`ProxyProtocolV2` class as a base of
-        the given edge object. Use with caution.
-
-        :param edge: the edge object to use proxy protocol on.
-        :type edge: :class:`~slimta.edge.EdgeServer`
-        :raises: ValueError
-
-        """
-        if not isinstance(edge, EdgeServer):
-            raise ValueError(edge)
-        old_class = edge.__class__
-        new_class_name = cls.__name__ + old_class.__name__
-        edge.__class__ = type(new_class_name, (cls, old_class), {})
-
     def handle(self, sock, addr):
         """Intercepts calls to :meth:`~slimta.edge.EdgeServer.handle`, reads
         the proxy protocol header, and then resumes the original call.
@@ -324,22 +291,6 @@ class ProxyProtocol(object):
             read = memoryview(buf)[0:len(read)+read_n].tobytes()
         return read

-    @classmethod
-    def mixin(cls, edge):
-        """Dynamically mix-in the :class:`ProxyProtocol` class as a base of the
-        given edge object. Use with caution.
-
-        :param edge: the edge object to use proxy protocol on.
-        :type edge: :class:`~slimta.edge.EdgeServer`
-        :raises: ValueError
-
-        """
-        if not isinstance(edge, EdgeServer):
-            raise ValueError(edge)
-        old_class = edge.__class__
-        new_class_name = cls.__name__ + old_class.__name__
-        edge.__class__ = type(new_class_name, (cls, old_class), {})
-
     def handle(self, sock, addr):
         """Intercepts calls to :meth:`~slimta.edge.EdgeServer.handle`, reads
         the proxy protocol header, and then resumes the original call.
diff --git a/python-slimta-4.0.9/slimta/util/pycompat.py b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/util/pycompat.py
index 8afa921..ea4a27b 100644
--- a/python-slimta-4.0.9/slimta/util/pycompat.py
+++ b/python_slimta-4.0.9-py2.py3-none-any.whl/slimta/util/pycompat.py
@@ -56,4 +56,33 @@ httplib = httplib_mod
 reprlib = reprlib_mod

+if sys.version_info < (3, 4):  # pragma: no cover
+    orig_HTTPConnection = httplib_mod.HTTPConnection
+    orig_HTTPSConnection = httplib_mod.HTTPSConnection
+
+    class _StrictHTTPConnection(orig_HTTPConnection):
+
+        _init_args = ('host', 'port', 'timeout', 'source_address')
+
+        def __init__(self, *args, **kwargs):
+            for i, arg in enumerate(args):
+                kwargs.setdefault(self._init_args[i], arg)
+            kwargs['strict'] = True
+            orig_HTTPConnection.__init__(self, **kwargs)
+
+    class _StrictHTTPSConnection(orig_HTTPSConnection):
+
+        _init_args = ('host', 'port', 'key_file', 'cert_file', 'timeout',
+                      'source_address', 'context')
+
+        def __init__(self, *args, **kwargs):
+            for i, arg in enumerate(args):
+                kwargs.setdefault(self._init_args[i], arg)
+            kwargs['strict'] = True
+            orig_HTTPSConnection.__init__(self, **kwargs)
+
+    httplib.HTTPConnection = _StrictHTTPConnection
+    httplib.HTTPSConnection = _StrictHTTPSConnection
+
+
 # vim:et:fdm=marker:sts=4:sw=4:ts=4

python-slimta-4.0.9.tar.gz

python_slimta-4.0.9-py2.py3-none-any.whl.zip