Perl, Sendgrid and Net::SMTP and MIME::Entity - perl

In my Perl code, I was using Net::SMTP and MIME:Entity to send email through my Sendgrid account (from their v2 api, where I could use my account login as the api)...
it was working flawlessly.
But we moved servers to a newer version.
Still Unix (Linux), but has newer hardware, including a newer version of Plesk.
The IP addresses are all the same, we moved them to the new hardware. We did change the IP address that is the primary one, but we made sure we added it to sendgrid whitelabel...
So I'm clueless why it would stop working.
I am not getting any errors in the code, the api is connecting. But I don't ever see any emails, no matter what I do.
Since no errors, not sure why it does not work.
Here is my code, do you see the flaw that would make it fail?
sub Send_Multi_Part_Email_MimeLite {
my ($__to,$__cc,$__bcc,$__from,$__subject,$__html_message,$__text_message,$__importance,$__xpriority,$__x_ms_priority) = #_;
if($__x_ms_priority && ($__x_ms_priority =~ /\|/)) {
($__x_ms_priority,$_attachFile,$_attachFilePath) = split /\|/, $__x_ms_priority, 3;
}
if($__xpriority && ($__xpriority =~ /\|/)) {
($__xpriority,$_unSubKey,$_mbrUn) = split /\|/, $__xpriority, 3;
}
if($__importance && ($__importance =~ /\|/)) {
($__importance,$_emailType) = split /\|/, $__importance, 2;
}
my $_useNewSystem = 1; # Declare new system... set to 1 to use it and not use MimeLite any longer...
if($_useNewSystem) {
use MIME::Entity;
use Net::SMTP;
my $_sendIp = $ENV{REMOTE_ADDR} || "127.0.0.1";
if($__to && ($__to =~ /\;/)) {
$__to =~ s/\;/\,/g;
}
if($__cc && ($__cc=~ /\;/)) {
$__cc =~ s/\;/\,/g;
}
if(!$__bcc) {
$__bcc = 'testmailacct#gmail.com'; # not real, but when in production is a real email, so we can get emails to confirm they are being delivered... only when debugging... Commented out if not in test mode...
}
if($__bcc && ($__bcc =~ /\;/)) {
$_bcc =~ s/\;/\,/g;
}
if(!$__html_message) {
$__html_message = $__text_message;
$__html_message =~ s/\n/br()."\n"/eg;
$__html_message =~ s/\cM\cJ/br()."\n"/eg;
}
$_emailSentType = "";
$mime = MIME::Entity->build(Type => 'multipart/alternative',
Encoding => '-SUGGEST',
From => $__from,
To => $__to,
Subject => $__subject,
'Importance' => $__importance,
"X-Mailer" => "$_co_domain Sendgrid Mailer - Version 2.0",
'X-Organization' => "$_co_name",
"X-Mail-Sent-For" => "The Club or www.$_co_domain/$_un; From IP: $_sendIp",
'X-Priority' => $__xpriority,
'X-MSMail-Priority' => $__x_ms_priority
);
$_sendKey = ""; # removed for security...
$_removeLink = qq~https://www.testing.com/backoffice.cgi?page=unsubscribe$_sendKey~;
$__text_message .= qq~
You are receiving this email as a club member of The $_co_name. You can turn off your
email subscription by logging into your back office with your username and password
that you registered with. Or you may click:
$_removeLink to remove yourself.
~;
if($__html_message) {
$__html_message .= qq~<br>
<br>
<span style="font-size: 12px; color: #808040;">
You are receiving this email as a club member of The $_co_name.<br>
You can turn off your email subscription by logging into your back office<br>
with your username and password that you registered with.<br>
Or you may click: Here to remove yourself,<br>
or call our Customer Care Center at (888)123-4567 for assistance<br>
<br>
<br></span>
<br>
~;
}
$mime->attach(Type => 'text/plain',
Encoding =>'-SUGGEST',
Data => $__text_message);
$mime->attach(Type => 'text/html',
Encoding =>'-SUGGEST',
Data => $__html_message);
if($_attachFile) {
### Attach stuff to it:
if($_attachFilePath && ($_attachFilePath =~ /csv$/i)) {
$_mimType = "text/csv";
$_mimEncoding = "US-ASCII";
} elsif($_attachFilePath && ($_attachFilePath =~ /gif$/i)) {
$_mimType = "image/gif";
$_mimEncoding = "base64";
} elsif($_attachFilePath && ($_attachFilePath =~ /jpg$/i)) {
$_mimType = "images/jpeg";
$_mimEncoding = "base64";
} elsif($_attachFilePath && ($_attachFilePath =~ /png$/i)) {
$_mimType = "images/png";
$_mimEncoding = "base64";
} else {
$_mimType = "text/plain";
$_mimEncoding = "UTF-8";
}
# Attach the file that it sent...
$mime->attach(Path => $_attachFilePath,
Type => "$_mimType",
Encoding => "$_mimEncoding");
}
# Sendgrid Login credentials
$username = 'myloginemail';
$password = "myloginpass";
# Open a connection to the SendGrid mail server
$smtp = Net::SMTP->new('smtp.sendgrid.net',
Port=> 587,
Timeout => 60,
Hello => "testing.com", Debug => 1) or return("0","could not establish connection!: $!");
# Authenticate
if($smtp) {
$smtp->auth($username, $password);
# Send the rest of the SMTP stuff to the server
$smtp->mail($__from);
$smtp->to($__to);
$smtp->data($mime->stringify);
$smtp->quit();
open(DEB,">>/home/path/files/aa_mime_email_debug_tracking.txt");
seek(DEB,0,2);
$_resultMsg = $smtp->message();
$_resultMsg =~ s/\n//eg;
$_resultMsg =~ s/\cM\cJ//eg;
# just added this, not sure if it will work... want to create a loop of the $smtp to put it in the debug file, so I can see what is happening and why email does not go anywhere, but connection works...
$_resultMsg2 = "";
foreach (keys %{$smtp}) {
$_resultMsg2 .= "," if $_resultMsg2;
# Test this one:
$_resultMsg2 .= " $_ => ${$hash_ref}{$_}";
# Then test this one:
$_resultMsg2 .= ", $_ = " . ${$hash_ref}->{$_};
}
print DEB qq~Result: '~ . $_resultMsg . qq~'; smtpTest: '~ . $_resultMsg2 . qq~'; From => "$__from", To => "$__to", Subject => "$__subject", If Member Username passed: "$_mbrUn", on: ~ . Format_Date_For_Viewing(time(),"") . "\n";
close(DEB);
return (1,"");
} else {
open(DEB,">>/home/path/files/aa_mime_email_debug_tracking.txt");
seek(DEB,0,2);
print DEB qq~ERROR: '~ . $smtp->message() . qq~' (Sendgrid error!!!) - From => "$__from", To => "$__to", Subject => "$__subject", If Member Username passed: "$_mbrUn", on: ~ . Format_Date_For_Viewing(time(),"") . "\n";
close(DEB);
return (0,$smtp->message());
}
} else {
# Code for old email system... not using sendgrid, using sendmail... no longer in use, but code left there for times when sendgrid not working or something... used MimeLite
}
}
Anyhow, I have no idea why it is not working. The Server admins checked and there are no errors or no emails in the queue, either... so nothing just stuck somewhere, that we can find...
Can someone see anything, aside from my terrible programming??? lol
Thanks,
-Richard

