0
votes

I am using Strawberry Perl (v5.16.3) on a Windows 2008 server. I have added some CPAN modules for use in a script, but this seems to have broken MIME:Lite (which is used by other scripts on this box). When I run the following code:

use strict;
use warnings;
use v5.16;

use MIME::Lite;

my $msg = MIME::Lite->new(From     => '[email protected]',
                          To       => '[email protected]',
                          Subject  => "testing",
                          Type     => 'multipart/mixed');
my $dataString = "Trying to figure out why this isn't working.\r\n";
$msg->attach(  Type            => 'TEXT',
                  Data            => $dataString);  
$msg->send('smtp', 'mailhost.com', Debug=>1);

I get the following error message:

MIME::Lite::SMTP>>> MIME::Lite::SMTP
MIME::Lite::SMTP>>>   Net::SMTP(3.10)
MIME::Lite::SMTP>>>     Net::Cmd(3.10)
MIME::Lite::SMTP>>>       Exporter(5.67)
MIME::Lite::SMTP>>>     IO::Socket::INET6(2.69)
MIME::Lite::SMTP>>>       IO::Socket(1.34)
MIME::Lite::SMTP>>>         IO::Handle(1.33)
MIME::Lite::SMTP: Net::Cmd::_is_closed(): unexpected EOF on command channel:  at d:/strawberry/perl/site/lib/MIME/Lite.pm line 2877.
SMTP Failed to connect to mail server: Bad file descriptor.

The modules I installed where Email::Simple, Email::Sender, Email::MIME::CreateHTML & String::Util. As I am sure you know, there were various dependencies that were installed as well.

I would just switch the scripts over to Email::Sender but that is getting a 421 error from the server (which is yet another mystery). I am able to connect to the mail server through telnet and get a 'HELO' with no problem.

I fear the Perl installation is now all jacked up but I am hoping one of you brilliant guys might have some insight on how to fix it.

Thanks in advance for your help.

EDIT: I noticed that the Socket.pm in perl/lib was updated. Could that be causing the problem? I updated MIME::Lite to the latest (ver. 3.030) but that did not solve the problem.

EDIT #2: Per, ikegami's reply, added print statements to NET::SMTP:

sub new {
    print "inside Net::SMTP::new\n";
  my $self = shift;
  my $type = ref($self) || $self;
  my ($host, %arg);
  if (@_ % 2) {
    $host = shift;
    %arg  = @_;
  }
  else {
    %arg  = @_;
    $host = delete $arg{Host};
  }

    print "checked for SSL.\n";
  if ($arg{SSL}) {
    # SSL from start
    die $nossl_warn if !$ssl_class;
    $arg{Port} ||= 465;
  }

  my $hosts = defined $host ? $host : $NetConfig{smtp_hosts};
  my $obj;

  $arg{Timeout} = 120 if ! defined $arg{Timeout};
    print "set timeout:  $arg{Timeout}. \n";

  foreach my $h (@{ref($hosts) ? $hosts : [$hosts]}) {
      print "host: $h, port: $arg{Port}, laddr: $arg{LocalAddr}, lport: $arg{LocalPort}, familyKey: " . ( $arg{Domain} || $arg{Family} ) . "\n";
    $obj = $type->SUPER::new(
      PeerAddr => ($host = $h),
      PeerPort => $arg{Port} || 'smtp(25)',
      LocalAddr => $arg{LocalAddr},
      LocalPort => $arg{LocalPort},
      $family_key => $arg{Domain} || $arg{Family},
      Proto     => 'tcp',
      Timeout   => $arg{Timeout}
      )
      and last;
  }
    print "$obj\n";
  return
    unless defined $obj;

    print "object defined. \n";
  ${*$obj}{'net_smtp_arg'} = \%arg;
  ${*$obj}{'net_smtp_host'} = $host;
    print "set net_smtp_host to $host\n";
  if ($arg{SSL}) {
      print "setting SSL. \n";
    Net::SMTP::_SSL->start_SSL($obj,%arg)
      or return;
  }

  $obj->autoflush(1);
    print "set autoflush.\n";
  $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef);
    print "setting debug: $arg{Debug}\n";
    my $response = $obj->response();
    print "object response: $response (5 == CMD_ERROR)\n";
  unless ($obj->response() == CMD_OK) {
    my $err = ref($obj) . ": " . $obj->code . " " . $obj->message;
    $obj->close();
    $@ = $err;
    print "returning because response was not CMD_OK\n";
    return;
  } ....

Here's the output:

