I'm trying to send mail using Perl Net::SMTP with auth method other than default picked GSSAPI (e.g. force PLAIN).
I have tried:
my $smtp;
$smtp = Net::SMTP::SSL->new($host, Port => $port);
$smtp->auth($user, $passwd);
and replacing last line with:
$smtp->auth('PLAIN', $user, $passwd);
or passing Authen::SASL object with selected mechanism to $smtp->auth(). None of the above work - debug (and mail server logs) says it still tries AUTH GSSAPI.
Does anyone know how to correctly force auth method in Net::SMTP?
My Perl version is 5.20.2-3+deb8u8 from Debian 8, packages version:
Net::SMTP - 2.33
Net::SMTP::SSL - 1.01
Authen::SASL - 2.16
Net::SMTP version above 3.00
Net::SMTP above version 3:
* does not overwrite mechanism in Authen::SASL parameter of auth method
* supports STARTTLS and smtps
use Net::SMTP;
use Authen::SASL;
my($host, $user, $pass) = ('...','...','...'); # fill correct data
my $smtp = Net::SMTP->new( $host, SSL=>1, Debug => 1 ); # SSL=>1 - use smtps
$smtp->auth(
Authen::SASL->new(
mechanism => 'PLAIN LOGIN',
callback => { user => $user, pass => $passwd }
)
);
Net::SMTP version below 3.00
Net::SMTP version 2.31_1 (newest pre 3.00 in libnet) overwrites mechanism in Authen::SASL with mechanism listed in EHLO reply. Below please find my UGLY fix.
use Net::SMTP::SSL;
use Authen::SASL;
my ( $host, $port, $user, $pass ) = ( '...', '...', '...', '...' ); # fill correct data
my $smtp = Net::SMTP::SSL->new( $host, Port => $port, Debug => 1 );
my $auth = Authen::SASL->new(
mechanism => 'PLAIN LOGIN',
callback => { user => $user, pass => $passwd }
);
{
no warnings 'redefine';
my $count;
local *Authen::SASL::mechanism = sub {
my $self = shift;
# Fix Begin
# ignore first setting of mechanism
if ( !$count++ && #_ && $Net::SMTP::VERSION =~ /^2\./ ) {
return;
}
# Fix End
#_
? $self->{mechanism} = shift
: $self->{mechanism};
};
$smtp->auth($auth);
}
Code requiring fixing - Net::SMTP 2.31_1 from libnet 1.22_01
sub auth {
my ($self, $username, $password) = #_;
...
my $mechanisms = $self->supports('AUTH', 500, ["Command unknown: 'AUTH'"]);
return unless defined $mechanisms;
my $sasl;
if (ref($username) and UNIVERSAL::isa($username, 'Authen::SASL')) {
$sasl = $username;
$sasl->mechanism($mechanisms); # <= HERE Authen::SASL mechanism overwrite
}
Related
I'm trying to use an IO loop to send concurrent requests (5) to a pool of hosts (3), but the code stops after 3 requests. I've had help to kickstart this code, but I certainly understand most of it now. What I don't get is why the number of processed requests is linked to the number of hosts in my pool of hosts. The objective of the code is to determine routing information from a given IP.
use strict;
use warnings;
use Net::OpenSSH;
use IO::Async::Loop;
use Future::Utils 'fmap_concat';
my #hosts = qw(host1 host2 host3);
my #ssh;
my $user = 'myuser';
my $pass = 'mypassword';
foreach my $host (#hosts) {
my $ssh = Net::OpenSSH->new(host => $host, user => $user, password => $pass, master_opts => [-o => "StrictHostKeyChecking=no"]);
die "Failed to connect to $host: " . $ssh->error if $ssh->error;
push #ssh, $ssh;
}
my #ipv4 = (
'ip1','ip2','ip3','ip4','ip5'
);
my $loop = IO::Async::Loop->new;
my $future = fmap_concat {
my $ip = shift;
my $ssh = shift #ssh;
my $cmd = 'show ip route '.$ip.' | i \*';
my #remote_cmd = $ssh->make_remote_command($cmd);
return $loop->run_process(command => \#remote_cmd)
->transform(done => sub { [#_] })
->on_ready(sub { push #ssh, $ssh });
} generate => sub { return () unless #ssh and #ipv4; shift #ipv4 }, concurrent => scalar #ssh;
my #results = $future->get;
foreach my $result (#results) {
my ($exit, $stdout) = #$result;
print $stdout, "\n";
}
Here are the results
Connection to host1 closed by remote host.
Connection to host2 closed by remote host.
Connection to host3 closed by remote host.
* ip1, from host1, 3w0d ago, via GigabitEthernet0/0/0
* ip2, from host2, 7w0d ago, via GigabitEthernet0/0/0
* ip3, from host3, 3w0d ago, via GigabitEthernet0/0/1
After researching on the problem, I found out that network devices such as Cisco might have an issue handling several requests over the same connection. So the code changed a bit in a way where a new connection is opened everytime the future is called instead of using a pool of pre-opened connections.
use strict;
use warnings;
use Net::OpenSSH;
use IO::Async::Loop;
use Future::Utils 'fmap_concat';
my #hosts = qw(host1 host2 host3);
my #ssh;
my $user = 'myuser';
my $pass = 'mypassword';
my #ipv4 = (
'ip1','ip2','ip3','ip4','ip5'
);
my $loop = IO::Async::Loop->new;
my $future = fmap_concat {
my $ip = shift;
my $host = shift #hosts;
my $ssh = Net::OpenSSH->new(host => $host, user => $user, password => $pass, master_opts => [-o => "StrictHostKeyChecking=no"]);
die "Failed to connect to $host: " . $ssh->error if $ssh->error;
my $cmd = 'show ip route '.$ip.' | i \*|^Routing entry';
my #remote_cmd = $ssh->make_remote_command($cmd);
return $loop->run_process(command => \#remote_cmd)
->transform(done => sub { [#_] })
->on_ready(sub { push #hosts, $host ; });
} generate => sub { return () unless #hosts and #ipv4; shift #ipv4 }, concurrent => scalar #hosts;
my #results = $future->get;
foreach my $result (#results) {
my ($exit, $stdout) = #$result;
print $stdout, "\n";
}
But this has leaded to other problems with underlying openssh library.
It looks like there was a race condition with the ssh connection not being released properly when the future was being invoked on the $host again.
undef $ssh fixed it
->on_ready(sub { undef $ssh; push #hosts, $host ; });
I'm trying to debug a weird warning that is showing up in server logs when a Plack::Request is being parsed. In some cases, a broken UserAgent will send a Content-Length header that looks something like "6375, 6375", which is obviously wrong.
To fix this properly, I need to be able to reproduce the warning. I'd like to include this in a unit test so that I can ensure there are no regressions after the warning is silenced. However, I'm having trouble doing this with Perl. I know this can be done using netcat and socat, but I don't want the unit test to have to rely on other binaries to be installed.
Here is what I've tried:
#!/usr/bin/env perl
use strict;
use warnings;
use JSON::XS qw( encode_json );
use WWW::Mechanize;
my $mech = WWW::Mechanize->new;
$mech->add_handler(
request_prepare => sub {
my ( $req, $ua, $h ) = #_;
$req->headers->header( 'Content-Length' => 9999 );
return;
}
);
my $json = encode_json( { foo => 'bar' } );
$mech->post(
'http://example.com'/url,
'Content-Length' => 999,
Content => $json
);
Output is:
Content-Length header value was wrong, fixed at /opt/perl5.16.3/lib/site_perl/5.16.3/LWP/Protocol/http.pm line 260.
200
That's entirely too helpful for me. :)
If I use HTTP::Request and LWP::UserAgent, it's the same end result.
So, I tried HTTP::Tiny.
#!/usr/bin/env perl
use strict;
use warnings;
use DDP;
use HTTP::Tiny;
use JSON::XS qw( encode_json );
my $http = HTTP::Tiny->new;
my $json = encode_json( { foo => 'bar' } );
my $response = $http->request(
'POST',
'http://example.com'/url',
{ headers => { 'Content-Length' => 999, },
content => $json,
}
);
p $response;
The output is:
{ content => "Content-Length missmatch (got: 13 expected: 999)
",
headers => {
content
-length => 49,
content-type => "text/plain",
},
reason => "Internal Exception",
status => 599,
success => "",
url => "http://example.com'/url",
}
Again, too helpful. At this point, I could use a few suggestions.
Seems like the higher level API's are fixing your error; Here's an example using raw sockets that overcomes this;
#!/usr/bin/env perl
use strict 'vars';
use warnings;
use Socket;
# initialize host and port
my $host = 'www.example.com';
my $port = 80;
# contact the server
open_tcp(F, $host, $port)
or die 'Could not connect to server';
# Send request data
while ( my $request = <DATA> ) {
print F $request;
}
# Get Response
while ( my $response = <F> ) {
print "Response:> $response";
}
close(F);
# TCP Helper
sub open_tcp
{
# get parameters
my ($FS, $dest, $port) = #_;
my $proto = getprotobyname('tcp');
socket($FS, PF_INET, SOCK_STREAM, $proto);
my $sin = sockaddr_in($port,inet_aton($dest));
connect($FS,$sin);
my $old_fh = select($FS);
$| = 1; # don't buffer output
select($old_fh);
}
__DATA__
GET / HTTP/1.1
Host: example.com
Content-Length: 999
-END-
I want to know if it is possible to return a object handle from a subroutine in a Perl program.
I will use a specific example from a program that uses MAIL::IMAPClient
Create client object handle
my $client = Mail::IMAPClient->new(
Socket => $socket,
User => $user,
Password => $pass,
)
or die "new(): $#";
I would like to create this object handle from a sub routine instead
my $client = &create_client_object;
sub create_client_object {
my $client = Mail::IMAPClient->new(
Socket => $socket,
User => $user,
Password => $pass,
)
or die "new(): $#";
return $client;
}
If possible, what is the proper way to do this?
Yes, that works perfectly. Besides #Miller's comment, I'd recommend you to also pass the $socket, $user and $pass as parameters to your function instead of using them from context:
my $client = create_client_object($socket, $user, $pass);
sub create_client_object {
my ($socket, $user, $pass) = #_;
my $client = Mail::IMAPClient->new(
Socket => $socket,
User => $user,
Password => $pass,
)
or die "new(): $#";
return $client;
}
I'm writing an email service which sends data to my users with Email::Sender::Simple and Email::Sender::Transport::SMTPS. Right now, I've got a package which should just take some inputs and send an e-mail:
package MyApp::Service::Mail;
use Email::Sender::Simple qw(sendmail);
use Email::Simple;
use Email::Sender::Transport::SMTPS;
use Try::Tiny;
use Dancer;
use constant CANT_SEND_MAIL => -1;
use constant SENT_SUCCESSFULLY => 1;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub sendEmail {
my $self = shift;
my $to = shift;
my $subject = shift;
my $body = shift;
my $failed = 0;
my $email = Email::Simple->create(
header => [
To => $to,
From => 'noreply#myapp.com',
Subject => $subject
],
body => $body
);
my $transport = Email::Sender::Transport::SMTPS->new({
host => config->{smtp_host},
port => config->{smtp_port},
sasl_username => config->{smtp_username},
sasl_password => config->{smtp_password},
ssl => 'ssl'
});
try {
sendmail($email, {transport => $transport});
} catch {
$failed = 1;
}
return $self->CANT_SEND_MAIL if ($failed eq 1);
return $self->SENT_SUCCESSFULLY;
}
1;
This is based heavily on the example from the CPAN page for the modules involved.
Note that those config variables are coming from Dancers config.yml, and I have confirmed they are being passed in correctly. I have also confirmed that $to, $body and $subject contain what I expect them to.
The sendEmail function is being called and returning 1 (SENT_SUCCESSFULLY) but I cannot see anything in the Sent box on my e-mail client, and there is nothing at the receiving address either. I've been trying to find some kind of debug function to delve deeper into why this is failing, but to no avail.
Code calling this is:
package MyApp::Service::Mail::User;
use MyApp::Service::Mail;
our #ISA = qw(MyApp::Service::Mail);
sub sendPasswordByEmail {
my $self = shift;
my $to = shift;
my $username = shift;
my $subject = "Test E-Mail";
(my $body = << " END_MESSAGE") =~ s/^ {4}//gm;
Dear $username,
This is a test e-mail.
END_MESSAGE
return $self->sendEmail($to, $subject, $body);
}
1;
I can confirm the SMTP account definitely works as I use it in my e-mail client (Thunderbird). Any suggestions as to why this function could be returning 1 with no success? Is there a way to debug the connection between this and my SMTP server (it's 3rd party so can't check the logs) to see if a connection is being established and what's being passed / whether there's a problem?
There is a semicolon missing after the try {} catch {} block. Without it, the following line becomes part of the same statement, so the entire try/catch block is conditional based on $failed, which will never be 1 at that point.
This is an unfortunate side effect of the implementation of Try::Tiny, but it can't be avoided in a pure-perl implementation. Your existing code is parsed is like:
try(
sub {
sendmail($email, {transport => $transport});
},
catch(
sub {
$failed = 1;
},
return($self->CANT_SEND_MAIL),
),
) if ($failed eq 1);
I'm trying to use the Net::Twitter::Stream Perl module from CPAN to read the stream from sample.json. I believe this is the corect module though they way they crafted it allows one to process the filter stream. I've modified it as such but I must be missing something as I don't get any data in return. I establish a connection but nothing comes back. I'm guessing this should be an easy fix but I'm a touch new to this part of Perl.....
package Net::Twitter::Stream;
use strict;
use warnings;
use IO::Socket;
use MIME::Base64;
use JSON;
use IO::Socket::SSL;
use LibNewsStand qw(%cf);
use utf8;
our $VERSION = '0.27';
1;
=head1 NAME
Using Twitter streaming api.
=head1 SYNOPSIS
use Net::Twitter::Stream;
Net::Twitter::Stream->new ( user => $username, pass => $password,
callback => \&got_tweet,
track => 'perl,tinychat,emacs',
follow => '27712481,14252288,972651' );
sub got_tweet {
my ( $tweet, $json ) = #_; # a hash containing the tweet
# and the original json
print "By: $tweet->{user}{screen_name}\n";
print "Message: $tweet->{text}\n";
}
=head1 DESCRIPTION
The Streaming verson of the Twitter API allows near-realtime access to
various subsets of Twitter public statuses.
The /1/status/filter.json api call can be use to track up to 200 keywords
and to follow 200 users.
HTTP Basic authentication is supported (no OAuth yet) so you will need
a twitter account to connect.
JSON format is only supported. Twitter may depreciate XML.
More details at: http://dev.twitter.com/pages/streaming_api
Options
user, pass: required, twitter account user/password
callback: required, a subroutine called on each received tweet
perl#redmond5.com
#martinredmond
=head1 UPDATES
https fix: iwan standley <iwan#slebog.net>
=cut
sub new {
my $class = shift;
my %args = #_;
die "Usage: Net::Twitter::Stream->new ( user => 'user', pass => 'pass', callback => \&got_tweet_cb )" unless
$args{user} && $args{pass} && $args{callback};
my $self = bless {};
$self->{user} = $args{user};
$self->{pass} = $args{pass};
$self->{got_tweet} = $args{callback};
$self->{connection_closed} = $args{connection_closed_cb} if
$args{connection_closed_cb};
my $content = "follow=$args{follow}" if $args{follow};
$content = "track=$args{track}" if $args{track};
$content = "follow=$args{follow}&track=$args{track}\r\n" if $args{track} && $args{follow};
my $auth = encode_base64 ( "$args{user}:$args{pass}" );
chomp $auth;
my $cl = length $content;
my $req = <<EOF;
GET /1/statuses/sample.json HTTP/1.1\r
Authorization: Basic $auth\r
Host: stream.twitter.com\r
User-Agent: net-twitter-stream/0.1\r
Content-Type: application/x-www-form-urlencoded\r
Content-Length: $cl\r
\r
EOF
my $sock = IO::Socket::INET->new ( PeerAddr => 'stream.twitter.com:https' );
#$sock->print ( "$req$content" );
while ( my $l = $sock->getline ) {
last if $l =~ /^\s*$/;
}
while ( my $l = $sock->getline ) {
next if $l =~ /^\s*$/; # skip empty lines
$l =~ s/[^a-fA-F0-9]//g; # stop hex from compaining about \r
my $jsonlen = hex ( $l );
last if $jsonlen == 0;
eval {
my $json;
my $len = $sock->read ( $json, $jsonlen );
my $o = from_json ( $json );
$self->{got_tweet} ( $o, $json );
};
}
$self->{connection_closed} ( $sock ) if $self->{connection_closed};
}
You don't need to post the source, we can pretty much figure it out. You should try one of the examples, but my advice is to use AnyEvent::Twitter::Stream which comes with a good example that you only have to modify a bit to get it running
sub parse_from_twitter_stream {
my $user = 'XXX';
my $password = 'YYYY';
my $stream = Net::Twitter::Stream->new ( user => $user, pass => $password,
callback => \&got_tweet,
connection_closed_cb => \&connection_closed,
track => SEARCH_TERM);
sub connection_closed {
sleep 1;
warn "Connection to Twitter closed";
parse_from_twitter_stream();#This isn't working for me -- can't get connection to reopen after disconnect
}
sub got_tweet {
my ( $tweet, $json ) = #_; # a hash containing the tweet
#Do stuff here
}
}