#1745035: add limits for command and data size to smtpd; patch by Savio Sena.
This commit is contained in:
parent
106a54d764
commit
1e5c5f8f7d
24
Lib/smtpd.py
24
Lib/smtpd.py
|
@ -109,6 +109,9 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
COMMAND = 0
|
COMMAND = 0
|
||||||
DATA = 1
|
DATA = 1
|
||||||
|
|
||||||
|
data_size_limit = 33554432
|
||||||
|
command_size_limit = 512
|
||||||
|
|
||||||
def __init__(self, server, conn, addr):
|
def __init__(self, server, conn, addr):
|
||||||
asynchat.async_chat.__init__(self, conn)
|
asynchat.async_chat.__init__(self, conn)
|
||||||
self.smtp_server = server
|
self.smtp_server = server
|
||||||
|
@ -121,6 +124,7 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
self.rcpttos = []
|
self.rcpttos = []
|
||||||
self.received_data = ''
|
self.received_data = ''
|
||||||
self.fqdn = socket.getfqdn()
|
self.fqdn = socket.getfqdn()
|
||||||
|
self.num_bytes = 0
|
||||||
try:
|
try:
|
||||||
self.peer = conn.getpeername()
|
self.peer = conn.getpeername()
|
||||||
except socket.error as err:
|
except socket.error as err:
|
||||||
|
@ -262,6 +266,15 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
|
|
||||||
# Implementation of base class abstract method
|
# Implementation of base class abstract method
|
||||||
def collect_incoming_data(self, data):
|
def collect_incoming_data(self, data):
|
||||||
|
limit = None
|
||||||
|
if self.smtp_state == self.COMMAND:
|
||||||
|
limit = self.command_size_limit
|
||||||
|
elif self.smtp_state == self.DATA:
|
||||||
|
limit = self.data_size_limit
|
||||||
|
if limit and self.num_bytes > limit:
|
||||||
|
return
|
||||||
|
elif limit:
|
||||||
|
self.num_bytes += len(data)
|
||||||
self.received_lines.append(str(data, "utf8"))
|
self.received_lines.append(str(data, "utf8"))
|
||||||
|
|
||||||
# Implementation of base class abstract method
|
# Implementation of base class abstract method
|
||||||
|
@ -270,6 +283,11 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
print('Data:', repr(line), file=DEBUGSTREAM)
|
print('Data:', repr(line), file=DEBUGSTREAM)
|
||||||
self.received_lines = []
|
self.received_lines = []
|
||||||
if self.smtp_state == self.COMMAND:
|
if self.smtp_state == self.COMMAND:
|
||||||
|
if self.num_bytes > self.command_size_limit:
|
||||||
|
self.push('500 Error: line too long')
|
||||||
|
self.num_bytes = 0
|
||||||
|
return
|
||||||
|
self.num_bytes = 0
|
||||||
if not line:
|
if not line:
|
||||||
self.push('500 Error: bad syntax')
|
self.push('500 Error: bad syntax')
|
||||||
return
|
return
|
||||||
|
@ -290,6 +308,11 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
else:
|
else:
|
||||||
if self.smtp_state != self.DATA:
|
if self.smtp_state != self.DATA:
|
||||||
self.push('451 Internal confusion')
|
self.push('451 Internal confusion')
|
||||||
|
self.num_bytes = 0
|
||||||
|
return
|
||||||
|
if self.num_bytes > self.data_size_limit:
|
||||||
|
self.push('552 Error: Too much mail data')
|
||||||
|
self.num_bytes = 0
|
||||||
return
|
return
|
||||||
# Remove extraneous carriage returns and de-transparency according
|
# Remove extraneous carriage returns and de-transparency according
|
||||||
# to RFC 821, Section 4.5.2.
|
# to RFC 821, Section 4.5.2.
|
||||||
|
@ -307,6 +330,7 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
self.rcpttos = []
|
self.rcpttos = []
|
||||||
self.mailfrom = None
|
self.mailfrom = None
|
||||||
self.smtp_state = self.COMMAND
|
self.smtp_state = self.COMMAND
|
||||||
|
self.num_bytes = 0
|
||||||
self.set_terminator(b'\r\n')
|
self.set_terminator(b'\r\n')
|
||||||
if not status:
|
if not status:
|
||||||
self.push('250 Ok')
|
self.push('250 Ok')
|
||||||
|
|
|
@ -121,6 +121,24 @@ class SMTPDChannelTest(TestCase):
|
||||||
self.assertEqual(self.channel.socket.last,
|
self.assertEqual(self.channel.socket.last,
|
||||||
b'451 Internal confusion\r\n')
|
b'451 Internal confusion\r\n')
|
||||||
|
|
||||||
|
def test_command_too_long(self):
|
||||||
|
self.write_line(b'MAIL from ' +
|
||||||
|
b'a' * self.channel.command_size_limit +
|
||||||
|
b'@example')
|
||||||
|
self.assertEqual(self.channel.socket.last,
|
||||||
|
b'500 Error: line too long\r\n')
|
||||||
|
|
||||||
|
def test_data_too_long(self):
|
||||||
|
# Small hack. Setting limit to 2K octets here will save us some time.
|
||||||
|
self.channel.data_size_limit = 2048
|
||||||
|
self.write_line(b'MAIL From:eggs@example')
|
||||||
|
self.write_line(b'RCPT To:spam@example')
|
||||||
|
self.write_line(b'DATA')
|
||||||
|
self.write_line(b'A' * self.channel.data_size_limit +
|
||||||
|
b'A\r\n.')
|
||||||
|
self.assertEqual(self.channel.socket.last,
|
||||||
|
b'552 Error: Too much mail data\r\n')
|
||||||
|
|
||||||
def test_need_MAIL(self):
|
def test_need_MAIL(self):
|
||||||
self.write_line(b'RCPT to:spam@example')
|
self.write_line(b'RCPT to:spam@example')
|
||||||
self.assertEqual(self.channel.socket.last,
|
self.assertEqual(self.channel.socket.last,
|
||||||
|
|
|
@ -33,6 +33,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #1745035: Add a command size and data size limit to smtpd.py, to
|
||||||
|
prevent DoS attacks. Patch by Savio Sena.
|
||||||
|
|
||||||
- Issue #4925: Add filename to error message when executable can't be found in
|
- Issue #4925: Add filename to error message when executable can't be found in
|
||||||
subprocess.
|
subprocess.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue