Error handling with invalid MAC entry with Perl Net::MAC module - perl

I'm attempting write a Perl script that asks the user to enter a MAC address. I'm using the Net::MAC module to convert whatever format MAC address the user enters to a standard format for me to use later in the script. I've got most of it working but I can't seem to figure out how to handle a situation in which they enter an invalid MAC. Something that couldn't possibly be a MAC address. Such as an entry that includes letters that aren't A-F.
I'm thinking something like the following should work but when it dies it just flat out kills the entire script rather than re-asks the user to enter the MAC again.
use Net::MAC;
my $proceed = "no";
while ($proceed eq "no"){
print "Enter the MAC address: ";
my $mac;
$mac = <>;
chomp($mac);
my $tempMac = Net::MAC->new('mac' => $mac, 'die' => 0);
if ($tempMac->die() eq "0"){
print "Looks like you entered an invalid MAC address. Please try again.\n";
} else {
my $newMac = $tempMac->convert('base' => 16,'bit_group' => 8,'delimeter' => ":");
$proceed = "yes";
}
}
Should this instead use something like a Try, Catch statement? I think in other words, I need to know how to appropriately handle the die() event without having the script completely bail on me.

See https://mvp.kablamo.org/essentials/die-eval for some info on how exceptions work in Perl and how to handle them. Consider Syntax::Keyword::Try for a familiar try/catch paradigm.
use Syntax::Keyword::Try;
my $newMac;
try {
my $tempMac = Net::MAC->new('mac' => $mac);
$newMac = $tempMac->convert('base' => 16,'bit_group' => 8,'delimeter' => ":");
$proceed = "yes";
} catch {
print "Looks like you entered an invalid MAC address. Error: $# Please try again.\n";
}
You can also validate the mac address before passing it to Net::MAC using something like Regexp::Common.
use Regexp::Common 'net';
my $newMac;
if ($mac =~ m/$RE{net}{MAC}/) {
my $tempMac = Net::MAC->new('mac' => $mac);
$newMac = $tempMac->convert('base' => 16,'bit_group' => 8,'delimeter' => ":");
$proceed = "yes";
} else {
print "Looks like you entered an invalid MAC address. Please try again.\n";
}

Related

Error: ' Can't use string ("") as a HASH ref while "strict refs" ' in combination with google recaptcha v2. Works on local machine but not on webhost

