مراية لـ
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>`.
هذا الالتزام موجود في:
@@ -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(
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم