1
0
مراية لـ https://github.com/postalserver/postal.git تم المزامنة 2025-12-01 05:43:04 +00:00

feat: only accept RFC-compliant End-of-DATA sequence

Previously Postal was fairly forgiving about line endings. This requires that the end of data sequence is `<CR><LF>.<CR><LF>`.
هذا الالتزام موجود في:
Adam Cooke
2024-03-01 17:12:10 +00:00
الأصل a9ade3c8bc
التزام 0140dc4483
3 ملفات معدلة مع 51 إضافات و17 حذوفات

عرض الملف

@@ -22,6 +22,10 @@ module SMTPServer
def initialize(ip_address) def initialize(ip_address)
@logging_enabled = true @logging_enabled = true
@ip_address = ip_address @ip_address = ip_address
@cr_present = false
@previous_cr_present = nil
if @ip_address if @ip_address
check_ip_address check_ip_address
@state = :welcome @state = :welcome
@@ -51,6 +55,14 @@ module SMTPServer
end end
def handle(data) def handle(data)
if data[-1] == "\r"
@cr_present = true
data = data.chop # remove last character (\r)
else
Postal.logger.debug("\e[33m WARN: Detected line with invalid line ending (missing <CR>)\e[0m", id: id)
@cr_present = false
end
Postal.logger.tagged(id: id) do Postal.logger.tagged(id: id) do
if @state == :preauth if @state == :preauth
return proxy(data) return proxy(data)
@@ -59,11 +71,12 @@ module SMTPServer
log "\e[32m<= #{sanitize_input_for_log(data.strip)}\e[0m" log "\e[32m<= #{sanitize_input_for_log(data.strip)}\e[0m"
if @proc if @proc
@proc.call(data) @proc.call(data)
else else
handle_command(data) handle_command(data)
end end
end end
ensure
@previous_cr_present = @cr_present
end end
def finished? def finished?
@@ -409,7 +422,7 @@ module SMTPServer
@headers["received"] = [received_header] @headers["received"] = [received_header]
handler = proc do |idata| handler = proc do |idata|
if idata == "." if idata == "." && @cr_present && @previous_cr_present
@logging_enabled = true @logging_enabled = true
@proc = nil @proc = nil
finished finished
@@ -424,7 +437,7 @@ module SMTPServer
end end
if @receiving_headers if @receiving_headers
if idata.blank? if idata&.length&.zero?
@receiving_headers = false @receiving_headers = false
elsif idata.to_s =~ /^\s/ elsif idata.to_s =~ /^\s/
# This is a continuation of a header # This is a continuation of a header

عرض الملف

@@ -194,16 +194,11 @@ module SMTPServer
eof = true eof = true
end end
# Normalize all \r\n and \n to \r\n, but ignore only \r.
# A \r\n may be split in 2 buffers (\n in one buffer and \r in the other)
buffers[io] = buffers[io].gsub(/\r/, "").encode(buffers[io].encoding, crlf_newline: true)
# We line buffer, so look to see if we have received a newline # We line buffer, so look to see if we have received a newline
# and keep doing so until all buffered lines have been processed. # and keep doing so until all buffered lines have been processed.
while buffers[io].index("\r\n") while buffers[io].index("\n")
# Extract the line # Extract the line
line, buffers[io] = buffers[io].split("\r\n", 2) line, buffers[io] = buffers[io].split("\n", 2)
# Send the received line to the client object for processing # Send the received line to the client object for processing
result = client.handle(line) result = client.handle(line)
# If the client object returned some data, write it back to the client # If the client object returned some data, write it back to the client

عرض الملف