D:\strawberry>perl D:\Perl\Src\pmTasks\emailTest.pl
inside Net::SMTP::new
checked for SSL.
set timeout:  120.
Use of uninitialized value in concatenation (.) or string at D:/strawberry/perl/lib/Net/SMTP.pm line 83, <DATA> line 100
3.
Use of uninitialized value in concatenation (.) or string at D:/strawberry/perl/lib/Net/SMTP.pm line 83, <DATA> line 100
3.
Use of uninitialized value in concatenation (.) or string at D:/strawberry/perl/lib/Net/SMTP.pm line 83, <DATA> line 100
3.
Use of uninitialized value in concatenation (.) or string at D:/strawberry/perl/lib/Net/SMTP.pm line 83, <DATA> line 100
3.
host: {correct_mailhost_here}, port: , laddr: , lport: , familyKey:
MIME::Lite::SMTP=GLOB(0x1f36fbc)
object defined.
set net_smtp_host to {correct_mailhost_here}
set autoflush.
MIME::Lite::SMTP>>> MIME::Lite::SMTP
MIME::Lite::SMTP>>>   Net::SMTP(3.10)
MIME::Lite::SMTP>>>     Net::Cmd(3.10)
MIME::Lite::SMTP>>>       Exporter(5.67)
MIME::Lite::SMTP>>>     IO::Socket::INET6(2.69)
MIME::Lite::SMTP>>>       IO::Socket(1.34)
MIME::Lite::SMTP>>>         IO::Handle(1.33)
setting debug: 1
MIME::Lite::SMTP: Net::Cmd::_is_closed(): unexpected EOF on command channel:  at D:/strawberry/perl/site/lib/MIME/Lite.pm line 2877.
object response: 5 (5 == CMD_ERROR)
MIME::Lite::SMTP: Net::Cmd::_is_closed(): unexpected EOF on command channel:  at D:/strawberry/perl/site/lib/MIME/Lite.pm line 2877.
returning because response was not CMD_OK
SMTP Failed to connect to mail server: Bad file descriptor

So, it seems that reason it is returning is that the $obj has a response of CMD_ERROR. The only parameter it is sending to the $obj=$type->SUPER::new is the host. (which is the correct host, but I had to blank it out above).

So, I'm not sure which superclass this is calling. It seems to be returning the constants from NET::Cmd, but there is no new subroutine. IO::Socket:INET calls the new from IO::Socket, which calls the new from IO::Handle which seems to be calling the fdopen C code? I get a little lost here. However, thanks for your help so far. Seems that we are almost getting to the root of the problem here.

1
Maybe you could get add some more debug output to your question. But given "failed to connect" it might be that your mail server has an IPv6 address and an IPv4 address and that IPv6 is not properly setup. But Net::SMTP 3.x (which you have installed) will use IPv6 if the hostname resolves to an IPv6 address.Steffen Ullrich
It's not even getting to the point where it can send HELO. It can't even connect, or can't establish a SSL handshake once connected if so configured. I would edit Net/SMTP.pm to obtain more info. Specifically: 1) Which return causes sub new to exit? 2) What arguments are passed to $type->SUPER::new in each pass of the foreach loop?ikegami
@Steffen Ullrich - So, nothing has changed on the mail server. It has always been at IPv4. But thanks for the idea, one never knows. @ikegami - I started digging into the NET::SMTP code. Please see my edit above. Thanks for the idea.FrankRalphBob
@FrankRalphBob: that's what I mean: the mail server is IPv4 only. But if the hostname resolves also to a IPv6 address it will try this one and fail. Older versions of Net::SMTP did not lookup IPv6 at all so these worked in that scenario. If the mail server is public it would be useful if you could provide the hostname so that one can try to reproduce the problem.Steffen Ullrich
@Steffen Ullrich - Sorry, I misunderstood you. Unfortunately, the mail server is not public. Is there any way to determine if Net::SMTP is using IPv6? or to force IPv4? Thanks for your help!FrankRalphBob

1 Answers

1
votes

With Net::SMTP version 3.x support for SSL and IPv6 was added. If the necessary packages are installed (i.e. IO::Socket::IP or IO::Socket::INET6) it will use getaddrinfo to resolve the name which usually prefers IPv6 before IPv4.

This is not a problem if everything is properly setup. But in your case the hostname of the mail server resolves to both IPv6 and IPv4 address even though there is no mail server listening on the IPv6 address or a firewall is blocking the connection. Thus results in a failed connection because getaddrinfo returns that IPv6 should be used but the server is not reachable by IPv6.

The proper fix would be to either remove the IPv6 record for the host or to make it accessible by IPv6. As long as this is not the case one might work around the problem by explicitly enforcing IPv4 in Net::SMTP:

 use Socket;
 Net::SMTP->new(host, Domain => AF_INET,...);