From: Kero van Gelder Date: 2009-10-19T02:17:07+09:00 Subject: [ruby-core:26150] Patch: Let Net::SMTP continu when at least one RCPT is accepted --4SFOXa2GPu3tIq4H Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi all, While writing an SMTP daemon in Ruby, one of my tests (that uses Ruby's existing Net::SMTP library) failed. I was testing that my daemon will deliver mail when only some RCPTs specified by the client were accepted. Ruby's Net::SMTP quits when the first Unknown User is encountered. I think Net::SMTP should continue with other RCPTs, then use DATA to let the mail be delivered to existing users. I think this should be so, because - RFC821 mentions clearly in the second paragraph in section 2 "if not, it responds with a reply rejecting that recipient (but not the whole mail transaction)", and - RFC2821 leaves this in the middle, but still gives an example in section D.1 A Typical SMTP Transaction Scenario "C: RCPT TO: S: 250 OK C: RCPT TO: S: 550 No such user here C: RCPT TO: S: 250 OK" So, I wrote code to accomplish this. Please find a patch attached. Comments welcome, especially on the Errors constructed and returned. They are in plain text; I can imagine making the unknown and ok users explicit (arrays within the exception), so that code that uses Net::SMTP has an easier time determining which emails were not sent. Bye, Kero. PS: patch applies to both ruby_1_8 branch and trunk ___ How can I change the world if I can't even change myself? -- Faithless, Salva Mea --4SFOXa2GPu3tIq4H Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="smtp_rcptto.patch" Index: lib/net/smtp.rb =================================================================== --- lib/net/smtp.rb (revision 25392) +++ lib/net/smtp.rb (working copy) @@ -50,7 +50,12 @@ include SMTPError end - # Represents a fatal SMTP error (error code 5xx, except for 500) + # Represents SMTP error code 550, user unknown + class SMTPUnknownUser < ProtoFatalError + include SMTPError + end + + # Represents a fatal SMTP error (error code 5xx, except for 500 and 550) class SMTPFatalError < ProtoFatalError include SMTPError end @@ -452,6 +457,7 @@ # * Net::SMTPAuthenticationError # * Net::SMTPServerBusy # * Net::SMTPSyntaxError + # * Net::SMTPUnknownUser # * Net::SMTPFatalError # * Net::SMTPUnknownError # * IOError @@ -513,6 +519,7 @@ # * Net::SMTPAuthenticationError # * Net::SMTPServerBusy # * Net::SMTPSyntaxError + # * Net::SMTPUnknownUser # * Net::SMTPFatalError # * Net::SMTPUnknownError # * IOError @@ -643,6 +650,7 @@ # # * Net::SMTPServerBusy # * Net::SMTPSyntaxError + # * Net::SMTPUnknownUser # * Net::SMTPFatalError # * Net::SMTPUnknownError # * IOError @@ -651,8 +659,10 @@ def send_message(msgstr, from_addr, *to_addrs) raise IOError, 'closed session' unless @socket mailfrom from_addr - rcptto_list to_addrs - data msgstr + ok_users, unknown_users = rcptto_list to_addrs.flatten + res = data msgstr + raise SMTPUnknownUser, "Unknown users: #{unknown_users.join', '}, mail delivered to #{ok_users.join', '}" if not unknown_users.empty? + res end alias send_mail send_message @@ -697,6 +707,7 @@ # # * Net::SMTPServerBusy # * Net::SMTPSyntaxError + # * Net::SMTPUnknownUser # * Net::SMTPFatalError # * Net::SMTPUnknownError # * IOError @@ -705,8 +716,10 @@ def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream raise IOError, 'closed session' unless @socket mailfrom from_addr - rcptto_list to_addrs - data(&block) + ok_users, unknown_users = rcptto_list to_addrs.flatten + res = data(&block) + raise SMTPUnknownUser, "Failed to deliver for #{unknown_users.join', '}, succesful for #{ok_users.join', '}" if not unknown_users.empty? + res end alias ready open_message_stream # obsolete @@ -830,9 +843,19 @@ def rcptto_list(to_addrs) raise ArgumentError, 'mail destination not given' if to_addrs.empty? - to_addrs.flatten.each do |addr| - rcptto addr + unknown_users = [] + to_addrs.each do |addr| + begin + rcptto addr + rescue SMTPUnknownUser + unknown_users << addr + end end + if unknown_users.length == to_addrs.flatten.length + raise SMTPUnknownUser, "Unknown users: #{unknown_users.join', '}" + else + [to_addrs - unknown_users, unknown_users] + end end def rcptto(to_addr) @@ -997,6 +1020,7 @@ when /\A4/ then SMTPServerBusy when /\A50/ then SMTPSyntaxError when /\A53/ then SMTPAuthenticationError + when /\A550/ then SMTPUnknownUser when /\A5/ then SMTPFatalError else SMTPUnknownError end --4SFOXa2GPu3tIq4H--