If the program worked before, did not change, and is not giving any errors, there is probably nothing wrong with it. Test sending the email manually.
Start by encoding your username and password:
perl -MMIME::Base64 -e 'print encode_base64 $_ for qw/myloginemail myloginpass/'
Then use telnet with encoded username after the first 334 and password after the second:
telnet smtp.sendgrid.net 587
Trying 167.89.118.51...
Connected to smtp.sendgrid.net.
Escape character is '^]'.
220 SG ESMTP service ready at ismtpd0011p1las1.sendgrid.net
helo testing.com
250 Hello, nice to meet you
auth login
334 VXNlcm5hbWU6
bXlsb2dpbmVtYWls
334 UGFzc3dvcmQ6
bXlsb2dpbnBhc3M=
235 Authentication succeeded
mail from:<testmailacct#gmail.com>
250 2.1.5 Ok
rcpt to:<testmailacct#gmail.com>
250 2.1.5 Ok
data
354 End data with <CR><LF>.<CR><LF>
subject: test
This is test 0.
.
250 2.0.0 Ok: queued as BBAEA3483C73
quit
221 2.0.0 Bye
Connection closed by foreign host.

Related

Perl: Unable to check validity of hostname for Net::Appliance::Session

When I tried to pass an invalid hostname, the code will get into an infinite loop.
my $s = Net::Appliance::Session->new({
personality => 'ios',
transport => 'SSH',
host => $ip
});
Is there a way to overcome this bug?
EDIT:
Here's my full code:
I use the subroutine to download the config file of my network device. When I pass in an invalid IP address in download_config, it will get into an infinite loop.
sub download_config
{
my ($ip) = #_;
my $s = Net::Appliance::Session->new({
personality => 'ios',
transport => 'SSH',
host => $ip,
Timeout => 1
});
$s->set_global_log_at('debug'); # maximum debugging
eval {
$s->connect({ username => $username, password => $password });
$s->begin_privileged({ password => $enable_password });
#get hostname to set the file name
$hostname_result = $s->cmd('sh run | inc hostname');
$hostname_result =~ m/hostname (.*)/;
$hostname = $1;
#download the file
my #running_config = $s->cmd('sh run');
#running_config = #running_config[ 2 .. (#running_config -1)];#remove header and footer of the file
open(FH, "> temp/".$hostname.".txt") or die("Cannot open config file : $!");
print FH #running_config;
close FH;
$s->end_privileged;
};
if ($#) {
#when the login details are wrong
print redirect('../../na/unauthorised.html');
}
$s->close;
}
The developer has fixed the bug.
Are you sure?
Are you tried this example code with your host name?
http://cpansearch.perl.org/src/OLIVER/Net-Appliance-Session-3.120560/examples/example-1.pl
If yes, you could create an RT ticket or you could try to contact the author.
I have chacked the code, and did not found anything (too) nasty.
Regards,

