Hi! I started implementing ESMTP AUTH support in md_check_against_smtp_server(). It uses Authen::SASL.
The changes are basically: in get_smtp_return_code(): - add support for multiline responses in md_check_against_smtp_server(): - try EHLO first, fall back to HELO if EHLO fails - if parameters for AUTH are supplied and ESMTP supports AUTH and AUTH method try AUTH command Please let me know what you think of it. Feel free to include it in mimedefang if you like. BTW. I thought about using Net::SMTP instead of coding the flow of SMTP on my own. Was there any reason not to use Net::SMTP in md_check_against_smtp_server()? Best regards Franz
--- mimedefang.pl 2013/10/17 12:18:44 1.1 +++ mimedefang.pl 2013/10/22 12:59:27 @@ -73,6 +73,7 @@ use MIME::Parser; use Sys::Hostname; use File::Spec qw (); +use Authen::SASL qw(Perl); # Detect these Perl modules at run-time. Can explicitly prevent # loading of these modules by setting $Features{"xxx"} = 0; @@ -7312,19 +7313,28 @@ my($sock, $recip, $server) = @_; my($line, $code, $text, $retval, $dsn); while (defined ($line = $sock->getline())) { + my $temp_text; + # Chew up all white space, including CR $line =~ s/\s+$//; - if (($line =~ /^\d\d\d$/) or ($line =~ /^\d\d\d\s/)) { + + if ($line =~ /^(\d\d\d)-(.*)$/) { + # multiline response + $code = $1; + $text .= $2 . "\r\n"; + } elsif (($line =~ /^\d\d\d$/) or ($line =~ /^\d\d\d\s/)) { + # single line response or end of multiline response $line =~ /^(\d\d\d)\s*(.*)$/; $code = $1; - $text = $2; + $temp_text = $2; # Check for DSN - if ($text =~ /^(\d\.\d{1,3}\.\d{1,3})\s+(.*)$/) { + if ($temp_text =~ /^(\d\.\d{1,3}\.\d{1,3})\s+(.*)$/) { $dsn = $1; - $text = $2; + $temp_text = $2; } else { $dsn = ""; } + $text .= $temp_text; if ($code =~ /^[123]/) { $retval = 'CONTINUE'; } elsif ($code =~ /^4/) { @@ -7361,17 +7371,21 @@ # helo -- string to put in "HELO" command # server -- SMTP server to try. # port -- optional: Port to connect on (defaults to 25) +# mechanism -- optional: authentication mechanism +# user -- optional: username +# password -- optional: password # %RETURNS: # ('CONTINUE', "OK") if recipient is OK # ('TEMPFAIL', "err") if temporary failure # ('REJECT', "err") if recipient is not OK. # %DESCRIPTION: # Verifies a recipient against another SMTP server by issuing a -# HELO / MAIL FROM: / RCPT TO: / QUIT sequence +# EHLO / MAIL FROM: / RCPT TO: / QUIT sequence #*********************************************************************** -sub md_check_against_smtp_server ($$$$;$) { - my($sender, $recip, $helo, $server, $port) = @_; +sub md_check_against_smtp_server ($$$$;$;$$$) { + my($sender, $recip, $helo, $server, $port, $mechanism, $user, $password) = @_; my($code, $text, $dsn, $retval); + my %esmtp_supports; $port = 'smtp(25)' unless defined($port); @@ -7419,17 +7433,99 @@ } } - $sock->print("HELO $helo\r\n"); + %esmtp_supports = (); + + # try ESMTP first + $sock->print("EHLO $helo\r\n"); $sock->flush(); ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); if ($retval ne 'CONTINUE') { - $sock->print("QUIT\r\n"); + # try SMTP if ESMTP isn't supported + $sock->print("HELO $helo\r\n"); $sock->flush(); - # Swallow return value - get_smtp_return_code($sock, $recip, $server); - $sock->close(); - return ($retval, $text, $code, $dsn); + + ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); + if ($retval ne 'CONTINUE') { + $sock->print("QUIT\r\n"); + $sock->flush(); + # Swallow return value + get_smtp_return_code($sock, $recip, $server); + $sock->close(); + return ($retval, $text, $code, $dsn); + } + + # ESMTP not supported, SMTP supported + $esmtp_supports{'EHLO'} = 0; + } else { + # ESMTP supported + $esmtp_supports{'EHLO'} = 1; + + # explore ESMTP extensions + my @temp_esmtp_support = split(/\r\n/, $text); + shift @temp_esmtp_support; + + foreach my $esmtp_support (@temp_esmtp_support) { + if ($esmtp_support =~ /(\w+)\b[= \t]*([^\n]*)/) { + $esmtp_supports{uc $1} = $2 + }; + } + } + + if ($mechanism ne '' and $user ne '' and $password ne '') { + unless (defined($esmtp_supports{'AUTH'})) { + # AUTH command not supported by server + return ('TEMPFAIL', "451", "4.3.0", 'AUTH command not supported by server'); + } + + unless ($esmtp_supports{'AUTH'} =~ m/\b${mechanism}\b/) { + # requested AUTH mechanism not supported by server + return ('TEMPFAIL', "451", "4.3.0", 'requested AUTH mechanism not supported by server'); + } + + my $module_name = 'Authen::SASL::Perl::' . $mechanism; + eval "require $module_name"; + if ($@) { + # requested AUTH mechanism not supported by Authen::SASL + return ('TEMPFAIL', "451", "4.3.0", 'requested AUTH mechanism not supported by Authen::SASL'); + } + + my $sasl = Authen::SASL->new( + mechanism => $mechanism, + callback => { + user => $user, + pass => $password, + authname => $user, + }, + ); + + my $client = $sasl->client_new('smtp', $server, 0); + + my $initial_str = $client->client_start; + + $sock->print('AUTH ' . $client->mechanism); + $sock->print(' ' . MIME::Base64::encode_base64($initial_str, '')) if defined $initial_str and length $initial_str; + $sock->print("\r\n"); + $sock->flush(); + + ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); + + while ($retval eq 'CONTINUE' and $code eq '334') { + $sock->print(MIME::Base64::encode_base64($client->client_step(MIME::Base64::decode_base64($text)), '')); + $sock->print("\r\n"); + $sock->flush(); + + ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); + } + + if ($retval ne 'CONTINUE') { + $sock->print("QUIT\r\n"); + $sock->flush(); + # Swallow return value + get_smtp_return_code($sock, $recip, $server); + $sock->close(); + return ($retval, $text, $code, $dsn); + } } $sock->print("MAIL FROM:$sender\r\n");
_______________________________________________ NOTE: If there is a disclaimer or other legal boilerplate in the above message, it is NULL AND VOID. You may ignore it. Visit http://www.mimedefang.org and http://www.roaringpenguin.com MIMEDefang mailing list MIMEDefang@lists.roaringpenguin.com http://lists.roaringpenguin.com/mailman/listinfo/mimedefang