Hire a senior Perl / Python programmer today; download my up-to-date resume (PDF)
John Bokma MexIT
freelance Perl programmer

MIME::Lite SMTP non-default port bug and fix

Monday, August 17, 2009 | 1 comment

Note: my patched version of 3.024 is now available as MIME::Lite 3.025 on CPAN, thanks Ricardo!

If you use SMTP to send email using MIME::Lite from your Perl program and use not the default SMTP port 25 but specify one as follows:

$msg->send('smtp','some.host', Port => 12345 );

the Port setting is ignored if you're using MIME::Lite version between 3.01_04 - 3.024 (inclusive). The culprit is not Net::SMTP which checks the value of the Port argument, and if true copies this value to PeerPort otherwise sets PeerPort to 'smtp(25)', but MIME::Lite which copies argument name-value pairs only if the argument name is in a list, a list which misses Port. This results in the following error if port 25 is configured to not accept connections (viz. blocked by your firewall, blocked by your ISP, has no service listening on port 25 on the server):

SMTP Failed to connect to mail server: Unknown error

because MIME::Lite by not passing the Port argument and value makes Net::SMTP default to smtp(25).

Adding a Debug argument with a non-zero value doesn't report anything because Net::SMTP can't even connect.

MIME::Lite non-default smtp port fix

Edit 18th of August 2009: an easier solution might be to specify the Host value as host:port. Since MIME::Lite uses Net::SMTP which allows this value to "be a single scalar, as defined for the PeerAddr option in IO::Socket::INET" which is <hostname>[:<port>]

In order to make MIME::Lite copy the non-default port you just have to add Port to the list that specifies which agument name-value pairs MIME::Lite should copy. This list is assigned to the variable @_net_smpt_opts:

my @_net_smtp_opts = qw( Hello LocalAddr LocalPort Timeout
                         ExactAddresses Debug );

at line 2829 for MIME::Lite 3.024. By adding Port to this it's again possible to specify a non-default SMTP port:

my @_net_smtp_opts = qw( Hello LocalAddr LocalPort Timeout
                         Port ExactAddresses Debug );

If you don't want to modify Lite.pm on your system you can copy the Lite.pm file to a directory named MIME and modify it.

In order for your Perl program to load the modified MIME::Lite module you have to make sure that the path to the parent directory of the MIME directory is added to the front of the @INC array:

See also: perldoc perlrun.

Another option is to create a module that inherits from MIME::Lite and redefines the __opt method. And since there are always many ways in Perl, you could also redefine this method by manipulating the symbol table of MIME::Lite as follows:

{
    no warnings 'redefine';
    *MIME::Lite::__opts = sub {

        my ( $args, @keys ) = @_;
        join ( '', @keys ) 
	    eq 'HelloLocalAddrLocalPortTimeoutExactAddressesDebug'
            and push @keys, 'Port';
        return map {

            exists $args->{$_} ? ( $_ => $args->{$_} ) : ()

        } @keys;
    };
}

Note the use of no warnings 'redefine'; - within an enclosing block to limit its scope - to prevent a "Subroutine MIME::Lite::__opts redefined at ... line ...." warning.

Since __opts is called with a list of keys to copy the above code check if it's the list that misses Port and if so, adds it

Note: make sure you check the version of MIME::Lite before you redefine __opts.

Reporting the bug and helping out

After I had reported this bug I noticed that it had already been reported nearly 2 years ago: #21156: Not passing PORT parameter from smtp to Net::SMTP (Note to self: read the reported bugs first before reporting a duplicate). Anyway, since I can more than imagine that the current maintainer, Ricardo Signes, has been quite busy I emailed him if I could email a patch or use git; I had noticed that mime-lite is located at http://github.com/rjbs/mime-lite/.

Within a very short time I received a reply that either way would be fine but a git remote to pull would be preferred. After I had installed git and git-core and done a

git clone git://github.com/rjbs/mime-lite/ mime-lite-remote

I tried to find out, using "Pragmatic Version Control Using Git" by Travis Swicegood how to do the "remote to pull". Since I couldn't find an answer fast enough and also because I had the feeling that "remote to pull" involves the files I would modify to be available on the Internet somehow I decided to just generate a patches file.

Creating a patches file with git diff

So after modifying ./changes.pod (entry for 3.025 including thanking myself) and ./lib/MIME/Lite.pm (update of $VERSION's value and addition of Port to @_net_smtp_opts) I generated a patches file using:

git diff > ../patch.txt

which I emailed to Ricardo.

Note: there is a git send-email but which I am sure makes things much easier if I take the time to set all SMTP settings.

Github: fork, clone, edit, commit, push, and pull request

Shortly after I had emailed the file containing the MIME::Lite patches to Ricardo I received a very clear explanation of the "remote to pull" git part I didn't understand:

  1. click "fork" button on github
  2. clone your fork of the project
  3. edit locally
  4. commit and push
  5. click "pull request" on github, causing them to email me

So I created a github free account, and did:

git clone git@github.com:john-bokma/mime-lite.git jb-mime-lite-remote

which failed with "Permission denied (publickey)." because I hadn't added a public SSH key during sign up. After I had created a public SSH key and added it to my profile, the aforementioned command succeeded, after it asked for my pass phrase.

Note: the clone URL was copied from the one given after "Your Clone URL:" at http://github.com/john-bokma/mime-lite/tree/master.

Next I copied the changed files I already had over the cloned repository, commited both files, and pushed the changes upstream using git push. I checked my fork at Github and hurray, my changes were there. Then I realised that it would be a good idea to have Port listed in the documentation of send_by_smtp as well, so I added "=item Port" to the POD section above send_by_smtp.

Satisfied with the changes I clicked the "pull request" button on my mime-lite page, and entered a note for Ricardo, to thank him for explaining how to do the pull request.

About an hour later I got an email from Ricardo stating "Merged, reviewed, released.". I checked CPAN and there it was MIME::Lite 3.025, the 3.024 version I patched.

In his email Ricardo also suggested to consider Email::Sender, which I certainly will do.

Special thanks

Special thanks to Ricardo "rjbs" Signes for taking the time to read and reply to my emails, teaching me how to use github to assist in updating MIME::Lite, and last but not least for maintaining MIME::Lite.

Also today

Please post a comment | read 1 comment by Anthony DiSante | RSS feed