Connecting keeps closing?

so i'm having a problem trying to automatically login to a internal website. I'm able to send a post request but in the response I always get the Header Connection: close. I've tried to pass is through the post request but it still seems to respond with Connection: close. I want to be able to navigate through the website so I need the Connection: keep-alive so that i can send more request. Could anyone tell me what I'm doing wrong? here's the code:
#usr/bin/perl
#NetTelnet.pl
use strict; use warnings;
#Sign into cfxint Unix something...
use Net::Telnet;
# Create a new instance of Net::Telnet,
my $telnetCon = new Net::Telnet (Timeout => 10,
Prompt => '/bash\$ $/') or die "Could not make connection.";
my $hostname = 'cfxint';
# Connect to the host of the users choice
$telnetCon->open(Host => $hostname,
Port => 23) or die "Could not connect to $hostname.";
use WWW::Mechanize;
my $mech = WWW::Mechanize->new(cookie_jar => {});
&login_alfresco;
sub login_cxfint {
#get username and password from user
my $CXusername = '';
my $CXpassword = '';
# Recreate the login
# Wait for the login: message and then enter the username
$telnetCon->waitfor(match => '/login:/i');
# this method adds a \n to the end of the username, it mimics hitting the enter key after entering your username
$telnetCon->print($CXusername);
# does the same as the previous command but for the password
$telnetCon->print($CXpassword);
#Wait for the login successful message
$telnetCon->waitfor();
}
sub login_alfresco{
my $ALusername = '';
my $ALpassword = '';
$mech->get('http://documents.ifds.group:8080/alfresco/faces/jsp/login.jsp');
my $res = $mech->res;
my $idfaces = '';
if($res->is_success){
my $ff = $res->content;
if($ff =~ /id="javax.faces.ViewState" value="(.*?)"/){
$idfaces = $1;
}
else {
print "javax.faces /Regex error?\n";
die;
}
}
print $idfaces, "\n";
#Send the get request for Alfresco
$mech->post('http://documents.ifds.group:8080/alfresco/faces/jsp/login.jsp',[
'loginForm:rediretURL' =>,
'loginForm:user-name' => $ALusername,
'loginForm:user-password' => $ALpassword,
'loginForm:submit' => 'Login',
'loginForm_SUBMIT' => '1',
'loginForm:_idcl' => ,
'loginForm:_link_hidden_' => ,
'javax.faces.ViewState' => $idfaces], **'Connection' =>'keep-alive'**);
$res = $mech->res;
open ALF, ">Alfresco.html";
print ALF $mech->response->as_string;
if($res->is_success){
my $ff = $res->content;
if($ff =~ /id="javax.faces.ViewState" value="(.*?)"/){
$idfaces = $1;
}
else {
print "javax.faces /Regex error?\n";
die;
}
}
print $idfaces, "\n";
#Logout
$mech->post('http://documents.ifds.group:8080/alfresco/faces/jsp/extension/browse/browse.jsp', [
'browse:serach:_option' => '0',
'browse:search' => ,
'browse:spaces-pages' => '20',
'browse:content-pages' => '50',
'browse_SUBMIT' => '1',
'id' => ,
'browse:modelist' => '',
'ref'=>'',
'browse:spacesList:sort' => ,
'browse:_idJsp7' => ,
'browse:sidebar-body:navigator' => ,
'browse:contentRichList:sort' => ,
'browse:act' => 'browse:logout',
'outcome' => 'logout',
'browse:panel' => ,
'javax.faces.ViewState' => $idfaces,])
}
You can enable keep-alive by using a connection cache:
use LWP::ConnCache;
...
$mech->conn_cache(LWP::ConnCache->new);
All that header means is that the connection will be closed upon completion of the request, instead of being kept open for possible further requests. This is perfectly normal and should not interfere with sending the request.
EDIT: If you're sending a Connection:Keep-Alive and the server is still responding with Connection:Close, then the server configuration needs to be changed. The default for HTTP/1.1 is persistent connections, so the server must explicitly be configured to send Connection:Close. See Section 8 of RFC2616.