I am very new to Perl and was just assigned the quick task to change the reCAPTCHA to reCAPTCHA v2.
On my local machine this works fine, however when I push the new version of the login form to the webhost, the form does not seem to work. I get the following error:
AH01215: Can't use string ("") as a HASH ref while "strict refs" in use at login_new.cgi line 68, line 1.: [........]
The code snippet is based on the documentation I found here: https://metacpan.org/pod/Captcha::reCAPTCHA::V2
And according to the error log my error is somewhere in here (line 68):
if ($submit) {
my $response = $cgi->param('g-recaptcha-response');
my $result = $captcha->verify($captcha_private_key, $response );
if ($result->{success}) # This is line 68
{....}
This is quite confusing to me, especially since it is working on my local machine. Could you please help? Best regards and thank you in advance!
The Verify Function has a bug. I just looked at the implementation. in case that "$res" isn't 'successful' it wont give you back the wanted hash reference.
As a quick workaround I would check if your my $result is equal to "" if thats the case the verify function failed.
In the long run you should probably submit that bug to the creator of the module.
disclaimer: I havent downloaded the module and just looked at the source without actually trying my workaround so take it with a grain of salt and try it in a safe environment first
sub verify {
my ($self, $secret, $response, $remoteip) = #_;
# ... more code here, removed for better readability
my $res = $self->{ua}->post_form(
$self->{verify_api},
$params
);
if ($res->{success}) {
my $content = decode_json $res->{content};
if ($content->{success}){
return { success => 1 };
} else {
return { success => 0, error_codes => $content->{'error-codes'} };
}
}
}

Using Number::Phone to validate and format

I'm trying to use Number::Phone from CPAN to accomplish 2 tasks:
Validate a Phone Number; and
Format the number in E.164 Notation.
However, I'm unable to figure out how it works. My sample code is:
#!/usr/bin/perl -w
use strict;
use warnings;
use Number::Phone;
foreach my $fnum ( '17888888', '97338888888', '00923455555333', '+97366767777' , '38383838') {
my $phone = Number::Phone->new($fnum);
my $norm = "";
eval {
$norm = $phone->format_using('E123'); # or 'Raw'
print "E164 => '$norm'\n";
} or do {
print STDERR "Unable to parse '$fnum'\n";
}
}
Expected output:
E164 => '+97317888888'
E164 => '+97338888888'
E164 => '+923455555333'
E164 => '+97366767777'
E164 => '+97338383838'
But the results were incorrect. I tried using Number::Phone::Normalize, but still not successful:
#!/usr/bin/perl -w
use strict;
use warnings;
use Number::Phone::Normalize;
my %params = (
'CountryCode'=>'973',
'IntlPrefix' =>'00',
'CountryCodeOut'=>'973',
'IntlPrefixOut' => '+',
);
my $nlz = Number::Phone::Normalize->new( %params );
foreach my $number ('17888888', '97338888888', '00923455555333', '+97366767777' , '38383838') {
my $e164 = $nlz->intl( $number );
print "E164 => '$e164'\n";
}
with the same expected output of:
E164 => '+97317888888'
E164 => '+97338888888'
E164 => '+923455555333'
E164 => '+97366767777'
E164 => '+97338383838'
However, this produced the wrong results too. The snippet Java code below works perfectly, and it's what I'm trying to achieve in Perl.
// Uses libphonenumber: http://code.google.com/p/libphonenumber/
// setenv CLASSPATH .:libphonenumber-8.5.2.jar
// libphonenumber
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
public class ValidateList {
public static void main(String[] args) {
try {
if (args.length != 1) {
throw new IllegalArgumentException("Invalid number of arguments.");
}
String file = args[0];
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
try (java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(file))) {
String line = null;
while ((line = br.readLine()) != null) {
try {
PhoneNumber phoneNumber = phoneUtil.parse(line, "BH");
boolean isValid = phoneUtil.isValidNumber(phoneNumber);
if (isValid) {
System.out.println( "E164 => " + phoneUtil.format(phoneNumber, PhoneNumberFormat.E164) );
}
else {
System.err.println( "Invalid => " + line);
}
}
catch (NumberParseException e) {
System.err.println("NumberParseException for ("+line+"): " + e.toString());
}
}
}
}
catch (Exception e) {
System.err.println(e);
System.err.println("Usage: java ValidateList <fileNameWithPhoneNumbers>");
}
}
}
% cat input.txt
17888888
97338888888
00923455555333
+97366767777
38383838
% javac -cp libphonenumber-8.5.2.jar ValidateList.java
% java -cp .:libphonenumber-8.5.2.jar ValidateList input.txt
E164 => +97317888888
E164 => +97338888888
E164 => +923455555333
E164 => +97366767777
E164 => +97338383838
Your input is greatly appreciated.
When I run the first example code for the numbers, two of those fail to be parsed:
17888888 - this is obvious, when calling Number::Phone without a country code, this will not be parsed as it's unclear what country this is from
00923455555333 - 923 is, according to a quick google search, the country code for Pakistan. The Wikipedia page for dialing codes in Pakistan shows no 455, leading me to think that this is not a known area code to either Number::Phone or Wikipedia. I suspect it is an invalid number.
So for the first Number: specify which country this is supposed to be from.
If you are certain the other number is correct, you know more about that than the developer of Number::Phone currently and I'm sure he'd be happy to receive your input in the form of a more complete Number::Phone localized package.
The fact that your Java code accepts the (probably) invalid number does not necessarily mean it is more correct, just that it is less picky in what it declares to be a correct number.
Edit:
Asking Phone::Number to parse the input '+923455555333' instead of '00923455555333' leads to the desired output.
Looking at the source of Phone::Number:
# ... processing input arguments
$number = "+$number" unless($number =~ /^\+/);
It becomes clear that the 00 is interpreted as '+00' and then rejected as being an invalid number.
View some discussion on that here
It seems to me you will have to handle this yourself.
One way may be to simply replace leading 00 with '+' - preferably only if parsing failed.
The other number can be parsed if you make it clear what country it should belong to.
Perhaps like so:
my $phone = Number::Phone->new($fnum);
unless ($phone){
$phone = Number::Phone->new('BH',$fnum);
if ( !$phone && $fnum =~ s/^00/+/ ){
# You should probably check the discussion I linked.
# There may well be problems with this approach!
$phone = Number::Phone->new($fnum);
}
}

Can't call method "verify" on an undefined value

