diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index ad0e86dd..7d498b6a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.6, 3.7, 3.8, 3.9] + python-version: ['3.6', '3.7', '3.8', '3.9'] steps: - uses: actions/checkout@v2 @@ -28,9 +28,9 @@ jobs: - name: Lint with flake8 run: | flake8 slimta - - name: Type checking with pytype + - name: Type checking with pyright run: | - pytype -k + pyright slimta - name: Test with pytest run: | py.test --cov=slimta diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f757f03..1f961aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,18 @@ # Change Log -## 4.1 - _Unreleased_ +## 5.0 - Change Logs have moved to [Releases][18] + +## 4.2 - 2021-02-14 + +- New `handle_tls2(ssl_socket)` validator on [`SmtpValidators`][16]. +- Switch to [`select.poll()`][17] for DNS lookups. + +## 4.1 - 2020-10-29 ### Added +- [**Extension Consolidation**][15]. - New [`create_listeners`][10] function for creating IP sockets on both IPv4 and IPv6, if available. - New `mixin` functions in the [`proxyproto`][12] classes. @@ -152,6 +160,10 @@ [12]: http://slimta.org/en/latest/api/slimta.util.proxyproto.html [13]: http://slimta.org/en/latest/api/slimta.relay.smtp.static.html#slimta.relay.smtp.static.StaticLmtpRelay [14]: http://slimta.org/en/latest/api/slimta.relay.pipe.html +[15]: https://www.slimta.org/blog/2020-10-30.html +[16]: https://www.slimta.org/api/slimta.edge.smtp.html#slimta.edge.smtp.SmtpValidators +[17]: https://docs.python.org/3/library/select.html#select.poll +[18]: https://github.com/slimta/python-slimta/releases [3.0]: https://github.com/slimta/python-slimta/issues?q=milestone%3A3.0 [3.1]: https://github.com/slimta/python-slimta/issues?q=milestone%3A3.1 [3.2]: https://github.com/slimta/python-slimta/issues?q=milestone%3A3.2 diff --git a/README.md b/README.md index c3efbeae..1a4aac81 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,12 @@ can incorporate any protocol or policy, custom or built-in. An MTA built with Python's great community. The `python-slimta` project is released under the [MIT License][4]. It is -tested for Python 2.7+ or 3.6+. +tested for Python 3.6+. [![build](https://github.com/slimta/python-slimta/actions/workflows/python-package.yml/badge.svg)](https://github.com/slimta/python-slimta/actions/workflows/python-package.yml) [![PyPI](https://img.shields.io/pypi/v/python-slimta.svg)](https://pypi.python.org/pypi/python-slimta) [![PyPI](https://img.shields.io/pypi/pyversions/python-slimta.svg)](https://pypi.python.org/pypi/python-slimta) [![PyPI](https://img.shields.io/pypi/l/python-slimta.svg)](https://pypi.python.org/pypi/python-slimta) -[![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=icgood&url=https%3A%2F%2Fgithub.com%2Fslimta%2Fpython-slimta&title=python-slimta&language=python&tags=github&category=software) Getting Started diff --git a/requirements-dev.txt b/requirements-dev.txt index 4a342556..597f1956 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ pytest >= 4 pytest-cov -pytype -mox3 +pyright +pymox testfixtures flake8 twine diff --git a/setup.cfg b/setup.cfg index 9241ecea..9e220dd8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,10 @@ filterwarnings = ignore::DeprecationWarning ignore::PendingDeprecationWarning +[flake8] +per-file-ignores = + test/*: E501 + [coverage:report] exclude_lines = declare_namespace diff --git a/setup.py b/setup.py index b4b550cd..a1f99a83 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ license = f.read() setup(name='python-slimta', - version='4.2.1', + version='5.0.0', author='Ian Good', author_email='ian@icgood.net', description='Lightweight, asynchronous SMTP libraries.', @@ -40,12 +40,9 @@ packages=find_packages(), namespace_packages=['slimta'], install_requires=['gevent >= 1.1rc', - 'pysasl >= 0.4.0, < 0.5', - 'pycares < 3.0.0; python_version < "3.0"', - 'pycares >= 1; python_version >= "3.0"'], - extras_require={'spf': ['pyspf', 'py3dns; python_version >= "3.0"', - 'pydns; python_version < "3.0"', - 'ipaddr; python_version < "3.0"'], + 'pysasl >= 0.5.0', + 'pycares >= 1'], + extras_require={'spf': ['pyspf', 'py3dns'], 'redis': ['redis'], 'aws': ['boto'], 'disk': ['pyaio >= 0.4; platform_system == "Linux"']}, @@ -55,7 +52,6 @@ 'Intended Audience :: Information Technology', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', diff --git a/slimta/cloudstorage/__init__.py b/slimta/cloudstorage/__init__.py index 48f9bbc9..5d5bb44a 100644 --- a/slimta/cloudstorage/__init__.py +++ b/slimta/cloudstorage/__init__.py @@ -22,8 +22,6 @@ """Package containing a module for the different cloud service providers along with any necessary helper modules. -.. _Cloud Files: http://www.rackspace.com/cloud/files/ -.. _Cloud Queues: http://www.rackspace.com/cloud/queues/ .. _S3: http://aws.amazon.com/s3/ .. _SQS: http://aws.amazon.com/sqs/ @@ -49,21 +47,18 @@ class CloudStorageError(QueueError): class CloudStorage(QueueStorage): """This class implements a :class:`~slimta.queue.QueueStorage` backend that uses cloud services to store messages. It coordinates the storage of - messages and metadata (using `Cloud Files`_ or `S3`_) with the optional - message queue mechanisms (using `Cloud Queues`_ or `SQS`_) that can alert - other *slimta* processes that a new message is available in the object - store. + messages and metadata (using `S3`_) with the optional message queue + mechanisms (using `SQS`_) that can alert other *slimta* processes that a + new message is available in the object store. :param object_store: The object used as the backend for storing message contents and metadata in the cloud. Currently this can be an instance of - :class:`~rackspace.RackspaceCloudFiles` or :class:`~aws.SimpleStorageService`. :param message_queue: The optional object used as the backend for alerting other processes that a new message is in the object store. Currently this can be an instance of - :class:`~rackspace.RackspaceCloudQueues` or :class:`~aws.SimpleQueueService`. """ diff --git a/slimta/cloudstorage/aws.py b/slimta/cloudstorage/aws.py index fc42916f..ad9a533d 100644 --- a/slimta/cloudstorage/aws.py +++ b/slimta/cloudstorage/aws.py @@ -62,8 +62,7 @@ import uuid import json - -from six.moves import cPickle +import pickle import gevent from boto.s3.key import Key @@ -105,7 +104,7 @@ def _get_key(self, id): def write_message(self, envelope, timestamp): key = self.Key(self.bucket) key.key = self.prefix+str(uuid.uuid4()) - envelope_raw = cPickle.dumps(envelope, cPickle.HIGHEST_PROTOCOL) + envelope_raw = pickle.dumps(envelope, pickle.HIGHEST_PROTOCOL) with gevent.Timeout(self.timeout): key.set_metadata('timestamp', json.dumps(timestamp)) key.set_metadata('attempts', '') @@ -137,7 +136,7 @@ def get_message(self, id): timestamp_raw = key.get_metadata('timestamp') attempts_raw = key.get_metadata('attempts') delivered_raw = key.get_metadata('delivered_indexes') - envelope = cPickle.loads(envelope_raw) + envelope = pickle.loads(envelope_raw) meta = {'timestamp': json.loads(timestamp_raw)} if attempts_raw: meta['attempts'] = json.loads(attempts_raw) diff --git a/slimta/cloudstorage/rackspace.py b/slimta/cloudstorage/rackspace.py deleted file mode 100644 index caa91a38..00000000 --- a/slimta/cloudstorage/rackspace.py +++ /dev/null @@ -1,577 +0,0 @@ -# Copyright (c) 2013 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. -# - -"""This module defines the queue storage mechanism specific to the `Rackspace -Cloud`_ hosting service. It requires an account as well as the `Cloud Files`_ -and optionally the `Cloud Queues`_ services. - -For each queued message, the contents and metadata of the message are written -to *Cloud Files*. Upon success, a reference to the message is injected into -*Cloud Queues* as a new message. - -The *Cloud Queues* service is only necessary for alerting separate *slimta* -processes that a new message has been queued. If reception and relaying are -happening in the same process, *Cloud Queues* is unnecessary. - -:: - - auth = RackspaceCloudAuth({'username': 'slimta', 'api_key': 'xxxxxx'}, - region='IAD') - cloud_files = RackspaceCloudFiles(auth) - cloud_queues = RackspaceCloudQueues(auth) - - storage = CloudStorage(cloud_files, cloud_queues) - -.. _Rackspace Cloud: http://www.rackspace.com/cloud/ -.. _Cloud Files: http://www.rackspace.com/cloud/files/ -.. _Cloud Queues: http://www.rackspace.com/cloud/queues/ - -""" - -from __future__ import absolute_import - -import uuid -import json -from socket import getfqdn -from functools import partial - -from six.moves import cPickle -from six.moves.urllib.parse import urlsplit, urljoin, urlencode - -import gevent - -from slimta.http import get_connection -from slimta import logging -from . import CloudStorageError - -__all__ = ['RackspaceError', 'RackspaceCloudAuth', 'RackspaceCloudFiles', - 'RackspaceCloudQueues'] - -log = logging.getHttpLogger(__name__) - -_DEFAULT_AUTH_ENDPOINT = 'https://identity.api.rackspacecloud.com/v2.0/' -_DEFAULT_CLIENT_ID = str(uuid.uuid5(uuid.NAMESPACE_DNS, getfqdn())) - -_TIMESTAMP_HDR = 'X-Object-Meta-Timestamp' -_ATTEMPTS_HDR = 'X-Object-Meta-Attempts' -_DELIVERED_RCPTS_HDR = 'X-Object-Meta-Delivered-Rcpts' - - -class RackspaceError(CloudStorageError): - """Thrown when an unexpected status has been returned from a Rackspace - Cloud API request and the engine does not know how to continue. - - """ - - def __init__(self, response): - status = '{0!s} {1}'.format(response.status, response.reason) - msg = 'Received {0!r} from the API.'.format(status) - super(RackspaceError, self).__init__(msg) - - #: The :class:`~httplib.HTTPResponse` object that triggered the - #: exception. - self.response = response - - -class RackspaceCloudAuth(object): - """This class implements and manages the creation of authentication tokens - when :class:`RackspaceCloudFiles` or :class:`RackspaceCloudQueues` objects - require them. - - :param credentials: This dictionary defines how credentials are sent to the - Auth API. - - If the ``function`` key is defined, it must be a - callable that takes no arguments and returns a tuple. - The tuple must contain a token string, a Cloud Files - service endpoint, and a Cloud Queues service endpoint. - - Otherwise, this dictionary must have a ``username`` key - whose value is the Rackspace Cloud username string. - - The ``password`` key may be used to fetch tokens using - the account's password. Alternatively, the ``api_key`` - key may be used to fetch tokens using the account's API - key. With ``username``, either ``password`` or - ``api_key`` must be given. - - Optionally, ``tenant_id`` may also be provided for - situations where it is necessary for authentication. - :type credentials: dict - :param endpoint: If given, this is the Rackspace Cloud Auth endpoint to hit - when creating tokens. - :param region: When discovering API endpoints from the service catalog, - this is the endpoint region to use, e.g. ``IAD`` or ``HKG``. - If not given, the first region returned is used. - :param timeout: Timeout, in seconds, for requests to the Cloud Auth API to - create a new token for the session. - :param tls: Optional dictionary of TLS settings passed directly as keyword - arguments to :class:`gevent.ssl.SSLSocket`. This is only used - for URLs with the ``https`` scheme. - - """ - - def __init__(self, credentials, endpoint=_DEFAULT_AUTH_ENDPOINT, - region=None, timeout=None, tls=None): - super(RackspaceCloudAuth, self).__init__() - self.get_connection = get_connection - self.timeout = timeout - self.region = region - self.tls = tls or {} - self.token_func = None - self._token_id = None - - if 'function' in credentials: - self.token_func = credentials['function'] - elif 'username' in credentials: - username = credentials['username'] - tenant_id = credentials.get('tenant_id') - if 'password' in credentials: - password = credentials['password'] - self.token_func = partial(self._get_token_password, - endpoint, - username, password, tenant_id) - elif 'api_key' in credentials: - api_key = credentials['api_key'] - self.token_func = partial(self._get_token_api_key, - endpoint, - username, api_key, tenant_id) - if not self.token_func: - msg = 'Required keys not found in credentials dictionary.' - raise KeyError(msg) - - #: The current Cloud Queues API endpoint in use by the mechanism. This - #: should be populated automatically on authentication. - self.queues_endpoint = None - - #: The current Cloud Files API endpoint in use by the mechanism. This - #: should be populated automatically on authentication. - self.files_endpoint = None - - def _get_token(self, url, payload): - full_url = urljoin(url+'/', 'tokens') - parsed_url = urlsplit(full_url, 'http') - conn = self.get_connection(parsed_url, self.tls) - json_payload = json.dumps(payload, sort_keys=True) - headers = [('Host', parsed_url.hostname), - ('Content-Type', 'application/json'), - ('Content-Length', str(len(json_payload))), - ('Accept', 'application/json')] - with gevent.Timeout(self.timeout): - log.request(conn, 'POST', parsed_url.path, headers) - conn.putrequest('POST', parsed_url.path) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders(json_payload) - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - return self._get_token_response(res) - - def _get_token_response(self, response): - if response.status != 200: - raise RackspaceError(response) - payload = json.load(response) - token_id = payload['access']['token']['id'] - files_endpoint = None - queues_endpoint = None - for service in payload['access']['serviceCatalog']: - if service['type'] == 'object-store': - for endpoint in service['endpoints']: - if not self.region or endpoint['region'] == self.region: - files_endpoint = endpoint['publicURL'] - break - if service['type'] == 'rax:queues': - for endpoint in service['endpoints']: - if not self.region or endpoint['region'] == self.region: - queues_endpoint = endpoint['publicURL'] - break - return token_id, files_endpoint, queues_endpoint - - def _get_token_password(self, url, username, password, tenant_id): - payload = {'auth': {'passwordCredentials': {'username': username, - 'password': password}}} - if tenant_id: - payload['auth']['tenantId'] = tenant_id - return self._get_token(url, payload) - - def _get_token_api_key(self, url, username, api_key, tenant_id): - payload = {'auth': {'RAX-KSKEY:apiKeyCredentials': - {'username': username, 'apiKey': api_key}}} - if tenant_id: - payload['auth']['tenantId'] = tenant_id - return self._get_token(url, payload) - - @property - def token_id(self): - """The current token in use by the mechanism. - - """ - if not self._token_id: - self.create_token() - return self._token_id - - def create_token(self): - """Creates a new token for use in future requests to Rackspace Cloud - services. This method is called automatically in most cases. The new - token is stored in the :attr:`.token_id` attribute. - - """ - self._token_id, self.files_endpoint, self.queues_endpoint = \ - self.token_func() - - -class RackspaceCloudFiles(object): - """Instances of this class may be passed in to the - :class:`~slimta.cloudstorage.CloudStorage` constructor for the ``storage`` - parameter to use `Cloud Files`_ as the storage backend. - - Keys added to the container are generated with - ``prefix + str(uuid.uuid4())``. - - :param auth: The :class:`RackspaceCloudAuth` object used to manage tokens - this service. - :param container: The Cloud Files container name to use. The files in this - container will be named with random UUID strings. - :param timeout: Timeout, in seconds, for all requests to the Cloud Files - API to return before an exception is thrown. - :param tls: Optional dictionary of TLS settings passed directly as keyword - arguments to :class:`gevent.ssl.SSLSocket`. This is only used - for URLs with the ``https`` scheme. - :param prefix: The string prefixed to every key added to the bucket. - - """ - - def __init__(self, auth, container='slimta-queue', timeout=None, tls=None, - prefix=''): - super(RackspaceCloudFiles, self).__init__() - self.get_connection = get_connection - self.auth = auth - self.container = container - self.timeout = timeout - self.prefix = prefix - self.tls = tls or {} - - def _get_files_url(self, files_id=None): - url = urljoin(self.auth.files_endpoint+'/', self.container) - if files_id: - url = urljoin(url+'/', files_id) - return url - - def write_message(self, envelope, timestamp, retry=False): - envelope_raw = cPickle.dumps(envelope, cPickle.HIGHEST_PROTOCOL) - files_id = self.prefix + str(uuid.uuid4()) - url = self._get_files_url(files_id) - parsed_url = urlsplit(str(url), 'http') - conn = self.get_connection(parsed_url, self.tls) - headers = [('Host', parsed_url.hostname), - ('Content-Type', 'application/octet-stream'), - ('Content-Length', str(len(envelope_raw))), - (_TIMESTAMP_HDR, json.dumps(timestamp)), - ('X-Auth-Token', self.auth.token_id)] - with gevent.Timeout(self.timeout): - log.request(conn, 'PUT', parsed_url.path, headers) - conn.putrequest('PUT', parsed_url.path) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders(envelope_raw) - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - if res.status == 401 and not retry: - self.auth.create_token() - return self.write_message(envelope, timestamp, retry=True) - elif res.status != 201: - raise RackspaceError(res) - return files_id - - def _write_message_meta(self, files_id, meta_headers, retry=False): - url = self._get_files_url(files_id) - parsed_url = urlsplit(url, 'http') - conn = self.get_connection(parsed_url, self.tls) - headers = [('Host', parsed_url.hostname), - ('X-Auth-Token', self.auth.token_id)] + meta_headers - with gevent.Timeout(self.timeout): - log.request(conn, 'POST', parsed_url.path, headers) - conn.putrequest('POST', parsed_url.path) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders() - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - if res.status == 401 and not retry: - self.auth.create_token() - return self._write_message_meta(files_id, meta_headers, retry=True) - elif res.status != 202: - raise RackspaceError(res) - - def set_message_meta(self, files_id, timestamp=None, attempts=None, - delivered_indexes=None): - meta_headers = [] - if timestamp is not None: - timestamp_raw = json.dumps(timestamp) - meta_headers.append((_TIMESTAMP_HDR, timestamp_raw)) - if attempts is not None: - attempts_raw = json.dumps(attempts) - meta_headers.append((_ATTEMPTS_HDR, attempts_raw)) - if delivered_indexes is not None: - delivered_raw = json.dumps(delivered_indexes) - meta_headers.append((_DELIVERED_RCPTS_HDR, delivered_raw)) - return self._write_message_meta(files_id, meta_headers) - - def delete_message(self, files_id, retry=False): - url = self._get_files_url(files_id) - parsed_url = urlsplit(url, 'http') - conn = self.get_connection(parsed_url, self.tls) - headers = [('Host', parsed_url.hostname), - ('X-Auth-Token', self.auth.token_id)] - with gevent.Timeout(self.timeout): - log.request(conn, 'DELETE', parsed_url.path, headers) - conn.putrequest('DELETE', parsed_url.path) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders() - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - if res.status == 401 and not retry: - return self.delete_message(files_id, retry=True) - elif res.status != 204: - raise RackspaceError(res) - - def get_message(self, files_id, only_meta=False, retry=False): - url = self._get_files_url(files_id) - parsed_url = urlsplit(url, 'http') - conn = self.get_connection(parsed_url, self.tls) - headers = [('Host', parsed_url.hostname), - ('X-Auth-Token', self.auth.token_id)] - method = 'HEAD' if only_meta else 'GET' - with gevent.Timeout(self.timeout): - log.request(conn, method, parsed_url.path, headers) - conn.putrequest(method, parsed_url.path) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders() - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - data = None if only_meta else res.read() - if res.status == 401 and not retry: - self.auth.create_token() - return self.get_message(files_id, only_meta, retry=True) - if res.status == 404: - raise KeyError(files_id) - elif res.status != 200: - raise RackspaceError(res) - timestamp_raw = res.getheader(_TIMESTAMP_HDR) - attempts_raw = res.getheader(_ATTEMPTS_HDR, None) - delivered_raw = res.getheader(_DELIVERED_RCPTS_HDR, None) - meta = {'timestamp': json.loads(timestamp_raw)} - if attempts_raw: - meta['attempts'] = json.loads(attempts_raw) - if delivered_raw: - meta['delivered_indexes'] = json.loads(delivered_raw) - if only_meta: - return meta - else: - envelope = cPickle.loads(data) - return envelope, meta - - def get_message_meta(self, files_id): - return self.get_message(files_id, only_meta=True) - - def _list_messages_page(self, marker, retry=False): - url = self._get_files_url() - parsed_url = urlsplit(url, 'http') - conn = self.get_connection(parsed_url, self.tls) - headers = [('Host', parsed_url.hostname), - ('X-Auth-Token', self.auth.token_id)] - query = urlencode({'limit': '1000'}) - if marker: - query += '&{0}'.format(urlencode({'marker': marker})) - selector = '{0}?{1}'.format(parsed_url.path, query) - with gevent.Timeout(self.timeout): - log.request(conn, 'GET', selector, headers) - conn.putrequest('GET', selector) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders() - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - data = res.read() - if res.status == 401 and not retry: - self.auth.create_token() - return self._list_messages_page(marker, retry=True) - if res.status == 200: - lines = data.splitlines() - return [line for line in lines - if line.startswith(self.prefix)], lines[-1] - elif res.status == 204: - return [], None - else: - raise RackspaceError(res) - - def list_messages(self): - marker = None - ids = [] - while True: - ids_batch, marker = self._list_messages_page(marker) - if not marker: - break - ids.extend(ids_batch) - for id in ids: - timestamp, attempts = self.get_message_meta(id) - yield timestamp, id - - -class RackspaceCloudQueues(object): - """Instances of this class may be passed in to the - :class:`~slimta.cloudstorage.CloudStorage` constructor for the - ``message_queue`` parameter to use `Cloud Queues`_ as the message queue - backend to alert other processes that a new message was stored. - - :param auth: The :class:`RackspaceCloudAuth` object used to manage tokens - this service. - :param queue_name: The Cloud Files queue name to use. - :param client_id: The ``Client-ID`` header passed in with all Cloud Queues - requests. By default, this is generated using - :func:`~uuid.uuid5` in conjunction with - :func:`~socket.getfqdn` to be consistent across restarts. - :param timeout: Timeout, in seconds, for all requests to the Cloud Queues - API. - :param poll_pause: The time, in seconds, to idle between attempts to poll - the queue for new messages. - :param tls: Optional dictionary of TLS settings passed directly as keyword - arguments to :class:`gevent.ssl.SSLSocket`. This is only used - for URLs with the ``https`` scheme. - - """ - - def __init__(self, auth, queue_name='slimta-queue', client_id=None, - timeout=None, poll_pause=1.0, tls=None): - super(RackspaceCloudQueues, self).__init__() - self.get_connection = get_connection - self.auth = auth - self.queue_name = queue_name - self.client_id = client_id or _DEFAULT_CLIENT_ID - self.timeout = timeout - self.poll_pause = poll_pause - self.tls = tls or {} - - def queue_message(self, storage_id, timestamp, retry=False): - url = urljoin(self.auth.queues_endpoint+'/', - 'queues/{0}/messages'.format(self.queue_name)) - parsed_url = urlsplit(url, 'http') - conn = self.get_connection(parsed_url, self.tls) - payload = [{'ttl': 86400, - 'body': {'timestamp': timestamp, - 'storage_id': storage_id}}] - json_payload = json.dumps(payload, sort_keys=True) - headers = [('Host', parsed_url.hostname), - ('Client-ID', self.client_id), - ('Content-Type', 'application/json'), - ('Content-Length', str(len(json_payload))), - ('Accept', 'application/json'), - ('X-Auth-Token', self.auth.token_id)] - with gevent.Timeout(self.timeout): - log.request(conn, 'POST', parsed_url.path, headers) - conn.putrequest('POST', parsed_url.path) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders(json_payload) - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - if res.status == 401 and not retry: - self.auth.create_token() - return self.queue_message(storage_id, timestamp, retry=True) - elif res.status != 201: - raise RackspaceError(res) - - def _claim_queued_messages(self, retry=False): - url = urljoin(self.auth.queues_endpoint+'/', - 'queues/{0}/claims'.format(self.queue_name)) - parsed_url = urlsplit(url, 'http') - conn = self.get_connection(parsed_url, self.tls) - json_payload = '{"ttl": 3600, "grace": 3600}' - headers = [('Host', parsed_url.hostname), - ('Client-ID', self.client_id), - ('Content-Type', 'application/json'), - ('Content-Length', str(len(json_payload))), - ('Accept', 'application/json'), - ('X-Auth-Token', self.auth.token_id)] - with gevent.Timeout(self.timeout): - log.request(conn, 'POST', parsed_url.path, headers) - conn.putrequest('POST', parsed_url.path) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders(json_payload) - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - data = res.read() - if res.status == 401 and not retry: - self.auth.create_token() - return self._claim_queued_messages(retry=True) - if res.status == 201: - messages = json.loads(data) - return [(msg['body'], msg['href']) for msg in messages] - elif res.status == 204: - return [] - else: - raise RackspaceError(res) - - def poll(self): - messages = self._claim_queued_messages() - for body, href in messages: - yield (body['timestamp'], body['storage_id'], href) - - def sleep(self): - gevent.sleep(self.poll_pause) - - def delete(self, href, retry=False): - url = self.auth.queues_endpoint - parsed_url = urlsplit(url, 'http') - conn = self.get_connection(parsed_url, self.tls) - headers = [('Host', parsed_url.hostname), - ('Client-ID', self.client_id), - ('Content-Type', 'application/json'), - ('Accept', 'application/json'), - ('X-Auth-Token', self.auth.token_id)] - with gevent.Timeout(self.timeout): - log.request(conn, 'DELETE', href, headers) - conn.putrequest('DELETE', href) - for name, value in headers: - conn.putheader(name, value) - conn.endheaders() - res = conn.getresponse() - status = '{0!s} {1}'.format(res.status, res.reason) - log.response(conn, status, res.getheaders()) - if res.status == 401 and not retry: - self.auth.create_token() - return self.delete(href, retry=True) - elif res.status != 204: - raise RackspaceError(res) - - -# vim:et:fdm=marker:sts=4:sw=4:ts=4 diff --git a/slimta/diskstorage/__init__.py b/slimta/diskstorage/__init__.py index 4e470b4f..dbe5c563 100644 --- a/slimta/diskstorage/__init__.py +++ b/slimta/diskstorage/__init__.py @@ -33,11 +33,10 @@ import os import uuid import os.path +import pickle from tempfile import mkstemp from functools import partial -from six.moves import cPickle - from pyaio import aio_read, aio_write # type: ignore import gevent from gevent.event import AsyncResult # type: ignore @@ -79,6 +78,7 @@ def _stop_keep_awake_thread(cls): try: cls._keep_awake_refs -= 1 if cls._keep_awake_refs <= 0: + assert cls._keep_awake_thread is not None cls._keep_awake_thread.kill() cls._keep_awake_thread = None finally: @@ -127,7 +127,7 @@ def dump(self, data): self._stop_keep_awake_thread() def pickle_dump(self, obj): - return self.dump(cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)) + return self.dump(pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)) def _read_callback(self, event, buf, ret, errno): if ret > 0: @@ -163,7 +163,7 @@ def load(self): raise RuntimeError() def pickle_load(self): - return cPickle.loads(self.load()) + return pickle.loads(self.load()) class DiskOps(object): diff --git a/slimta/edge/__init__.py b/slimta/edge/__init__.py index 3ff8b3f1..2460314c 100644 --- a/slimta/edge/__init__.py +++ b/slimta/edge/__init__.py @@ -125,6 +125,7 @@ def __init__(self, listener, queue, pool=None, hostname=None): self.server = None def _handle(self, socket, address): + assert self.server is not None log.accept(self.server.socket, socket, address) try: self.handle(socket, address) @@ -145,9 +146,11 @@ def handle(self, socket, address): raise NotImplementedError() def kill(self): + assert self.server is not None self.server.stop() def _run(self): + assert self.server is not None self.server.start() self.server.serve_forever() diff --git a/slimta/edge/smtp.py b/slimta/edge/smtp.py index fa04d038..a1fe51b7 100644 --- a/slimta/edge/smtp.py +++ b/slimta/edge/smtp.py @@ -160,6 +160,7 @@ def MAIL(self, reply, address, params): def RCPT(self, reply, address, params): self._call_validator('rcpt', reply, address, params) if reply.code == '250': + assert self.envelope is not None self.envelope.recipients.append(address) def DATA(self, reply): @@ -179,6 +180,7 @@ def HAVE_DATA(self, reply, data, err): if self._ptr_lookup is not None: self.reverse_address = self._ptr_lookup.finish() + assert self.envelope is not None self.envelope.client['ip'] = self.address[0] self.envelope.client['host'] = self.reverse_address self.envelope.client['name'] = self.ehlo_as diff --git a/slimta/edge/wsgi.py b/slimta/edge/wsgi.py index 1f3d921d..2738325f 100644 --- a/slimta/edge/wsgi.py +++ b/slimta/edge/wsgi.py @@ -213,6 +213,7 @@ def _validate_request(self, environ): self._run_validators(environ) def _run_validators(self, environ): + assert self.validator_class is not None validators = self.validator_class(environ) validators.validate_ehlo(self._get_ehlo(environ)) validators.validate_sender(self._get_sender(environ)) diff --git a/slimta/envelope.py b/slimta/envelope.py index 82e64a92..e97fff3f 100644 --- a/slimta/envelope.py +++ b/slimta/envelope.py @@ -28,11 +28,11 @@ import re import copy +from email.generator import BytesGenerator +from email.parser import BytesParser +from email.policy import SMTP from io import BytesIO -from slimta.util import pycompat -from slimta.util.pycompat import generator_class, parser_class - __all__ = ['Envelope'] _HEADER_BOUNDARY = re.compile(br'\r?\n\s*?\n') @@ -87,15 +87,12 @@ def __init__(self, sender=None, recipients=None, self.timestamp = None def _parse_data(self, data, *extra): - return parser_class().parse(BytesIO(data), *extra) + return BytesParser(policy=SMTP).parse(BytesIO(data), *extra) def _msg_generator(self, msg): outfp = BytesIO() - generator_class(outfp).flatten(msg, False) - if pycompat.PY3: - return outfp.getvalue() - else: - return re.sub(_LINE_BREAK, b'\r\n', outfp.getvalue()) + BytesGenerator(outfp, policy=SMTP).flatten(msg, False) + return outfp.getvalue() def _merge_payloads(self, headers, payload): if headers.get_payload(): @@ -142,6 +139,7 @@ def flatten(self): :returns: Tuple of two bytestrings: ``(header_data, message_data)`` """ + assert self.message is not None header_data = self._msg_generator(self.headers) return header_data, self.message @@ -177,6 +175,7 @@ def encode_7bit(self, encoder=None): :raises: UnicodeDecodeError """ + assert self.message is not None try: self.message.decode('ascii') except UnicodeDecodeError: diff --git a/slimta/http/__init__.py b/slimta/http/__init__.py index 360697da..a4cebf6a 100644 --- a/slimta/http/__init__.py +++ b/slimta/http/__init__.py @@ -30,12 +30,12 @@ from __future__ import absolute_import +from http import client as httplib from socket import error as socket_error +from urllib import parse as urlparse from gevent import socket -from slimta.util.pycompat import httplib, urlparse - __all__ = ['HTTPConnection', 'HTTPSConnection', 'get_connection'] @@ -82,11 +82,15 @@ def get_connection(url, context=None): :type context: :py:class:`~ssl.SSLContext` """ - if isinstance(url, (str, bytes)): - url = urlparse.urlsplit(url, 'http') - host = url.netloc or 'localhost' + if isinstance(url, bytes): + url = url.decode('ascii') + if isinstance(url, str): + parsed = urlparse.urlsplit(url, scheme='http') + else: + parsed = url + host = parsed.netloc or 'localhost' - if url.scheme == 'https': + if parsed.scheme == 'https': conn = HTTPSConnection(host, context=context) else: conn = HTTPConnection(host) diff --git a/slimta/logging/__init__.py b/slimta/logging/__init__.py index 48af17a3..b2437a9c 100644 --- a/slimta/logging/__init__.py +++ b/slimta/logging/__init__.py @@ -29,6 +29,7 @@ import traceback import re import logging +import reprlib from ast import literal_eval from .log import log_repr @@ -36,12 +37,11 @@ from .subprocess import SubprocessLogger from .queuestorage import QueueStorageLogger from .http import HttpLogger -from ..util.pycompat import reprlib __all__ = ['getSocketLogger', 'getSubprocessLogger', 'getQueueStorageLogger', 'getHttpLogger', 'log_exception', 'parseline'] -threading._DummyThread._Thread__stop = lambda x: 42 +threading._DummyThread._Thread__stop = lambda x: 42 # type: ignore def getSocketLogger(name): @@ -109,7 +109,7 @@ def log_exception(name, **kwargs): """ type, value, tb = sys.exc_info() - if not value: + if not value or not type: return tb_repr = reprlib.Repr() tb_repr.maxstring = 10000 diff --git a/slimta/logging/log.py b/slimta/logging/log.py index f6127e08..855c14bb 100644 --- a/slimta/logging/log.py +++ b/slimta/logging/log.py @@ -21,7 +21,7 @@ from __future__ import absolute_import -from slimta.util.pycompat import reprlib +import reprlib __all__ = ['log_repr', 'logline'] diff --git a/slimta/lookup/drivers/dbapi2.py b/slimta/lookup/drivers/dbapi2.py index 4a50e980..0635887d 100644 --- a/slimta/lookup/drivers/dbapi2.py +++ b/slimta/lookup/drivers/dbapi2.py @@ -37,7 +37,7 @@ from __future__ import absolute_import import sqlite3 -from collections import Mapping +from collections.abc import Mapping from contextlib import contextmanager from . import LookupBase @@ -92,6 +92,7 @@ def _do_lookup(self, kwargs): if self.query_param_order is not None: params = [kwargs[key] for key in self.query_param_order] with self.conn_ctxmgr() as conn: + assert conn is not None cur = conn.cursor() try: cur.execute(self.query, params) @@ -103,6 +104,7 @@ def _do_lookup(self, kwargs): result_order = row.keys() except AttributeError: result_order = self.result_order + assert result_order is not None ret = {} for i, key in enumerate(result_order): ret[key] = row[i] diff --git a/slimta/queue/__init__.py b/slimta/queue/__init__.py index ff08193c..b4545654 100644 --- a/slimta/queue/__init__.py +++ b/slimta/queue/__init__.py @@ -30,7 +30,7 @@ import time import bisect -import collections +import collections.abc from itertools import repeat import gevent @@ -45,7 +45,6 @@ from slimta.smtp.reply import Reply from slimta.bounce import Bounce from slimta.policy import QueuePolicy -from slimta.util.pycompat import map __all__ = ['QueueError', 'Queue', 'QueueStorage'] @@ -288,6 +287,7 @@ def _pool_run(self, which, func, *args, **kwargs): def _pool_imap(self, which, func, *iterables): pool = getattr(self, which+'_pool', gevent) + assert pool is not None threads = map(pool.spawn, repeat(func), *iterables) ret = [] for thread in threads: @@ -297,6 +297,7 @@ def _pool_imap(self, which, func, *iterables): def _pool_spawn(self, which, func, *args, **kwargs): pool = getattr(self, which+'_pool', gevent) + assert pool is not None return pool.spawn(func, *args, **kwargs) def _add_queued(self, entry): @@ -382,6 +383,7 @@ def _retry_later(self, id, envelope, replies): return True def _attempt(self, id, envelope, attempts): + assert self.relay is not None try: results = self.relay._attempt(envelope, attempts) except TransientRelayError as e: @@ -394,9 +396,9 @@ def _attempt(self, id, envelope, attempts): self._pool_spawn('store', self._retry_later, id, envelope, reply) raise else: - if isinstance(results, collections.Mapping): + if isinstance(results, collections.abc.Mapping): self._handle_partial_relay(id, envelope, attempts, results) - elif isinstance(results, collections.Sequence): + elif isinstance(results, collections.abc.Sequence): results = dict(zip(envelope.recipients, results)) self._handle_partial_relay(id, envelope, attempts, results) else: diff --git a/slimta/redisstorage/__init__.py b/slimta/redisstorage/__init__.py index 0049fd30..eb89df3a 100644 --- a/slimta/redisstorage/__init__.py +++ b/slimta/redisstorage/__init__.py @@ -29,8 +29,7 @@ import uuid import time - -from six.moves import cPickle +import pickle import redis from gevent import socket @@ -85,13 +84,13 @@ def _get_key(self, id): return self.prefix + id def write(self, envelope, timestamp): - envelope_raw = cPickle.dumps(envelope, cPickle.HIGHEST_PROTOCOL) + envelope_raw = pickle.dumps(envelope, pickle.HIGHEST_PROTOCOL) while True: id = uuid.uuid4().hex key = self._get_key(id) if self.redis.hsetnx(key, 'envelope', envelope_raw): - queue_raw = cPickle.dumps((timestamp, id), - cPickle.HIGHEST_PROTOCOL) + queue_raw = pickle.dumps((timestamp, id), + pickle.HIGHEST_PROTOCOL) pipe = self.redis.pipeline() pipe.hmset(key, {'timestamp': timestamp, 'attempts': 0}) @@ -113,9 +112,9 @@ def set_recipients_delivered(self, id, rcpt_indexes): current = self.redis.hget(self._get_key(id), 'delivered_indexes') new_indexes = rcpt_indexes if current: - new_indexes = cPickle.loads(current) + rcpt_indexes + new_indexes = pickle.loads(current) + rcpt_indexes self.redis.hset(self._get_key(id), 'delivered_indexes', - cPickle.dumps(new_indexes, cPickle.HIGHEST_PROTOCOL)) + pickle.dumps(new_indexes, pickle.HIGHEST_PROTOCOL)) log.update_meta(id, delivered_indexes=rcpt_indexes) def load(self): @@ -131,10 +130,10 @@ def get(self, id): 'delivered_indexes') if not envelope_raw: raise KeyError(id) - envelope = cPickle.loads(envelope_raw) + envelope = pickle.loads(envelope_raw) del envelope_raw if delivered_indexes_raw: - delivered_indexes = cPickle.loads(delivered_indexes_raw) + delivered_indexes = pickle.loads(delivered_indexes_raw) self._remove_delivered_rcpts(envelope, delivered_indexes) return envelope, int(attempts or 0) @@ -145,7 +144,7 @@ def remove(self, id): def wait(self): ret = self.redis.blpop([self.queue_key], 0) if ret: - return [cPickle.loads(ret[1])] + return [pickle.loads(ret[1])] return [] diff --git a/slimta/relay/__init__.py b/slimta/relay/__init__.py index d3ce6e66..270c7742 100644 --- a/slimta/relay/__init__.py +++ b/slimta/relay/__init__.py @@ -43,6 +43,7 @@ def __init__(self, msg, reply=None): if reply: self.reply = reply else: + assert self._default_esc is not None reply_msg = ' '.join((self._default_esc, msg)) self.reply = Reply(self._default_code, reply_msg) diff --git a/slimta/relay/http.py b/slimta/relay/http.py index f266c974..34e4982d 100644 --- a/slimta/relay/http.py +++ b/slimta/relay/http.py @@ -31,13 +31,13 @@ import re import socket from base64 import b64encode +from urllib import parse as urlparse import gevent from slimta import logging from slimta.smtp.reply import Reply from slimta.http import get_connection -from slimta.util.pycompat import urlparse from . import PermanentRelayError, TransientRelayError from .pool import RelayPool, RelayPoolClient from .smtp import SmtpRelayError @@ -95,6 +95,7 @@ def _handle_request(self, result, envelope): method = self.relay.http_verb if not self.conn: self._new_conn() + assert self.conn is not None with gevent.Timeout(self.relay.timeout): msg_headers, msg_body = envelope.flatten() headers = self._build_headers(envelope, msg_headers, msg_body) diff --git a/slimta/relay/smtp/client.py b/slimta/relay/smtp/client.py index ace19e34..8f2268d9 100644 --- a/slimta/relay/smtp/client.py +++ b/slimta/relay/smtp/client.py @@ -88,6 +88,7 @@ def _connect(self): @current_command(b'[BANNER]') def _banner(self): + assert self.client is not None with Timeout(self.command_timeout): banner = self.client.get_banner() if banner.is_error(): @@ -95,10 +96,12 @@ def _banner(self): @current_command(b'EHLO') def _ehlo(self): + assert self.ehlo_as is not None try: - ehlo_as = self.ehlo_as(self.address) + ehlo_as = self.ehlo_as(self.address) # type: ignore except TypeError: ehlo_as = self.ehlo_as + assert self.client is not None with Timeout(self.command_timeout): ehlo = self.client.ehlo(ehlo_as) if ehlo.is_error(): @@ -109,6 +112,7 @@ def _ehlo(self): @current_command(b'HELO') def _helo(self, ehlo_as): + assert self.client is not None with Timeout(self.command_timeout): helo = self.client.helo(ehlo_as) if helo.is_error(): @@ -117,6 +121,7 @@ def _helo(self, ehlo_as): @current_command(b'STARTTLS') def _starttls(self): + assert self.client is not None with Timeout(self.command_timeout): starttls = self.client.starttls(self.context) if starttls.is_error() and self.tls_required: @@ -124,10 +129,12 @@ def _starttls(self): @current_command(b'AUTH') def _authenticate(self): + assert self.credentials is not None try: - credentials = self.credentials() + credentials = self.credentials() # type: ignore except TypeError: credentials = self.credentials + assert self.client is not None with Timeout(self.command_timeout): auth = self.client.auth(*credentials, mechanism=self.auth_mechanism) @@ -135,6 +142,7 @@ def _authenticate(self): raise SmtpRelayError.factory(auth) def _handshake(self): + assert self.client is not None if self.tls_immediately: self.client.encrypt(self.context) self._banner() @@ -150,11 +158,13 @@ def _handshake(self): @current_command(b'RSET') def _rset(self): + assert self.client is not None with Timeout(self.command_timeout): self.client.rset() @current_command(b'MAIL') def _mailfrom(self, sender): + assert self.client is not None with Timeout(self.command_timeout): mailfrom = self.client.mailfrom(sender, auth=False) if mailfrom and mailfrom.is_error(): @@ -163,11 +173,13 @@ def _mailfrom(self, sender): @current_command(b'RCPT') def _rcptto(self, rcpt): + assert self.client is not None with Timeout(self.command_timeout): return self.client.rcptto(rcpt) @current_command(b'DATA') def _data(self): + assert self.client is not None with Timeout(self.command_timeout): return self.client.data() @@ -184,12 +196,14 @@ def _check_replies(self, mailfrom, rcpttos, data): @current_command(b'[SEND_DATA]') def _send_empty_data(self): + assert self.client is not None with Timeout(self.data_timeout): self.client.send_empty_data() @current_command(b'[SEND_DATA]') def _send_message_data(self, envelope): header_data, message_data = envelope.flatten() + assert self.client is not None with Timeout(self.data_timeout): send_data = self.client.send_data( header_data, message_data) @@ -199,6 +213,7 @@ def _send_message_data(self, envelope): return send_data def _handle_encoding(self, envelope): + assert self.client is not None if '8BITMIME' not in self.client.extensions: try: envelope.encode_7bit(self.binary_encoder) @@ -240,6 +255,7 @@ def _deliver(self, result, envelope): result.set(rcpt_results) def _check_server_timeout(self): + assert self.client is not None try: if self.client.has_reply_waiting(): with Timeout(self.command_timeout): @@ -250,6 +266,7 @@ def _check_server_timeout(self): return False def _disconnect(self): + assert self.client is not None try: with Timeout(self.command_timeout): self.client.quit() @@ -260,8 +277,10 @@ def _disconnect(self): self.client.io.close() def _get_error_reply(self, exc): + assert self.client is not None try: - if self.client.last_error.code == '421': + if self.client.last_error is not None and \ + self.client.last_error.code == '421': return self.client.last_error except Exception: pass diff --git a/slimta/relay/smtp/lmtpclient.py b/slimta/relay/smtp/lmtpclient.py index 770a1e98..90a1a95d 100644 --- a/slimta/relay/smtp/lmtpclient.py +++ b/slimta/relay/smtp/lmtpclient.py @@ -35,10 +35,12 @@ class LmtpRelayClient(SmtpRelayClient): _client_class = LmtpClient def _ehlo(self): + assert self.ehlo_as is not None try: - ehlo_as = self.ehlo_as(self.address) + ehlo_as = self.ehlo_as(self.address) # type: ignore except TypeError: ehlo_as = self.ehlo_as + assert self.client is not None with Timeout(self.command_timeout): lhlo = self.client.lhlo(ehlo_as) if lhlo.is_error(): @@ -55,7 +57,7 @@ def _deliver(self, result, envelope): self._rset() return had_errors = False - for rcpt, reply in data_results: + for rcpt, reply in data_results: # type: ignore if reply.is_error(): rcpt_results[rcpt] = SmtpRelayError.factory(reply) had_errors = True diff --git a/slimta/smtp/auth.py b/slimta/smtp/auth.py index 18804390..fce7361c 100644 --- a/slimta/smtp/auth.py +++ b/slimta/smtp/auth.py @@ -24,8 +24,8 @@ import re import base64 -from pysasl import AuthenticationError, ServerChallenge, \ - AuthenticationCredentials +from pysasl import AuthenticationError, ServerChallenge, ChallengeResponse +from pysasl.creds import AuthenticationCredentials from . import SmtpError from .reply import Reply @@ -107,12 +107,11 @@ def _parse_arg(self, arg): @property def server_mechanisms(self): - return [mech for mech in self.auth.server_mechanisms] + return self.auth.server_mechanisms @property def client_mechanisms(self): - return [mech for mech in self.auth.client_mechanisms - if self.io.encrypted or not getattr(mech, 'insecure', False)] + return self.auth.client_mechanisms def _server_challenge(self, challenge, response=None): if not response: @@ -141,11 +140,9 @@ def server_attempt(self, arg): except AuthenticationError as exc: raise UnexpectedAuthError(exc) except ServerChallenge as chal: - resp = self._server_challenge(chal.challenge, - mechanism_arg) + resp = self._server_challenge(chal.data, mechanism_arg) mechanism_arg = None - chal.set_response(resp) - responses.append(chal) + responses.append(ChallengeResponse(chal.data, resp)) raise InvalidMechanismError() def _client_respond(self, mech, response, first=False): @@ -171,15 +168,14 @@ def client_attempt(self, authcid, secret, authzid, mech_name): if not mechanism: raise InvalidMechanismError() creds = AuthenticationCredentials(authcid, secret, authzid) - resp = mechanism.client_attempt(creds, []) + responses = [] + resp = mechanism.client_attempt(creds, responses) chal, reply = self._client_respond( - mechanism, resp.get_response(), True) - responses = [resp] + mechanism, resp.response, True) while chal is not None: - resp.set_challenge(chal) + responses.append(ServerChallenge(chal)) resp = mechanism.client_attempt(creds, responses) - responses.append(resp) - chal, reply = self._client_respond(mechanism, resp.get_response()) + chal, reply = self._client_respond(mechanism, resp.response) return reply diff --git a/slimta/smtp/client.py b/slimta/smtp/client.py index 5642b50c..c9c10c3a 100644 --- a/slimta/smtp/client.py +++ b/slimta/smtp/client.py @@ -268,9 +268,11 @@ def auth(self, authcid, secret, authzid=None, mechanism=None): self._flush_pipeline() if 'AUTH' not in self.extensions: return unknown_command - advertised = [self._encode(mech_name) for mech_name in - self.extensions.getparam('AUTH').split()] - auth = AuthSession(SASLAuth(advertised), self.io) + auth_ext = self.extensions.getparam('AUTH') + assert auth_ext is not None + advertised = [self._encode(mech_name) + for mech_name in auth_ext.split()] + auth = AuthSession(SASLAuth.named(advertised), self.io) if not mechanism and auth.client_mechanisms: mechanism = auth.client_mechanisms[0].name return auth.client_attempt(authcid, secret, authzid, mechanism) diff --git a/slimta/smtp/datareader.py b/slimta/smtp/datareader.py index 917b900f..9e74246a 100644 --- a/slimta/smtp/datareader.py +++ b/slimta/smtp/datareader.py @@ -112,6 +112,7 @@ def recv_piece(self): return not self.EOD def return_all(self): + assert self.EOD is not None data_lines = self.lines[:self.EOD] after_data_lines = self.lines[self.EOD+1:] diff --git a/slimta/smtp/datasender.py b/slimta/smtp/datasender.py index fcc045aa..8c8579df 100644 --- a/slimta/smtp/datasender.py +++ b/slimta/smtp/datasender.py @@ -28,7 +28,9 @@ class DataSender(object): """Class that writes multi-line message data, taking care of dot marker + """ + def __init__(self, *parts): self.parts = parts self._calc_end_marker() @@ -50,9 +52,6 @@ def _calc_end_marker(self): self.end_marker = b'\r\n.\r\n' def _process_part(self, part): - """ - :type part: bytes - """ part_len = len(part) i = 0 if part_len > 0 and part[0:1] == b'.': @@ -68,9 +67,8 @@ def _process_part(self, part): i = index+2 def __iter__(self): - iterables = [self._process_part(part) for part in self.parts] - iterables.append((self.end_marker, )) - return chain.from_iterable(iterables) + parts = [self._process_part(part) for part in self.parts] + return chain(chain.from_iterable(parts), (self.end_marker, )) def send(self, io): for piece in self: diff --git a/slimta/smtp/io.py b/slimta/smtp/io.py index c897c8fd..3be81cec 100644 --- a/slimta/smtp/io.py +++ b/slimta/smtp/io.py @@ -138,6 +138,7 @@ def flush_send(self): self.send_buffer = BytesIO() def recv_reply(self): + body = None code = None message_lines = [] incomplete = True @@ -173,6 +174,8 @@ def recv_reply(self): input = self.recv_buffer body = b'\r\n'.join(message_lines) + assert body is not None + assert code is not None try: return code.decode('ascii'), body.decode('utf-8') except UnicodeDecodeError: diff --git a/slimta/smtp/reply.py b/slimta/smtp/reply.py index c0556216..da07e22e 100644 --- a/slimta/smtp/reply.py +++ b/slimta/smtp/reply.py @@ -30,8 +30,6 @@ import re -from slimta.util import pycompat - __all__ = ['Reply', 'unknown_command', 'unknown_parameter', 'bad_sequence', 'bad_arguments', 'timed_out', 'unhandled_error', 'connection_failed', 'tls_failure', 'invalid_credentials'] @@ -136,12 +134,6 @@ def __contains__(self, substr): else: return substr in str(self) - # Python 2 compat. - if pycompat.PY2: - __nonzero__ = __bool__ - __unicode__ = __str__ - __str__ = __bytes__ - def copy(self, reply): """Direct-copies the given reply code and message into the current object. This is generally useful for sending pre-defined responses. diff --git a/slimta/smtp/server.py b/slimta/smtp/server.py index 60140b49..bee91564 100644 --- a/slimta/smtp/server.py +++ b/slimta/smtp/server.py @@ -119,7 +119,7 @@ def __init__(self, socket, handlers, address=None, auth=False, if isinstance(auth, list): auth_obj = SASLAuth(auth) else: - auth_obj = SASLAuth() + auth_obj = SASLAuth.defaults() auth_session = AuthSession(auth_obj, self.io) self.extensions.add('AUTH', auth_session) @@ -327,6 +327,7 @@ def _command_AUTH(self, arg): bad_sequence.send(self.io) return auth = self.extensions.getparam('AUTH') + assert auth is not None try: result = auth.server_attempt(arg) diff --git a/slimta/util/__init__.py b/slimta/util/__init__.py index 2c164cae..0a361ebc 100644 --- a/slimta/util/__init__.py +++ b/slimta/util/__init__.py @@ -112,7 +112,7 @@ def create_listeners(address, else: listeners.append(sock) if last_exc and not listeners: - raise last_exc + raise last_exc # type: ignore return listeners diff --git a/slimta/util/dns.py b/slimta/util/dns.py index 85bf0f2d..ae01415c 100644 --- a/slimta/util/dns.py +++ b/slimta/util/dns.py @@ -123,6 +123,7 @@ def _distinct(cls, read_fds, write_fds): @classmethod def _register_fds(cls, poll, prev_fds_map): + assert cls._channel is not None # we must mimic the behavior of pycares sock_state_cb to maintain # compatibility with custom DNSResolver.channel objects. fds_map = OrderedDict() @@ -145,6 +146,7 @@ def _register_fds(cls, poll, prev_fds_map): @classmethod def _wait_channel(cls): + assert cls._channel is not None poll = select.poll() fds_map = OrderedDict() try: diff --git a/slimta/util/dnsbl.py b/slimta/util/dnsbl.py index 26beec19..36b13549 100644 --- a/slimta/util/dnsbl.py +++ b/slimta/util/dnsbl.py @@ -158,6 +158,7 @@ def get(self, ip, timeout=None): """ matches = set() group = Group() + assert self.pool is not None with gevent.Timeout(timeout, None): for dnsbl in self.dnsbls: thread = self.pool.spawn(self._run_dnsbl_get, @@ -180,6 +181,7 @@ def get_reasons(self, matches, ip, timeout=None): """ reasons = dict.fromkeys(matches) group = Group() + assert self.pool is not None with gevent.Timeout(timeout, None): for dnsbl in self.dnsbls: if dnsbl.address in matches: diff --git a/slimta/util/ptrlookup.py b/slimta/util/ptrlookup.py index c43bbed2..c3047e9c 100644 --- a/slimta/util/ptrlookup.py +++ b/slimta/util/ptrlookup.py @@ -121,6 +121,7 @@ def finish(self, runtime=None): :returns: The PTR lookup results (a hostname string) or ``None``. """ + assert self.start_time is not None try: if runtime is None: result = self.get(block=False) diff --git a/slimta/util/pycompat.py b/slimta/util/pycompat.py deleted file mode 100644 index f81edad1..00000000 --- a/slimta/util/pycompat.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (c) 2016 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. -# - -"""This module makes compatibility between Python 2 and Python 3 a little more -convenient. It's intended to avoid dependence on the ``six`` library. - -""" - -from __future__ import absolute_import - -import sys -from functools import partial - -__all__ = ['PY3', 'PY2', 'map', 'urlparse', 'httplib', 'reprlib', - 'parser_class', 'generator_class'] - -#: True if the interpreter is Python 3.x, False otherwise. -PY3 = (sys.version_info[0] == 3) - -#: True if the interpreter is Python 2.x, False otherwise. -PY2 = (sys.version_info[0] == 2) - -if PY3: - map_func = map - - from urllib import parse as urlparse_mod - from http import client as httplib_mod - import reprlib as reprlib_mod - - from email.generator import BytesGenerator - from email.parser import BytesParser - from email.policy import SMTP - parser = partial(BytesParser, policy=SMTP) - generator = partial(BytesGenerator, policy=SMTP) -else: - from itertools import imap - map_func = imap - - import urlparse as urlparse_mod - import httplib as httplib_mod - import repr as reprlib_mod - - from email.generator import Generator - from email.parser import Parser - parser = Parser - generator = Generator - -#: The ``itertools.imap`` function on Python 2, ``map`` on Python 3. -map = map_func - -#: The ``urlparse`` module on Python 2, ``urllib.parse`` on Python 3. -urlparse = urlparse_mod - -#: The ``httplib`` module on Python 2, ``http.client`` on Python 3. In Python -#: 2, the deprecated ``strict`` parameter is set to True. -httplib = httplib_mod - -#: The ``repr`` module on Python 2, ``reprlib`` on Python 3. -reprlib = reprlib_mod - -#: An ``email.parser.Parser`` instance on Python 2, an -#: ``email.parser.BytesParser`` instance on Python 3. -parser_class = parser - -#: An ``email.generator.Generator`` instance on Python 2, an -#: ``email.generator.BytesGenerator`` instance on Python 3. -generator_class = generator - - -# vim:et:fdm=marker:sts=4:sw=4:ts=4 diff --git a/test/.lvimrc b/test/.lvimrc new file mode 100644 index 00000000..d1aafd87 --- /dev/null +++ b/test/.lvimrc @@ -0,0 +1 @@ +set tw=0 diff --git a/test/test_slimta_cloudstorage.py b/test/test_slimta_cloudstorage.py index c7779e62..052c86ec 100644 --- a/test/test_slimta_cloudstorage.py +++ b/test/test_slimta_cloudstorage.py @@ -1,5 +1,5 @@ -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.queue import QueueError from slimta.envelope import Envelope diff --git a/test/test_slimta_cloudstorage_aws.py b/test/test_slimta_cloudstorage_aws.py index 7e92369d..97813452 100644 --- a/test/test_slimta_cloudstorage_aws.py +++ b/test/test_slimta_cloudstorage_aws.py @@ -1,7 +1,7 @@ import json -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from six.moves import cPickle import gevent diff --git a/test/test_slimta_cloudstorage_rackspace_auth.py b/test/test_slimta_cloudstorage_rackspace_auth.py deleted file mode 100644 index 531d6d1c..00000000 --- a/test/test_slimta_cloudstorage_rackspace_auth.py +++ /dev/null @@ -1,95 +0,0 @@ - -import json - -from mox3.mox import MoxTestBase, IsA, Func - -from slimta.cloudstorage.rackspace import RackspaceError, RackspaceCloudAuth - - -class TestRackspaceCloudAuth(MoxTestBase): - - def setUp(self): - super(TestRackspaceCloudAuth, self).setUp() - self.response_payload = {'access': { - 'token': {'id': 'tokenid'}, - 'serviceCatalog': [ - {'type': 'object-store', - 'endpoints': [ - {'region': 'TEST', - 'publicURL': 'http://files/v1'}, - {'region': 'OTHER', - 'publicURL': 'http://files-other/v1'} - ]}, - {'type': 'rax:queues', - 'endpoints': [ - {'region': 'TEST', - 'publicURL': 'http://queues/v1'}, - {'region': 'OTHER', - 'publicURL': 'http://queues-other/v1'} - ]}, - ], - }} - - def test_response_error(self): - res = self.mox.CreateMockAnything() - res.status = 400 - res.reason = 'Bad Request' - exc = RackspaceError(res) - self.assertEqual("Received '400 Bad Request' from the API.", str(exc)) - self.assertEqual(res, exc.response) - - def test_create_token_func(self): - func = self.mox.CreateMockAnything() - func.__call__().AndReturn(('tokenid', 'files', 'queues')) - self.mox.ReplayAll() - auth = RackspaceCloudAuth({'function': func}) - self.assertEqual('tokenid', auth.token_id) - self.assertEqual('files', auth.files_endpoint) - self.assertEqual('queues', auth.queues_endpoint) - - def test_create_token_password(self): - auth = RackspaceCloudAuth({'username': 'testuser', 'password': 'testpass'}, 'http://test/v1', 'TEST') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(auth, 'get_connection') - auth.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('POST', '/v1/tokens') - conn.putheader('Host', 'test') - conn.putheader('Content-Type', 'application/json') - conn.putheader('Content-Length', '83') - conn.putheader('Accept', 'application/json') - conn.endheaders('{"auth": {"passwordCredentials": {"password": "testpass", "username": "testuser"}}}') - res.status = 200 - res.reason = 'OK' - conn.getresponse().AndReturn(res) - res.getheaders().AndReturn([]) - res.read().AndReturn(json.dumps(self.response_payload, sort_keys=True)) - self.mox.ReplayAll() - self.assertEqual('tokenid', auth.token_id) - self.assertEqual('http://files/v1', auth.files_endpoint) - self.assertEqual('http://queues/v1', auth.queues_endpoint) - - def test_create_token_api_key(self): - auth = RackspaceCloudAuth({'username': 'testuser', 'api_key': 'testkey'}, 'http://test/v1', 'TEST') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(auth, 'get_connection') - auth.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('POST', '/v1/tokens') - conn.putheader('Host', 'test') - conn.putheader('Content-Type', 'application/json') - conn.putheader('Content-Length', '88') - conn.putheader('Accept', 'application/json') - conn.endheaders('{"auth": {"RAX-KSKEY:apiKeyCredentials": {"apiKey": "testkey", "username": "testuser"}}}') - res.status = 200 - res.reason = 'OK' - conn.getresponse().AndReturn(res) - res.getheaders().AndReturn([]) - res.read().AndReturn(json.dumps(self.response_payload, sort_keys=True)) - self.mox.ReplayAll() - self.assertEqual('tokenid', auth.token_id) - self.assertEqual('http://files/v1', auth.files_endpoint) - self.assertEqual('http://queues/v1', auth.queues_endpoint) - - -# vim:et:fdm=marker:sts=4:sw=4:ts=4 diff --git a/test/test_slimta_cloudstorage_rackspace_files.py b/test/test_slimta_cloudstorage_rackspace_files.py deleted file mode 100644 index 5866e64d..00000000 --- a/test/test_slimta_cloudstorage_rackspace_files.py +++ /dev/null @@ -1,165 +0,0 @@ - -import re - -from mox3.mox import MoxTestBase, IsA, Func -from six.moves import cPickle - -from slimta.envelope import Envelope -from slimta.cloudstorage.rackspace import RackspaceCloudAuth, \ - RackspaceCloudFiles - - -def _is_files_path(path): - match = re.match('^/v1/test/[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}$', path) - return match - - -class TestRackspaceCloudFiles(MoxTestBase): - - def setUp(self): - super(TestRackspaceCloudFiles, self).setUp() - self.auth = self.mox.CreateMock(RackspaceCloudAuth) - self.auth.token_id = 'tokenid' - self.auth.files_endpoint = 'http://files/v1' - self.env = Envelope('sender@example.com', ['rcpt@example.com']) - self.pickled_env = cPickle.dumps(self.env, cPickle.HIGHEST_PROTOCOL) - - def test_write_message(self): - files = RackspaceCloudFiles(self.auth, container='test') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(files, 'get_connection') - files.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('PUT', Func(_is_files_path)) - conn.putheader('Host', 'files') - conn.putheader('Content-Type', 'application/octet-stream') - conn.putheader('Content-Length', str(len(self.pickled_env))) - conn.putheader('X-Object-Meta-Timestamp', '1234.0') - conn.putheader('X-Auth-Token', 'tokenid') - conn.endheaders(self.pickled_env) - conn.getresponse().AndReturn(res) - res.status = 201 - res.reason = 'Created' - res.getheaders().AndReturn([]) - self.mox.ReplayAll() - self.assertTrue(files.write_message(self.env, 1234.0)) - - def test_set_message_meta(self): - files = RackspaceCloudFiles(self.auth, container='test') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(files, 'get_connection') - files.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('POST', '/v1/test/4321') - conn.putheader('Host', 'files') - conn.putheader('X-Auth-Token', 'tokenid') - conn.putheader('X-Object-Meta-Timestamp', '1234.0') - conn.putheader('X-Object-Meta-Attempts', '3') - conn.endheaders() - conn.getresponse().AndReturn(res) - res.status = 202 - res.reason = 'Accepted' - res.getheaders().AndReturn([]) - self.mox.ReplayAll() - files.set_message_meta('4321', 1234.0, 3) - - def test_delete_message(self): - files = RackspaceCloudFiles(self.auth, container='test') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(files, 'get_connection') - files.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('DELETE', '/v1/test/4321') - conn.putheader('Host', 'files') - conn.putheader('X-Auth-Token', 'tokenid') - conn.endheaders() - conn.getresponse().AndReturn(res) - res.status = 204 - res.reason = 'No Content' - res.getheaders().AndReturn([]) - self.mox.ReplayAll() - files.delete_message('4321') - - def test_get_message(self): - files = RackspaceCloudFiles(self.auth, container='test') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(files, 'get_connection') - files.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('GET', '/v1/test/4321') - conn.putheader('Host', 'files') - conn.putheader('X-Auth-Token', 'tokenid') - conn.endheaders() - conn.getresponse().AndReturn(res) - res.status = 200 - res.reason = 'OK' - res.getheaders().AndReturn([]) - res.read().AndReturn(self.pickled_env) - res.getheader('X-Object-Meta-Timestamp').AndReturn('1234.0') - res.getheader('X-Object-Meta-Attempts', None).AndReturn('3') - res.getheader('X-Object-Meta-Delivered-Rcpts', None).AndReturn('[1, 2]') - self.mox.ReplayAll() - env, meta = files.get_message('4321') - self.assertTrue(isinstance(env, Envelope)) - self.assertEqual('sender@example.com', env.sender) - self.assertEqual(['rcpt@example.com'], env.recipients) - self.assertEqual(1234.0, meta['timestamp']) - self.assertEqual(3, meta['attempts']) - self.assertEqual([1, 2], meta['delivered_indexes']) - - def test_get_message_meta(self): - files = RackspaceCloudFiles(self.auth, container='test') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(files, 'get_connection') - files.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('HEAD', '/v1/test/4321') - conn.putheader('Host', 'files') - conn.putheader('X-Auth-Token', 'tokenid') - conn.endheaders() - conn.getresponse().AndReturn(res) - res.status = 200 - res.reason = 'OK' - res.getheaders().AndReturn([]) - res.getheader('X-Object-Meta-Timestamp').AndReturn('1234.0') - res.getheader('X-Object-Meta-Attempts', None).AndReturn('3') - res.getheader('X-Object-Meta-Delivered-Rcpts', None).AndReturn(None) - self.mox.ReplayAll() - meta = files.get_message_meta('4321') - self.assertEqual(1234.0, meta['timestamp']) - self.assertEqual(3, meta['attempts']) - self.assertFalse('delivered_indexes' in meta) - - def test_list_messages_page(self): - files = RackspaceCloudFiles(self.auth, container='test', prefix='test-') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(files, 'get_connection') - files.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('GET', '/v1/test?limit=1000&marker=marker') - conn.putheader('Host', 'files') - conn.putheader('X-Auth-Token', 'tokenid') - conn.endheaders() - conn.getresponse().AndReturn(res) - res.status = 200 - res.reason = 'OK' - res.getheaders().AndReturn([]) - res.read().AndReturn('test-one\ntest-two\ntest-three\nfour') - self.mox.ReplayAll() - lines, marker = files._list_messages_page('marker') - self.assertEqual(['test-one', 'test-two', 'test-three'], lines) - - def test_list_messages(self): - files = RackspaceCloudFiles(self.auth, container='test') - self.mox.StubOutWithMock(files, '_list_messages_page') - self.mox.StubOutWithMock(files, 'get_message_meta') - files._list_messages_page(None).AndReturn((['one', 'two'], 'two')) - files._list_messages_page('two').AndReturn(([], None)) - files.get_message_meta('one').AndReturn((1234.0, 0)) - files.get_message_meta('two').AndReturn((5678.0, 0)) - self.mox.ReplayAll() - results = list(files.list_messages()) - self.assertEqual([(1234.0, 'one'), (5678.0, 'two')], results) - - -# vim:et:fdm=marker:sts=4:sw=4:ts=4 diff --git a/test/test_slimta_cloudstorage_rackspace_queues.py b/test/test_slimta_cloudstorage_rackspace_queues.py deleted file mode 100644 index f55aafd3..00000000 --- a/test/test_slimta_cloudstorage_rackspace_queues.py +++ /dev/null @@ -1,94 +0,0 @@ - -import json - -import gevent -from mox3.mox import MoxTestBase, IsA, Func - -from slimta.cloudstorage.rackspace import RackspaceCloudAuth, \ - RackspaceCloudQueues - - -class TestRackspaceCloudQueues(MoxTestBase): - - def setUp(self): - super(TestRackspaceCloudQueues, self).setUp() - self.auth = self.mox.CreateMock(RackspaceCloudAuth) - self.auth.token_id = 'tokenid' - self.auth.queues_endpoint = 'http://queues/v1' - - def test_queue_message(self): - queues = RackspaceCloudQueues(self.auth, queue_name='test', client_id='test') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(queues, 'get_connection') - queues.get_connection(IsA(tuple), {}).AndReturn(conn) - json_payload = json.dumps([{'ttl': 86400, 'body': {'timestamp': 1234.0, 'storage_id': 'asdf'}}], sort_keys=True) - conn.putrequest('POST', '/v1/queues/test/messages') - conn.putheader('Host', 'queues') - conn.putheader('Client-ID', 'test') - conn.putheader('Content-Type', 'application/json') - conn.putheader('Content-Length', str(len(json_payload))) - conn.putheader('Accept', 'application/json') - conn.putheader('X-Auth-Token', 'tokenid') - conn.endheaders(json_payload) - conn.getresponse().AndReturn(res) - res.status = 201 - res.reason = 'Created' - res.getheaders().AndReturn([]) - self.mox.ReplayAll() - queues.queue_message('asdf', 1234.0) - - def test_poll(self): - queues = RackspaceCloudQueues(self.auth, queue_name='test', client_id='test') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(queues, 'get_connection') - queues.get_connection(IsA(tuple), {}).AndReturn(conn) - json_payload = '{"ttl": 3600, "grace": 3600}' - conn.putrequest('POST', '/v1/queues/test/claims') - conn.putheader('Host', 'queues') - conn.putheader('Client-ID', 'test') - conn.putheader('Content-Type', 'application/json') - conn.putheader('Content-Length', str(len(json_payload))) - conn.putheader('Accept', 'application/json') - conn.putheader('X-Auth-Token', 'tokenid') - conn.endheaders(json_payload) - conn.getresponse().AndReturn(res) - res.status = 201 - res.reason = 'Created' - res.getheaders().AndReturn([]) - res.read().AndReturn("""[{"body": {"timestamp": 1234.0, "storage_id": "storeid1"}, "href": "msgid1"}, - {"body": {"timestamp": 5678.0, "storage_id": "storeid2"}, "href": "msgid2"}]""") - self.mox.ReplayAll() - results = list(queues.poll()) - self.assertEqual([(1234.0, 'storeid1', 'msgid1'), (5678.0, 'storeid2', 'msgid2')], results) - - def test_sleep(self): - queues = RackspaceCloudQueues(self.auth, poll_pause=1337.0) - self.mox.StubOutWithMock(gevent, 'sleep') - gevent.sleep(1337.0) - self.mox.ReplayAll() - queues.sleep() - - def test_delete(self): - queues = RackspaceCloudQueues(self.auth, client_id='test') - conn = self.mox.CreateMockAnything() - res = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(queues, 'get_connection') - queues.get_connection(IsA(tuple), {}).AndReturn(conn) - conn.putrequest('DELETE', '/path/to/msg') - conn.putheader('Host', 'queues') - conn.putheader('Client-ID', 'test') - conn.putheader('Content-Type', 'application/json') - conn.putheader('Accept', 'application/json') - conn.putheader('X-Auth-Token', 'tokenid') - conn.endheaders() - conn.getresponse().AndReturn(res) - res.status = 204 - res.reason = 'No Content' - res.getheaders().AndReturn([]) - self.mox.ReplayAll() - queues.delete('/path/to/msg') - - -# vim:et:fdm=marker:sts=4:sw=4:ts=4 diff --git a/test/test_slimta_edge.py b/test/test_slimta_edge.py index 7921c420..94e4ce96 100644 --- a/test/test_slimta_edge.py +++ b/test/test_slimta_edge.py @@ -1,7 +1,7 @@ import time import unittest -from mox3.mox import MoxTestBase +from mox import MoxTestBase from slimta.edge import Edge, EdgeServer diff --git a/test/test_slimta_edge_smtp.py b/test/test_slimta_edge_smtp.py index e9da6885..6fc80c0d 100644 --- a/test/test_slimta_edge_smtp.py +++ b/test/test_slimta_edge_smtp.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IsA, IgnoreArg +from mox import MoxTestBase, IsA, IgnoreArg import gevent from gevent.socket import create_connection from gevent.ssl import SSLSocket diff --git a/test/test_slimta_edge_wsgi.py b/test/test_slimta_edge_wsgi.py index 2b218e76..77bc5872 100644 --- a/test/test_slimta_edge_wsgi.py +++ b/test/test_slimta_edge_wsgi.py @@ -1,7 +1,7 @@ from io import BytesIO -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.edge.wsgi import WsgiEdge, WsgiValidators from slimta.envelope import Envelope diff --git a/test/test_slimta_http.py b/test/test_slimta_http.py index ecc6d7a0..c20700f0 100644 --- a/test/test_slimta_http.py +++ b/test/test_slimta_http.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase +from mox import MoxTestBase from gevent import socket from slimta.http import HTTPConnection, HTTPSConnection, get_connection diff --git a/test/test_slimta_http_wsgi.py b/test/test_slimta_http_wsgi.py index a6c4acff..845e4483 100644 --- a/test/test_slimta_http_wsgi.py +++ b/test/test_slimta_http_wsgi.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA import gevent from gevent.pywsgi import WSGIServer as GeventWSGIServer diff --git a/test/test_slimta_lookup_dbapi2.py b/test/test_slimta_lookup_dbapi2.py index 36b796a8..5fcdd5ce 100644 --- a/test/test_slimta_lookup_dbapi2.py +++ b/test/test_slimta_lookup_dbapi2.py @@ -1,7 +1,7 @@ from contextlib import contextmanager -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.lookup.drivers.dbapi2 import DBAPI2Lookup diff --git a/test/test_slimta_lookup_dict.py b/test/test_slimta_lookup_dict.py index bd70bd68..dc550d33 100644 --- a/test/test_slimta_lookup_dict.py +++ b/test/test_slimta_lookup_dict.py @@ -1,5 +1,5 @@ -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.lookup.drivers.dict import DictLookup diff --git a/test/test_slimta_lookup_policy.py b/test/test_slimta_lookup_policy.py index 736496de..b487661d 100644 --- a/test/test_slimta_lookup_policy.py +++ b/test/test_slimta_lookup_policy.py @@ -1,5 +1,5 @@ -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.envelope import Envelope from slimta.lookup.policy import LookupPolicy diff --git a/test/test_slimta_lookup_redis.py b/test/test_slimta_lookup_redis.py index 1919fc23..b6552cb7 100644 --- a/test/test_slimta_lookup_redis.py +++ b/test/test_slimta_lookup_redis.py @@ -1,6 +1,6 @@ from redis import StrictRedis -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.lookup.drivers.redis import RedisLookup diff --git a/test/test_slimta_lookup_regex.py b/test/test_slimta_lookup_regex.py index c223469e..f1d24a63 100644 --- a/test/test_slimta_lookup_regex.py +++ b/test/test_slimta_lookup_regex.py @@ -1,5 +1,5 @@ -from mox3.mox import MoxTestBase +from mox import MoxTestBase from slimta.lookup.drivers.regex import RegexLookup diff --git a/test/test_slimta_policy_spamassassin.py b/test/test_slimta_policy_spamassassin.py index eb0b1a92..905b8b50 100644 --- a/test/test_slimta_policy_spamassassin.py +++ b/test/test_slimta_policy_spamassassin.py @@ -1,7 +1,7 @@ import unittest -from mox3.mox import MoxTestBase +from mox import MoxTestBase from gevent.socket import socket, SHUT_WR from slimta.policy.spamassassin import SpamAssassin, SpamAssassinError diff --git a/test/test_slimta_queue.py b/test/test_slimta_queue.py index edb3081f..545a5e75 100644 --- a/test/test_slimta_queue.py +++ b/test/test_slimta_queue.py @@ -2,7 +2,7 @@ from functools import wraps import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA import gevent from gevent.pool import Pool from gevent.event import AsyncResult diff --git a/test/test_slimta_queue_proxy.py b/test/test_slimta_queue_proxy.py index c35efbe1..a5dd5ea1 100644 --- a/test/test_slimta_queue_proxy.py +++ b/test/test_slimta_queue_proxy.py @@ -1,6 +1,6 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.queue.proxy import ProxyQueue from slimta.smtp.reply import Reply diff --git a/test/test_slimta_redisstorage.py b/test/test_slimta_redisstorage.py index 0c6e1643..41e85393 100644 --- a/test/test_slimta_redisstorage.py +++ b/test/test_slimta_redisstorage.py @@ -1,7 +1,7 @@ import re -from mox3.mox import MoxTestBase, IsA, Func +from mox import MoxTestBase, IsA, Func from six.moves import cPickle from redis import StrictRedis diff --git a/test/test_slimta_relay.py b/test/test_slimta_relay.py index 3a3e67a9..732ed6b7 100644 --- a/test/test_slimta_relay.py +++ b/test/test_slimta_relay.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.relay import Relay, PermanentRelayError, TransientRelayError from slimta.policy import RelayPolicy diff --git a/test/test_slimta_relay_http.py b/test/test_slimta_relay_http.py index 104ce3a0..b633f980 100644 --- a/test/test_slimta_relay_http.py +++ b/test/test_slimta_relay_http.py @@ -1,11 +1,11 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from urllib import parse as urlparse +from mox import MoxTestBase, IsA from gevent.event import AsyncResult from gevent import Timeout from slimta.envelope import Envelope from slimta.util.deque import BlockingDeque -from slimta.util.pycompat import urlparse from slimta.smtp.reply import Reply from slimta.relay import PermanentRelayError, TransientRelayError from slimta.relay.http import HttpRelay, HttpRelayClient diff --git a/test/test_slimta_relay_pipe.py b/test/test_slimta_relay_pipe.py index f06e431e..e9f1cba8 100644 --- a/test/test_slimta_relay_pipe.py +++ b/test/test_slimta_relay_pipe.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase from gevent import Timeout from gevent import subprocess @@ -11,7 +11,7 @@ class TestPipeRelay(MoxTestBase, unittest.TestCase): def _mock_popen(self, rcpt, returncode, stdout): - pmock = self.mox.CreateMock(subprocess.Popen) + pmock = self.mox.CreateMockAnything() subprocess.Popen(['relaytest', '-f', 'sender@example.com', rcpt], stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -58,7 +58,7 @@ def test_attempt(self): self.mox.StubOutWithMock(subprocess, 'Popen') env = Envelope('sender@example.com', ['rcpt@example.com']) env.parse(b'From: sender@example.com\r\n\r\ntest test\r\n') - pmock = self.mox.CreateMock(subprocess.Popen) + pmock = self.mox.CreateMockAnything() subprocess.Popen(['maildrop', '-f', 'sender@example.com'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, diff --git a/test/test_slimta_relay_smtp_client.py b/test/test_slimta_relay_smtp_client.py index a0b857b3..6142662c 100644 --- a/test/test_slimta_relay_smtp_client.py +++ b/test/test_slimta_relay_smtp_client.py @@ -1,13 +1,12 @@ from email.encoders import encode_base64 import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent import Timeout from gevent.socket import socket, error as socket_error from gevent.ssl import SSLContext from gevent.event import AsyncResult -from slimta.util import pycompat from slimta.util.deque import BlockingDeque from slimta.smtp import ConnectionLost, SmtpError from slimta.smtp.reply import Reply @@ -277,10 +276,7 @@ def test_deliver_conversion(self): self.sock.recv(IsA(int)).AndReturn(b'250-Hello\r\n250 PIPELINING\r\n') self.sock.sendall(b'MAIL FROM:\r\nRCPT TO:\r\nDATA\r\n') self.sock.recv(IsA(int)).AndReturn(b'250 Ok\r\n250 Ok\r\n354 Go ahead\r\n') - if pycompat.PY3: - self.sock.sendall(b'From: sender@example.com\r\nContent-Transfer-Encoding: base64\r\n\r\ndGVzdCB0ZXN0IIEK\r\n.\r\n') - else: - self.sock.sendall(b'From: sender@example.com\r\nContent-Transfer-Encoding: base64\r\n\r\ndGVzdCB0ZXN0IIENCg==\r\n.\r\n') + self.sock.sendall(b'From: sender@example.com\r\nContent-Transfer-Encoding: base64\r\n\r\ndGVzdCB0ZXN0IIEK\r\n.\r\n') self.sock.recv(IsA(int)).AndReturn(b'250 Ok\r\n') self.mox.ReplayAll() client = SmtpRelayClient(('addr', 0), self.queue, socket_creator=self._socket_creator, ehlo_as='there', binary_encoder=encode_base64) diff --git a/test/test_slimta_relay_smtp_lmtpclient.py b/test/test_slimta_relay_smtp_lmtpclient.py index a3052517..12f5491f 100644 --- a/test/test_slimta_relay_smtp_lmtpclient.py +++ b/test/test_slimta_relay_smtp_lmtpclient.py @@ -1,11 +1,10 @@ from email.encoders import encode_base64 import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent.socket import socket, error as socket_error from gevent.event import AsyncResult -from slimta.util import pycompat from slimta.util.deque import BlockingDeque from slimta.smtp import ConnectionLost from slimta.relay import TransientRelayError, PermanentRelayError @@ -141,10 +140,7 @@ def test_deliver_conversion(self): self.sock.recv(IsA(int)).AndReturn(b'250-Hello\r\n250 PIPELINING\r\n') self.sock.sendall(b'MAIL FROM:\r\nRCPT TO:\r\nDATA\r\n') self.sock.recv(IsA(int)).AndReturn(b'250 Ok\r\n250 Ok\r\n354 Go ahead\r\n') - if pycompat.PY3: - self.sock.sendall(b'From: sender@example.com\r\nContent-Transfer-Encoding: base64\r\n\r\ndGVzdCB0ZXN0IIEK\r\n.\r\n') - else: - self.sock.sendall(b'From: sender@example.com\r\nContent-Transfer-Encoding: base64\r\n\r\ndGVzdCB0ZXN0IIENCg==\r\n.\r\n') + self.sock.sendall(b'From: sender@example.com\r\nContent-Transfer-Encoding: base64\r\n\r\ndGVzdCB0ZXN0IIEK\r\n.\r\n') self.sock.recv(IsA(int)).AndReturn(b'250 Ok\r\n') result.set({'rcpt@example.com': Reply('250', 'Ok')}) self.mox.ReplayAll() diff --git a/test/test_slimta_relay_smtp_mx.py b/test/test_slimta_relay_smtp_mx.py index 2db1b479..eaf81735 100644 --- a/test/test_slimta_relay_smtp_mx.py +++ b/test/test_slimta_relay_smtp_mx.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase +from mox import MoxTestBase from pycares.errno import ARES_ENOTFOUND, ARES_ENODATA from slimta.relay import PermanentRelayError diff --git a/test/test_slimta_relay_smtp_static.py b/test/test_slimta_relay_smtp_static.py index a49f6d08..0c634438 100644 --- a/test/test_slimta_relay_smtp_static.py +++ b/test/test_slimta_relay_smtp_static.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from slimta.relay.smtp.static import StaticSmtpRelay from slimta.relay.smtp.client import SmtpRelayClient diff --git a/test/test_slimta_smtp_auth.py b/test/test_slimta_smtp_auth.py index b5fa0743..0b6dd265 100644 --- a/test/test_slimta_smtp_auth.py +++ b/test/test_slimta_smtp_auth.py @@ -1,13 +1,13 @@ import email.utils import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent.ssl import SSLSocket from pysasl import SASLAuth from slimta.smtp.io import IO -from slimta.smtp.auth import AuthSession, ServerAuthError, \ - InvalidMechanismError, AuthenticationCanceled +from slimta.smtp.auth import AuthSession, \ + InvalidMechanismError, AuthenticationCanceled class TestSmtpAuth(MoxTestBase, unittest.TestCase): @@ -21,11 +21,11 @@ def setUp(self): self.make_msgid = email.utils.make_msgid = lambda: '' def test_bytes(self): - auth = AuthSession(SASLAuth(), self.io) - self.assertEqual('PLAIN LOGIN CRAM-MD5', str(auth)) + auth = AuthSession(SASLAuth.defaults(), self.io) + self.assertEqual('PLAIN LOGIN', str(auth)) def test_invalid_mechanism(self): - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) with self.assertRaises(InvalidMechanismError): auth.server_attempt(b'TEST') with self.assertRaises(InvalidMechanismError): @@ -35,7 +35,7 @@ def test_plain_noarg(self): self.sock.sendall(b'334 \r\n') self.sock.recv(IsA(int)).AndReturn(b'dGVzdHppZAB0ZXN0dXNlcgB0ZXN0cGFzc3dvcmQ=\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) result = auth.server_attempt(b'PLAIN') self.assertEqual(u'testuser', result.authcid) self.assertEqual(u'testpassword', result.secret) @@ -43,7 +43,7 @@ def test_plain_noarg(self): def test_plain(self): self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) result = auth.server_attempt(b'PLAIN dGVzdHppZAB0ZXN0dXNlcgB0ZXN0cGFzc3dvcmQ=') self.assertEqual(u'testuser', result.authcid) self.assertEqual(u'testpassword', result.secret) @@ -53,7 +53,7 @@ def test_plain_canceled(self): self.sock.sendall(b'334 \r\n') self.sock.recv(IsA(int)).AndReturn(b'*\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) with self.assertRaises(AuthenticationCanceled): auth.server_attempt(b'PLAIN') with self.assertRaises(AuthenticationCanceled): @@ -65,7 +65,7 @@ def test_login_noarg(self): self.sock.sendall(b'334 UGFzc3dvcmQ6\r\n') self.sock.recv(IsA(int)).AndReturn(b'dGVzdHBhc3N3b3Jk\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) result = auth.server_attempt(b'LOGIN') self.assertEqual(u'testuser', result.authcid) self.assertEqual(u'testpassword', result.secret) @@ -75,36 +75,17 @@ def test_login(self): self.sock.sendall(b'334 UGFzc3dvcmQ6\r\n') self.sock.recv(IsA(int)).AndReturn(b'dGVzdHBhc3N3b3Jk\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) result = auth.server_attempt(b'LOGIN dGVzdHVzZXI=') self.assertEqual(u'testuser', result.authcid) self.assertEqual(u'testpassword', result.secret) self.assertEqual(None, result.authzid) - def test_crammd5(self): - self.sock.sendall(b'334 PHRlc3RAZXhhbXBsZS5jb20+\r\n') - self.sock.recv(IsA(int)).AndReturn(b'dGVzdHVzZXIgNDkzMzA1OGU2ZjgyOTRkZTE0NDJkMTYxOTI3ZGI5NDQ=\r\n') - self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) - result = auth.server_attempt(b'CRAM-MD5') - self.assertEqual(u'testuser', result.authcid) - self.assertTrue(result.check_secret(u'testpassword')) - self.assertFalse(result.check_secret(u'testwrong')) - self.assertEqual(None, result.authzid) - - def test_crammd5_malformed(self): - self.sock.sendall(b'334 PHRlc3RAZXhhbXBsZS5jb20+\r\n') - self.sock.recv(IsA(int)).AndReturn(b'bWFsZm9ybWVk\r\n') - self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) - with self.assertRaises(ServerAuthError): - auth.server_attempt(b'CRAM-MD5') - def test_client_bad_mech(self): self.sock.sendall(b'AUTH LOGIN\r\n') self.sock.recv(IsA(int)).AndReturn(b'535 Nope!\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) reply = auth.client_attempt(u'test@example.com', u'asdf', None, b'LOGIN') self.assertEqual('535', reply.code) @@ -114,7 +95,7 @@ def test_client_plain(self): self.sock.sendall(b'AUTH PLAIN amtsAHRlc3RAZXhhbXBsZS5jb20AYXNkZg==\r\n') self.sock.recv(IsA(int)).AndReturn(b'235 Ok\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) reply = auth.client_attempt(u'test@example.com', u'asdf', u'jkl', b'PLAIN') self.assertEqual('235', reply.code) @@ -128,7 +109,7 @@ def test_client_login(self): self.sock.sendall(b'YXNkZg==\r\n') self.sock.recv(IsA(int)).AndReturn(b'235 Ok\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) reply = auth.client_attempt(u'test@example.com', u'asdf', None, b'LOGIN') self.assertEqual('235', reply.code) @@ -140,7 +121,7 @@ def test_client_login_bad_username(self): self.sock.sendall(b'dGVzdEBleGFtcGxlLmNvbQ==\r\n') self.sock.recv(IsA(int)).AndReturn(b'535 Nope!\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.defaults(), self.io) reply = auth.client_attempt(u'test@example.com', u'asdf', None, b'LOGIN') self.assertEqual('535', reply.code) @@ -152,29 +133,29 @@ def test_client_crammd5(self): self.sock.sendall(b'dGVzdEBleGFtcGxlLmNvbSA1Yzk1OTBjZGE3ZTgxMDY5Mzk2ZjhiYjlkMzU1MzE1Yg==\r\n') self.sock.recv(IsA(int)).AndReturn(b'235 Ok\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth(), self.io) + auth = AuthSession(SASLAuth.named([b'CRAM-MD5']), self.io) reply = auth.client_attempt(u'test@example.com', u'asdf', None, b'CRAM-MD5') self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message) def test_client_xoauth2(self): - self.sock.sendall(b'AUTH XOAUTH2 dXNlcj10ZXN0QGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyYXNkZgEB\r\n') + self.sock.sendall(b'AUTH XOAUTH2 dXNlcj10ZXN0QGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyIGFzZGYBAQ==\r\n') self.sock.recv(IsA(int)).AndReturn(b'235 Ok\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth([b'XOAUTH2']), self.io) + auth = AuthSession(SASLAuth.named([b'XOAUTH2']), self.io) reply = auth.client_attempt(u'test@example.com', u'asdf', None, b'XOAUTH2') self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message) def test_client_xoauth2_error(self): - self.sock.sendall(b'AUTH XOAUTH2 dXNlcj10ZXN0QGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyYXNkZgEB\r\n') + self.sock.sendall(b'AUTH XOAUTH2 dXNlcj10ZXN0QGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyIGFzZGYBAQ==\r\n') self.sock.recv(IsA(int)).AndReturn(b'334 eyJzdGF0dXMiOiI0MDEiLCJzY2hlbWVzIjoiYmVhcmVyIG1hYyIsInNjb3BlIjoiaHR0cHM6Ly9tYWlsLmdvb2dsZS5jb20vIn0K\r\n') self.sock.sendall(b'\r\n') self.sock.recv(IsA(int)).AndReturn(b'535 Nope!\r\n') self.mox.ReplayAll() - auth = AuthSession(SASLAuth([b'XOAUTH2']), self.io) + auth = AuthSession(SASLAuth.named([b'XOAUTH2']), self.io) reply = auth.client_attempt(u'test@example.com', u'asdf', None, b'XOAUTH2') self.assertEqual('535', reply.code) diff --git a/test/test_slimta_smtp_client.py b/test/test_slimta_smtp_client.py index 2bab2a3d..ea3c839d 100644 --- a/test/test_slimta_smtp_client.py +++ b/test/test_slimta_smtp_client.py @@ -1,9 +1,8 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent.socket import socket from gevent.ssl import SSLContext, SSLSocket -from slimta.smtp.auth import InvalidMechanismError from slimta.smtp.client import Client, LmtpClient from slimta.smtp.reply import Reply @@ -108,13 +107,6 @@ def test_auth(self): self.assertEqual('2.0.0 Ok', reply.message) self.assertEqual(b'AUTH', reply.command) - def test_auth_insecure(self): - self.mox.ReplayAll() - client = Client(self.sock) - client.extensions.add('AUTH', 'PLAIN') - self.assertRaises(InvalidMechanismError, client.auth, - 'test@example.com', 'asdf') - def test_auth_force_mechanism(self): self.sock.sendall(b'AUTH PLAIN AHRlc3RAZXhhbXBsZS5jb20AYXNkZg==\r\n') self.sock.recv(IsA(int)).AndReturn(b'535 Nope!\r\n') diff --git a/test/test_slimta_smtp_datareader.py b/test/test_slimta_smtp_datareader.py index 989c114f..e0874874 100644 --- a/test/test_slimta_smtp_datareader.py +++ b/test/test_slimta_smtp_datareader.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent.socket import socket from slimta.smtp.datareader import DataReader diff --git a/test/test_slimta_smtp_io.py b/test/test_slimta_smtp_io.py index 42ac45ae..d8df4e05 100644 --- a/test/test_slimta_smtp_io.py +++ b/test/test_slimta_smtp_io.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent.socket import socket from slimta.smtp.io import IO diff --git a/test/test_slimta_smtp_reply.py b/test/test_slimta_smtp_reply.py index 1c671fa5..742954f5 100644 --- a/test/test_slimta_smtp_reply.py +++ b/test/test_slimta_smtp_reply.py @@ -2,7 +2,6 @@ from slimta.smtp.reply import Reply from slimta.smtp.io import IO -from slimta.util.pycompat import PY3 class TestSmtpReply(unittest.TestCase): @@ -31,12 +30,8 @@ def test_repr(self): self.assertEqual(expected, repr(r)) def test_str(self): - if PY3: - r = Reply('250', '2.1.0 Ok \U0001f44d') - self.assertEqual('250 2.1.0 Ok \U0001f44d', str(r)) - else: - r = Reply('250', u'2.1.0 Ok \U0001f44d') - self.assertEqual('250 2.1.0 Ok \xf0\x9f\x91\x8d', str(r)) + r = Reply('250', '2.1.0 Ok \U0001f44d') + self.assertEqual('250 2.1.0 Ok \U0001f44d', str(r)) def test_bytes(self): r = Reply('250', u'2.1.0 Ok \U0001f44d') diff --git a/test/test_slimta_smtp_server.py b/test/test_slimta_smtp_server.py index 6c8ac865..30d72185 100644 --- a/test/test_slimta_smtp_server.py +++ b/test/test_slimta_smtp_server.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent.ssl import SSLSocket, SSLContext, SSLError from pysasl import SASLAuth @@ -219,7 +219,7 @@ def test_auth(self): self.mox.ReplayAll() s = Server(self.sock, None) s.extensions.reset() - s.extensions.add('AUTH', AuthSession(SASLAuth([b'PLAIN']), s.io)) + s.extensions.add('AUTH', AuthSession(SASLAuth.named([b'PLAIN']), s.io)) s.handle() self.assertTrue(s.authed) diff --git a/test/test_slimta_util.py b/test/test_slimta_util.py index 775a65eb..15038f9f 100644 --- a/test/test_slimta_util.py +++ b/test/test_slimta_util.py @@ -1,5 +1,5 @@ import unittest -from mox3.mox import MoxTestBase, IgnoreArg +from mox import MoxTestBase, IgnoreArg from gevent import socket from slimta import util diff --git a/test/test_slimta_util_deque.py b/test/test_slimta_util_deque.py index c8073364..aa3a780b 100644 --- a/test/test_slimta_util_deque.py +++ b/test/test_slimta_util_deque.py @@ -1,7 +1,7 @@ import unittest -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent.lock import Semaphore from slimta.util.deque import BlockingDeque diff --git a/test/test_slimta_util_dns.py b/test/test_slimta_util_dns.py index 76ba2b6d..42411f4f 100644 --- a/test/test_slimta_util_dns.py +++ b/test/test_slimta_util_dns.py @@ -1,5 +1,5 @@ -from mox3.mox import MoxTestBase, IgnoreArg +from mox import MoxTestBase, IgnoreArg import pycares import pycares.errno diff --git a/test/test_slimta_util_dnsbl.py b/test/test_slimta_util_dnsbl.py index efc113b6..76db8024 100644 --- a/test/test_slimta_util_dnsbl.py +++ b/test/test_slimta_util_dnsbl.py @@ -1,6 +1,6 @@ import unittest -from mox3.mox import MoxTestBase +from mox import MoxTestBase from pycares.errno import ARES_ENOTFOUND from slimta.util.dns import DNSResolver, DNSError diff --git a/test/test_slimta_util_proxyproto.py b/test/test_slimta_util_proxyproto.py index 984a6c95..2434bcef 100644 --- a/test/test_slimta_util_proxyproto.py +++ b/test/test_slimta_util_proxyproto.py @@ -1,5 +1,5 @@ -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA from gevent import socket from slimta.util.proxyproto import ProxyProtocol, \ @@ -110,7 +110,7 @@ def _side_effect(view, length): def test_read_pp_line_eof(self): sock = self.mox.CreateMock(socket.socket) - sock.recv_into(IsA(memoryview), 2).AndReturn(0) + sock.recv_into(IsA(memoryview), IsA(int)).AndReturn(0) self.mox.ReplayAll() with self.assertRaises(AssertionError): self.pp._ProxyProtocolV1__read_pp_line(sock, b'') diff --git a/test/test_slimta_util_ptrlookup.py b/test/test_slimta_util_ptrlookup.py index e8c57621..88040002 100644 --- a/test/test_slimta_util_ptrlookup.py +++ b/test/test_slimta_util_ptrlookup.py @@ -2,7 +2,7 @@ import gevent from gevent import socket -from mox3.mox import MoxTestBase +from mox import MoxTestBase from slimta.util.ptrlookup import PtrLookup @@ -45,7 +45,7 @@ def test_run_greenletexit(self): self.mox.StubOutWithMock(socket, 'gethostbyaddr') socket.gethostbyaddr('127.0.0.1').AndRaise(gevent.GreenletExit) self.mox.ReplayAll() - ptr = PtrLookup('abcd') + ptr = PtrLookup('127.0.0.1') self.assertIsInstance(ptr, gevent.Greenlet) self.assertIsNone(ptr._run()) diff --git a/test/test_slimta_util_spf.py b/test/test_slimta_util_spf.py index 3c9a4308..695edb4a 100644 --- a/test/test_slimta_util_spf.py +++ b/test/test_slimta_util_spf.py @@ -2,7 +2,7 @@ import unittest import threading -from mox3.mox import MoxTestBase, IsA +from mox import MoxTestBase, IsA import gevent.monkey import spf