[Mimedefang] ESMTP AUTH support for md_check_against_smtp_server()

Franz Schwartau franz at electromail.org
Tue Oct 22 09:03:30 EDT 2013


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
-------------- next part --------------
--- 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");


More information about the MIMEDefang mailing list