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.
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
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:
Gives
python-slimta-4.0.9.tar.gz
python_slimta-4.0.9-py2.py3-none-any.whl.zip