• I am working to migrate a Linux server to a newer one from Ubuntu 10.04 to 12.04
• This server is responsible for executing several a number of Perl modules via crontabs.
• These Perl Modules rely heavily on 30-40 perl extensions.
• I have installed all Perl extensions and the crontabs are able to process successfully except for several Syntax errors caused by the newer versions of these PERL extensions.
• I need some help with modifying the syntax to get the Perl script to process as intended.
This is my error message:
2015/12/28 12:56:48 ./cms.pl 88 FATAL main - Can't call method "verify" on an undefined value at pm/Emails/Core.pm line 438.
Code:
#===================================================================================================
# Send an eamil
# Args: enable_clients?, BCC Arrayref [admin1#a.com, ...], Hashref { email_address, email_subject, email_body }
#===================================================================================================
sub pm::Emails::Core::send_email {
my ($self, $enable_clients, $bcc, $email) = #_;
# die('Invalid BCC array') unless $bcc;
die('Invalid Email hashref') unless ($email && $email->{email_address} && $email->{email_subject} && $email->{email_body});
$email->{email_address} = trim $email->{email_address}; # Trim the email address just to be sure no invalid emails sneak in
my $mime = undef;
my $smtp = undef;
###
# Get a handle to the logger
my $logger = Log::Log4perl->get_logger();
die('Failed to create logger') unless $logger;
###
###
# Send the email using the local SMTP server
# SPAM FILTER NOTES:
# We are sending the email as inlined HTML.
# Sending the email as a multipart with HTML & PlainText is getting flagged as SPAM.
{
my $msg = join(', ',
(
'Time:' . localtime(),
'Sending Email TO: ' . $email->{email_address},
#'BCC: ' . join(',', #$bcc),
'SUBJECT: ' . $email->{email_subject},
'Clients Enabled: ' . ($enable_clients ? 'true' : 'false')
)
);
$logger->warn($msg);
open(FILE, '>>/var/log/mail.log') or die('Failed to open mail log: /var/log/mail.log');
print FILE $msg . "\n";
close FILE;
}
###
if (!defined($self->{_phpversion_})) {
$self->{_phpversion_} = `php -r 'print phpversion();' 2>/dev/null`;
}
###
# Generate the MIME email message
$mime = MIME::Lite->new(
Subject => $email->{email_subject},
To => $email->{email_address},
Type => 'text/html',
Data => $email->{email_body},
'Reply-To' => 'test#test.com',
'Return-Path' => 'test#test.com',
From => 'test#test.com',
Organization => 'Testing',
'X-Mailer' => 'PHP' . $self->{_phpversion_}
);
###
# Check to see if we are sending the email to clients, if not then redirect to another account & update the subject
if ($enable_clients) {
$logger->warn('Sending email to clients is enabled!');
} else {
use Sys::Hostname;
$logger->warn('Sending email to clients is disabled!');
$email->{email_address} = 'test#test.com';
$email->{email_subject} = '<' . hostname . ' - ADMIN ONLY EMAIL> ' . $email->{email_subject};
$mime->replace(Subject => $email->{email_subject});
}
$mime->preamble('');
$mime->top_level(1);
$mime = $mime->as_string();
###
###
# Connect to the SMTP server & send the message
$logger->debug('Connecting to SMPT server');
$smtp = Net::SMTP->new('localhost', Timeout => 60, Debug => 0, Hello => 'test.com');
$logger->debug('Connected to SMPT server');
###
###
# Verify we can send the email to the included addresses
foreach my $email_address (($email->{email_address}), #$bcc) {
$logger->debug('Verifying Email address: ' . $email_address);
next if $smtp->verify($email_address);
$logger->warn('Failed to verify email address: ' . $email_address . ', re-connecting to SMPT');
$smtp = Net::SMTP->new('localhost', Timeout => 60, Debug => 1, Hello => 'test.com');
die('Failed to reconnect to SMPT server') unless $smtp;
last;
}
###
###
# Send the email message
$smtp->mail('test#test.com');
$smtp->bcc(#$bcc, { Notify => ['FAILURE','DELAY', 'SUCCESS'] });
$smtp->to($email->{email_address}, { Notify => ['FAILURE','DELAY', 'SUCCESS'] });
$smtp->data; # This will start the data connection for the message body
$smtp->datasend( $mime ); # This will send the data for the message body
$smtp->dataend; # This will end the message body and send the message to the user
$smtp->quit;
###
use List::Util qw[min];
sleep(min(1, int(rand(2))));
}
Any help on this is greatly appreciated.
You don't create the $smtp object (using $smtp = Net::SMTP->new(...)) until three lines after you try to call the verify() method on it. So of course it's going to be undefined at that point.
The only way that this could ever work is if the $smtp is also created earlier on in code that you haven't shown us. But assuming that you have shown us all mentions of $smtp, then this code can't possibly have worked on the old server only. This is not a problem that is caused by a newer version of Perl, it's a logic error that would never have worked.
The obvious way to fix this is to re-order the code so that the object is created before you try to use it. But as I can only see a small amount of the code, I have no way of knowing whether this would have knock-on effects elsewhere.
Have you considered paying a Perl programmer to help you carry out these migrations? Expecting free consultancy from StackOverflow isn't really a sustainable business model :-/
Update: Ok, so now you've added more code, we can see that the $smtp is initialised a few lines before the call to verify. So why are you getting the error?
If you read the documentation for Net::SMTP, in the section describing the new() method, it says:
On failure undef will be returned and $# will contain the reason for
the failure.
It looks like this is what is happening. But your code isn't checking the return code from the new() and is assuming that it will always work - which is a pretty strange assumption to make. To fine out what is going wrong, you'll need to add some debugging output to the two lines that create your SMTP object. Where you have:
$smtp = Net::SMTP->new(...);
Change it to:
$smtp = Net::SMTP->new(...)
or die $#;
That way, if you fail to connect to the SMTP server, your program will die with a (hopefully) useful error message which will enable you to investigate further.
Incidentally, I don't know where your code comes from, but no-one really recommends Net::SMTP these days. It's all rather low-level. You would be better off looking at Email::Sender or Email::Stuffer (that's the kind of useful knowledge that a Perl programmer would bring to this project..
Hey Guys just Wanted to follow up on this problem. I tried all of your suggestions and was unable to get a solution.
However more in-depth research of the SMTP/Mail running on this machine revealed that it was running Postfix, it turns out this script was written for SendMail. Simply did the following:
Uninstall Postfix-
sudo apt-get purge postfix
Install Sendmail-
sudo apt-get install sendmail
All was resolved, thank you guys for all your help.

if condition using telnet in perl not working

I'm trying to use if condition to check if command has passed, but its not working. Even though the mount has been successful it goes to failed message. When i enter this command, it retruns to the prompt without any message, hence i'm comparing with a "". And when i do a "ls" of destination folder, it shows all contents of source folder. Any help? Is my if condition correct?
my $port = new Net::Telnet->new(Host=>$ip,Port=>$ip_port,Timeout => "$timeout", Dump_Log => "dumplog.log", Errmode=> "return" );
if($port->cmd("mount -t nfs -o nolock <path-of-source-folder> <destination-folder>") eq "")
{
print "Successful\n";
}
else{
print "Failed.\n ";
}
In scalar context, the Net::Telnet cmd method returns 1 on success (not a string). Your check should be something like:
if ($port->cmd("mount -t nfs -o nolock <path-of-source-folder> <destination-folder>") == 1)
{
print "Successful\n";
} else {
print "Failed.\n";
}
If you actually want to collect the output from the mount command and inspect it, you will have to either call it in list context or pass a stringref argument, like so:
my #outlines = $port->cmd("mount ...");
Or:
my $out;
my $ret = $port->cmd("mount ...", [Output => \$out]);
if ($ret == 1)
{
# inspect $out
}
See the Net::Telnet documentation for more.
Your check for the result seems to be wrong. The documenation of Net::Telnet says that
This method sends the command $string, and reads the characters sent back by the command up until and including the matching prompt. It's assumed that the program to which you're sending is some kind of command prompting interpreter such as a shell.
The command $string is automatically appended with the output_record_separator, by default it is "\n". This is similar to someone typing a command and hitting the return key. Set the output_record_separator to change this behavior.
In a scalar context, the characters read from the remote side are discarded and 1 is returned on success.
So you need to check in a scalar context
if ($port->cmd("..") ) {
...
}

Perl Net::SNMP returns noSuchName when snmpwalk works

I am new to perl, but I am trying to write a plug-in for nagios. I have a simple get request that fails, but if I try the same request with snmpwalk it works.
My code is:
#!/usr/bin/perl -w
use strict;
use Net::SNMP;
my $host = '10.10.10.203';
my $community = 'myComm';
my $session;
my $error;
my $response = undef;
($session, $error) = Net::SNMP->session(
-hostname => $host,
-version => 2,
-community =>$community,
-port => 161,
-timeout => 20
);
my $uptimeOID = '1.3.6.1.2.1.1.3.0';
my $myOID = '1.3.6.1.4.1.7933';
if( !defined( $response = $session->get_request($myOID)))
{
if( $session->error_status == 2)
{
my $sessionError = $session->error;
print ("($sessionError) OID not supported ($myOID).\n");
}
}
else
{
print ("$response");
}
If I run this script it will fail saying noSuchName, but if a run:
snmpwalk -v 2c -c myComm 10.10.10.203 1.3.6.1.4.1.7933
I get the response I want. Does anybody know why this wont work?
If I check the uptime OID with this script it will work the way it should.
You've already identified that via the command-line you're doing a "walk" rather than a "get". If there's a specific value you want to "get" in your script, put in the full OID identifying the target.
There's something in a table record that you probably want to get at (and it seems like everything in FASTTRAKIDERAID-MIB is in fact tabular), so a simple get isn't enough. Look at the snmpwalk.pl script that comes with Net::SNMP or see if SNMP::Util can easily provide the functionality you're looking for.
Use get_next_request, not get_request. It will return the first valid oid after the one you pass it.
I found my problem. When I use snmpwalk, it will grab the whole tree and return a value. The perl module will not. It doesn't traverse the tree to the end even thought there is only one thing below it, it just says no.