Skip to content

Commit

Permalink
Add AUTH= parameter to MAIL FROM client commands (slimta#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
icgood authored Jan 3, 2018
1 parent 6cb69b9 commit 4f37971
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 10 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
packages=find_packages(),
namespace_packages=['slimta'],
install_requires=['gevent >= 1.1rc',
'pysasl >= 0.2.0',
'pysasl >= 0.2.1',
'pycares >= 1'],
classifiers=['Development Status :: 3 - Alpha',
'Topic :: Communications :: Email :: Mail Transport Agents',
Expand Down
2 changes: 1 addition & 1 deletion slimta/relay/smtp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def _rset(self):
@current_command(b'MAIL')
def _mailfrom(self, sender):
with Timeout(self.command_timeout):
mailfrom = self.client.mailfrom(sender)
mailfrom = self.client.mailfrom(sender, auth=False)
if mailfrom and mailfrom.is_error():
raise SmtpRelayError.factory(mailfrom)
return mailfrom
Expand Down
26 changes: 23 additions & 3 deletions slimta/smtp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@

from __future__ import absolute_import

from gevent import ssl, Timeout
import re
import binascii

from gevent import Timeout
from gevent.socket import wait_read

from pysasl import SASLAuth
Expand All @@ -36,6 +39,8 @@

__all__ = ['Client', 'LmtpClient']

xtext_pattern = re.compile(br'[^\x21-\x2A\x2C-\x3C\x3E-\x7E]')


class Client(object):
"""Class whose methods perform various SMTP commands on a given socket. The
Expand Down Expand Up @@ -80,6 +85,14 @@ def _encode(self, thing):
else:
return thing.encode('ascii')

def _xtext_repl(self, match):
letter = match.group(0)
return b'+' + binascii.hexlify(letter).upper()

def _xtext(self, thing):
encoded = self._encode(thing)
return re.sub(xtext_pattern, self._xtext_repl, encoded)

def has_reply_waiting(self):
"""Checks if the underlying socket has data waiting to be received,
which means a reply is waiting to be read.
Expand Down Expand Up @@ -241,14 +254,18 @@ def auth(self, authcid, secret, authzid=None, mechanism=b'PLAIN'):
auth = AuthSession(SASLAuth(), self.io)
return auth.client_attempt(authcid, secret, authzid, mechanism)

def mailfrom(self, address, data_size=None):
def mailfrom(self, address, data_size=None, auth=None):
"""Sends the MAIL command with the ``address`` and possibly the message
size. The message size is sent if the server supports the SIZE
extension. If the server does *not* support PIPELINING, the returned
reply object is populated immediately.
:param address: The sender address to send.
:param data_size: Optional size of the message body.
:param auth: Optionally indicates the message was originally submitted
using the given authentication. Use ``False`` if
authentication was missing or unknown.
:type auth: str
:returns: |Reply| object that will be populated with the response
once a non-pipelined command is called, or if the server does
not support PIPELINING.
Expand All @@ -259,7 +276,10 @@ def mailfrom(self, address, data_size=None):

command = b''.join((b'MAIL FROM:<', self._encode(address), b'>'))
if data_size is not None and 'SIZE' in self.extensions:
command += b' SIZE='+str(data_size).encode()
command += b' SIZE='+self._encode(str(data_size))
if auth is not None and 'AUTH' in self.extensions:
authed = b'<>' if auth is False else self._xtext(auth)
command += b' AUTH=' + authed
self.io.send_command(command)

if 'PIPELINING' not in self.extensions:
Expand Down
2 changes: 1 addition & 1 deletion test/test_slimta_smtp_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def setUp(self):

def test_bytes(self):
auth = AuthSession(SASLAuth(), self.io)
self.assertEqual('CRAM-MD5 LOGIN PLAIN', str(auth))
self.assertEqual('PLAIN LOGIN CRAM-MD5', str(auth))

def test_invalid_mechanism(self):
auth = AuthSession(SASLAuth(), self.io)
Expand Down
16 changes: 12 additions & 4 deletions test/test_slimta_smtp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,18 @@ def test_mailfrom_size(self):
self.mox.ReplayAll()
client = Client(self.sock)
client.extensions.add('SIZE', 100)
reply = client.mailfrom('test', 10)
self.assertEqual('250', reply.code)
self.assertEqual('2.0.0 Ok', reply.message)
self.assertEqual(b'MAIL', reply.command)
client.mailfrom('test', data_size=10)

def test_mailfrom_auth(self):
self.sock.sendall(b'MAIL FROM:<test> AUTH=<>\r\n')
self.sock.recv(IsA(int)).AndReturn(b'250 2.0.0 Ok\r\n')
self.sock.sendall(b'MAIL FROM:<test> AUTH=1+2B1+3D2\r\n')
self.sock.recv(IsA(int)).AndReturn(b'250 2.0.0 Ok\r\n')
self.mox.ReplayAll()
client = Client(self.sock)
client.extensions.add('AUTH', True)
client.mailfrom('test', auth=False)
client.mailfrom('test', auth='1+1=2')

def test_rcptto(self):
self.sock.sendall(b'RCPT TO:<test>\r\n')
Expand Down

0 comments on commit 4f37971

Please sign in to comment.