Perl SSH connection to execute telnet

I tried the following to access a router via a central admin server as "ssh hop" server
#!/usr/bin/perl -X
use strict;
use Net::OpenSSH;
use Net::Telnet;
my $lhost = "linuxserver";
my $luser = "linuxuser";
my $lpass = "linuxpassword";
my $chost = "routername";
my $cpass = "Routerpassword";
my $prompt = '/(?:Password: |[>])/m';
my #commands = ("show users\r");
my $ssh = Net::OpenSSH->new($lhost,
'user' => $luser,
'password' => $lpass,
'master_opts' => [ '-t' ],
#'async' => 1 # if enabled then password cannot be set here
);
my ($pty, $err, $pid) = $ssh->open2pty("telnet $chost");
my $t = new Net::Telnet(
-telnetmode => 0,
-fhopen => $pty,
-prompt => $prompt,
-cmd_remove_mode => 1,
-output_record_separator => "\r",
#-dump_log => "debug.log",
);
my $end = 0;
while (!$end) {
my ($pre, $post) = $t->waitfor($prompt);
if ($post =~ /Password: /m) {
# send password
$t->print("$cpass");
}
elsif ($post =~ /[>#]/ && #commands) {
my $cmd = shift(#commands);
if ($cmd !~ /[\r\n]/) {
$t->print($cmd);
}
else {
print $t->cmd($cmd);
}
}
else {
$end = 1;
$t->cmd("exit");
}
}
#close $pty;
$t->close();
Unfortunately I always get the following error:
read error: Input/output error at test.pl line 71
Can somebody help me please or is there a better solution only to test if a telnet connection via the "hop" server is possible or not?
The connection looks like:
workstation --ssh-> server --telnet-> router
Thanks in advance.
I think best option is to make an SSH-tunnel to your admin server and use it for telnetting to the router.
Getting Net::Telnet to work over Net::OpenSSH sometimes is not as easy as it should be and it requires some experimentation to get to the right combination of flags and calls that make it work.
For instance, instead of telneting to the target host, use netcat to open a raw connection (or Net::OpenSSH support for TCP forwarding if tunnels are allowed on the proxy).
Expect + Net::OpenSSH may be a better option.

OpenID authentication to Google Apps via Perl and Net::OpenID::Consumer fails

I asked this over on Google's support forums for Apps integration, but got zero response. Maybe somebody here can help steer me in the right direction.
I'm trying to integrate a Perl application with Google Apps, and I'm having some trouble with the OpenID authentication. I've been using this PHP tutorial from Google as a kind of reference, since there are no Perl examples I can find.
My initial file, index.cgi (referred by manifest.xml, and the starting point of the OpenID transaction) is as follows:
use Net::OpenID::Consumer;
use CGI;
# ...
my $q = CGI->new();
my $domain = $q->param('domain');
if (!$domain) {
print $q->header(), 'Provide domain please.';
exit 0;
}
# my website
my $root = 'http://www.example.com/';
my $csr = Net::OpenID::Consumer->new(
# The user agent which sends the openid off to the server
ua => LWP::UserAgent->new,
# Who we are
required_root => $root,
# Consumer Key Secret from Google Apps Marketplace
consumer_secret => 'Zzzzzz9zzAAAAA....'
);
my $claimed_id = $csr->claimed_identity(
'https://www.google.com/accounts/o8/site-xrds?hd=' . $domain);
if ($claimed_id) {
my $check_url = $claimed_id->check_url(
# Upon validation, the user will be returned here, and real
# work may begin
return_to => $root . '/return.cgi',
trust_root => $root
);
print $q->redirect($check_url);
}
else {
print $q->header(), "Error";
}
This part seems to be working. That is, I get redirected to return.cgi with a bunch of openid.* parameters. However, at this point I get the following error:
no_identity_server The provided URL doesn't declare its OpenID identity server
I'm using the latest version of the Net::OpenID::Consumer module.
Here are the significant bits of return.cgi:
my $q = CGI->new();
my $csr = Net::OpenID::Consumer->new(
ua => LWP::UserAgent->new,
# The root of our URL
required_root => 'http://www.example.com/',
# Our password.
consumer_secret => 'Zzzzzz9zzAAAAA....',
# Where to get the information from.
args => $q
);
print $q->header();
$csr->handle_server_response(
not_openid => sub {
print "That's not an OpenID message. Did you just type in the URL?";
},
setup_required => sub {
my $setup_url = shift;
print 'You need to do something here.';
},
cancelled => sub {
print 'You cancelled your login.';
},
verified => sub {
my $vident = shift;
my $url = $vident->url;
print "You are verified as '$url'. ** FIN **";
},
error => sub { die "Can't figure it out: ", #_; }
);
As you can imagine, I'm wanting the verified sub to fire, but instead I'm getting an error. Anything obvious I'm missing? Any help would be appreciated.
So the solution, it turns out, is to switch modules. I changed to the skimpily documented Net::Google::FederatedLogin, and things are now working. The code is as follows (substitute example.com below for your actual developer's domain).
In your Google Apps Marketplace vendor profile, add the URL to index.cgi in the Application Manifest:
...
<Url>http://www.example.com/index.cgi?from=google&domain=${DOMAIN_NAME}</Url>
...
Then add the following code to your servers.
index.cgi
use CGI;
use Net::Google::FederatedLogin;
my $q = CGI->new();
my $domain = $q->param('domain');
if (!$domain) {
print $q->header(), 'Provide domain please.';
exit 0;
}
my $fl = Net::Google::FederatedLogin->new(
claimed_id =>
'https://www.google.com/accounts/o8/site-xrds?hd=' . $domain,
return_to =>
'http://www.example.com/return.cgi',
extensions => [
{
ns => 'ax',
uri => 'http://openid.net/srv/ax/1.0',
attributes => {
mode => 'fetch_request',
required => 'email',
type => {
email => 'http://axschema.org/contact/email'
}
}
}
] );
print $q->redirect($fl->get_auth_url());
return.cgi
use CGI;
use Net::Google::FederatedLogin;
my $q = CGI->new();
print $q->header();
my $fl = Net::Google::FederatedLogin->new(
cgi => $q,
return_to =>
'http://www.example.com/return.cgi' );
eval { $fl->verify_auth(); };
if ($#) {
print 'Error: ' . $#;
}
else {
# we've authenticated and gotten attributes --
my $ext = $fl->get_extension('http://openid.net/srv/ax/1.0');
print $ext->get_parameter('value.email');
}
(For a full sample, plus OAuth access to user data, see this post on my blog.)
In some cases, reportedly, this is caused by Perl missing Net::SSL, making it fail on Google's SSL URLs.
The step where it's failing is where it performs discovery on the identifier asserted by Google's server. For some reason discovery on that identifier URL is failing. It would be helpful to know what OpenID identifier the Google server is asserting to try to debug why discovery is not working for it.
I'm not sure what $domain is there, but you may need to escape it:
use URI::Escape 'uri_escape';
....
my $claimed_id = $csr->claimed_identity(
'https://www.google.com/accounts/o8/site-xrds?hd=' . uri_escape($domain) );
Also, the consumer_secret used by Net::OpenID::Consumer has no relationship to any other secret.

Perl - How to get the email address from the FROM part of header?

I am trying to set up this script for my local bands newsletter.
Currently, someone sends an email with a request to be added, we manually add it to newsletter mailer I set up.
(Which works great thanks to help I found here!)
The intent now is to have my script below log into the email account I set up for the list on our server, grab the info to add the email automatically.
I know there are a bunch of apps that do this but, I want to learn myself.
I already have the "add to list" working when there is an email address returned from the header(from) below BUT, sometimes the header(from) is a name and not the email address (eg "persons name" is returned from persons name<email#address> but, not the <email#address>.)
Now, I am not set in stone on the below method but, it works famously... to a point.
I read all the docs on these modules and there was nothing I could find to get the darn email in there all the time.
Can someone help me here? Verbose examples are greatly appreciated since I am struggling learning Perl.
#!/usr/bin/perl -w
##########
use CGI;
use Net::IMAP::Simple;
use Email::Simple;
use IO::Socket::SSL; #optional i think if no ssl is needed
use strict;
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
######################################################
# fill in your details here
my $username = '#########';
my $password = '#############';
my $mailhost = '##############';
#######################################################
print CGI::header();
# Connect
my $imap = Net::IMAP::Simple->new($mailhost, port=> 143, use_ssl => 0, ) || die "Unable to connect to IMAP: $Net::IMAP::Simple::errstr\n";
# Log in
if ( !$imap->login( $username, $password ) ) {
print STDERR "Login failed: " . $imap->errstr . "\n";
exit(64);
}
# Look in the INBOX
my $nm = $imap->select('INBOX');
# How many messages are there?
my ($unseen, $recent, $num_messages) = $imap->status();
print "unseen: $unseen, <br />recent: $recent, <br />total: $num_messages<br />\n\n";
## Iterate through unseen messages
for ( my $i = 1 ; $i <= $nm ; $i++ ) {
if ( $imap->seen($i) ) {
my $es = Email::Simple->new( join '', #{ $imap->top($i) } );
printf( "[%03d] %s\n\t%s\n", $i, $es->header('From'), $es->header('Subject'));
print "<br />";
next;
}## in the long version these are pushed into different arrays for experimenting purposes
else {
my $es = Email::Simple->new( join '', #{ $imap->top($i) } );
printf( "[%03d] %s\n\t%s\n", $i, $es->header('From'), $es->header('Subject'));
print "<br />";
}
}
# Disconnect
$imap->quit;
exit;
use Email::Address;
my #addresses = Email::Address->parse('persons name <email#address>');
print $addresses[0]->address;
The parse method returns an array, so the above way works for me.
I'm making this a separate answer because even though this information is hidden in the comments of the accepted answer, it took me all day to figure that out.
First you need to get the From header using something like Email::Simple. THEN you need to extract the address portion with Email::Address.
use Email::Simple;
use Email::Address;
my $email = Email::Simple->new($input);
my $from = $email->header('From');
my #addrs = Email::Address->parse($from);
my $from_address = $addrs[0]->address; # finally, the naked From address.
Those 4 steps in that order.
The final step is made confusing by the fact that Email::Address uses some voodoo where if you print the parts that Email::Address->parse returns, they will look like simple strings, but they are actually objects. For example if you print the result of Email::Address->parse like so,
my #addrs = Email::Address->parse($from);
foreach my $addr (#addrs) { say $addr; }
You will get the complete address as output:
"Some Name" <address#example.com>
This was highly confusing when working on this. Granted, I caused the confusion by printing the results in the first place, but I do that out of habit when debugging.