diff --git a/app/lib/smtp_server/client.rb b/app/lib/smtp_server/client.rb index 7266aba4..53cf6789 100644 --- a/app/lib/smtp_server/client.rb +++ b/app/lib/smtp_server/client.rb @@ -22,6 +22,10 @@ class Client def initialize(ip_address) @logging_enabled = true @ip_address = ip_address + + @cr_present = false + @previous_cr_present = nil + if @ip_address check_ip_address @state = :welcome @@ -51,6 +55,14 @@ def id end 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 )\e[0m", id: id) + @cr_present = false + end + Postal.logger.tagged(id: id) do if @state == :preauth return proxy(data) @@ -59,11 +71,12 @@ def handle(data) log "\e[32m<= #{sanitize_input_for_log(data.strip)}\e[0m" if @proc @proc.call(data) - else handle_command(data) end end + ensure + @previous_cr_present = @cr_present end def finished? @@ -409,7 +422,7 @@ def data(_data) @headers["received"] = [received_header] handler = proc do |idata| - if idata == "." + if idata == "." && @cr_present && @previous_cr_present @logging_enabled = true @proc = nil finished @@ -424,7 +437,7 @@ def data(_data) end if @receiving_headers - if idata.blank? + if idata&.length&.zero? @receiving_headers = false elsif idata.to_s =~ /^\s/ # This is a continuation of a header diff --git a/app/lib/smtp_server/server.rb b/app/lib/smtp_server/server.rb index a08f2a82..dadb48d2 100644 --- a/app/lib/smtp_server/server.rb +++ b/app/lib/smtp_server/server.rb @@ -194,16 +194,11 @@ def run_event_loop eof = true 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 # 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 - 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 result = client.handle(line) # If the client object returned some data, write it back to the client diff --git a/spec/lib/smtp_server/client/finished_spec.rb b/spec/lib/smtp_server/client/finished_spec.rb index e84f4cb5..0d8be3ac 100644 --- a/spec/lib/smtp_server/client/finished_spec.rb +++ b/spec/lib/smtp_server/client/finished_spec.rb @@ -22,12 +22,32 @@ module SMTPServer end describe "when finished sending data" do + context "when the . character does not end with a " 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 " 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 it "returns an error and resets the state" do allow(Postal::Config.smtp_server).to receive(:max_message_size).and_return(1) client.handle("DATA") 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 @@ -43,7 +63,8 @@ module SMTPServer client.handle("To: #{rcpt_to}") client.handle("") 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 @@ -55,7 +76,8 @@ module SMTPServer client.handle("To: #{rcpt_to}") client.handle("") 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 @@ -71,7 +93,8 @@ module SMTPServer client.handle("To: #{rcpt_to}") client.handle("") 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 expect(queued_message).to have_attributes( domain: "example.com", @@ -110,7 +133,8 @@ module SMTPServer client.handle("To: #{rcpt_to}") client.handle("") 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 expect(queued_message).to have_attributes( @@ -141,7 +165,8 @@ module SMTPServer client.handle("To: #{rcpt_to}") client.handle("") 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 expect(queued_message).to have_attributes( @@ -179,7 +204,8 @@ module SMTPServer client.handle("To: #{rcpt_to}") client.handle("") 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 expect(queued_message).to have_attributes(