@@ -22,12 +22,32 @@ module SMTPServer
end end
describe "when finished sending data" do describe "when finished sending data" do
context "when the . character does not end with a <CR>" do
it "does nothing" do
allow(Postal::Config.smtp_server).to receive(:max_message_size).and_return(1)
client.handle("DATA")
client.handle("Subject: Hello")
client.handle("\r")
expect(client.handle(".")).to be nil
end
end
context "when the data before the . character does not end with a <CR>" do
it "does nothing" do
allow(Postal::Config.smtp_server).to receive(:max_message_size).and_return(1)
client.handle("DATA")
client.handle("Subject: Hello")
expect(client.handle(".\r")).to be nil
end
end
context "when the data is larger than the maximum message size" do context "when the data is larger than the maximum message size" do
it "returns an error and resets the state" do it "returns an error and resets the state" do
allow(Postal::Config.smtp_server).to receive(:max_message_size).and_return(1) allow(Postal::Config.smtp_server).to receive(:max_message_size).and_return(1)
client.handle("DATA") client.handle("DATA")
client.handle("a" * 1024 * 1024 * 10) client.handle("a" * 1024 * 1024 * 10)
expect(client.handle(".")).to eq "552 Message too large (maximum size 1MB)" client.handle("\r")
expect(client.handle(".\r")).to eq "552 Message too large (maximum size 1MB)"
end end
end end
@@ -43,7 +63,8 @@ module SMTPServer
client.handle("To: #{rcpt_to}") client.handle("To: #{rcpt_to}")
client.handle("") client.handle("")
client.handle("This is a test message") client.handle("This is a test message")
expect(client.handle(".")).to eq "550 Loop detected" client.handle("\r")
expect(client.handle(".\r")).to eq "550 Loop detected"
end end
end end
@@ -55,7 +76,8 @@ module SMTPServer
client.handle("To: #{rcpt_to}") client.handle("To: #{rcpt_to}")
client.handle("") client.handle("")
client.handle("This is a test message") client.handle("This is a test message")
expect(client.handle(".")).to eq "530 From/Sender name is not valid" client.handle("\r")
expect(client.handle(".\r")).to eq "530 From/Sender name is not valid"
end end
end end
@@ -71,7 +93,8 @@ module SMTPServer
client.handle("To: #{rcpt_to}") client.handle("To: #{rcpt_to}")
client.handle("") client.handle("")
client.handle("This is a test message") client.handle("This is a test message")
expect(client.handle(".")).to eq "250 OK" client.handle("\r")
expect(client.handle(".\r")).to eq "250 OK"
queued_message = QueuedMessage.first queued_message = QueuedMessage.first
expect(queued_message).to have_attributes( expect(queued_message).to have_attributes(
domain: "example.com", domain: "example.com",
@@ -110,7 +133,8 @@ module SMTPServer
client.handle("To: #{rcpt_to}") client.handle("To: #{rcpt_to}")
client.handle("") client.handle("")
client.handle("This is a test message") client.handle("This is a test message")
expect(client.handle(".")).to eq "250 OK" client.handle("\r")
expect(client.handle(".\r")).to eq "250 OK"
queued_message = QueuedMessage.first queued_message = QueuedMessage.first
expect(queued_message).to have_attributes( expect(queued_message).to have_attributes(
@@ -141,7 +165,8 @@ module SMTPServer
client.handle("To: #{rcpt_to}") client.handle("To: #{rcpt_to}")
client.handle("") client.handle("")
client.handle("This is a test message") client.handle("This is a test message")
expect(client.handle(".")).to eq "250 OK" client.handle("\r")
expect(client.handle(".\r")).to eq "250 OK"
queued_message = QueuedMessage.first queued_message = QueuedMessage.first
expect(queued_message).to have_attributes( expect(queued_message).to have_attributes(
@@ -179,7 +204,8 @@ module SMTPServer
client.handle("To: #{rcpt_to}") client.handle("To: #{rcpt_to}")
client.handle("") client.handle("")
client.handle("This is a test message") client.handle("This is a test message")
expect(client.handle(".")).to eq "250 OK" client.handle("\r")
expect(client.handle(".\r")).to eq "250 OK"
queued_message = QueuedMessage.first queued_message = QueuedMessage.first
expect(queued_message).to have_attributes( expect(queued_message).to have_attributes(