Get IPv6 prefix using perl - perl

I am trying to get the IPv6 Prefix from a given IPv6 Address given the prefix length.
I have already tried several modules (like Net::IP, Net::IPv6Addr, and Net::IPv6Address) but none seems to do exactly that.
Actually only Net::IPv6Address gives such a method but the result is not in IPv6 format.
For example:
The IPv6: FE80:0202:B3FF:FE1E::/56 should give a prefix of FE80:0202:B3FF:FE00:: but when I run this code:
use Net::IPv6Address;
my $IPv6Address = new Net::IPv6Address("FE80:0202:B3FF:FE1E::", 56);
my $prefix = $IPv6Address->prefix();
print $prefix;
I get 'fe800202b3fffe' (Not the proper IPv6 format)...
I did the same in Java using the com.googlecode.ipv6.IPv6Address and com.googlecode.ipv6.IPv6NetworkMask libraries and it worked perfectly.
Any ideas on how to do this?
Thanks to everyone in advance!

There are many modules that can do this. I would suggest Net::Works or NetAddr::IP. With Net::Works:
use v5.16;
use Net::Works::Network;
my $network = Net::Works::Network->new_from_string(
string => 'FE80:0202:B3FF:FE1E::/56' );
say $network->first;
This will output fe80:202:b3ff:fe00::.

Related

Exim getting random credential in exim.conf

I have been trying to get perl subroutine value and substitution to get the required part of string from randomips subroutine in exim.conf. However when i use string substitution i get error as follow:
Here is what I am trying to achieve
I am trying to split string by colon and get first occurrence as "interface". I'll be using second occurrence as the "helo_data.
exim.pl
sub randomhosts {
#inet = ("x.x.x.1:hostname1.domain.com","x.x.x.2:hostname2.domain.com","x.x.x.3:hostname3.domain.com"
);
return $inet[int rand($#inet+1)];
}
exim.conf
dkim_remote_smtp:
driver = smtp
interface = "${perl{randomhosts}%:*}"
helo_data = "${sender_address_domain}"
Error I get is as follow:
"failed to expand "interface" option for dkim_remote_smtp transport: missing '}' after 'perl'".
Probably the syntax.
Any help?
The code that you are trying to copy was written by someone who doesn't know much about Perl. It includes this line:
return $inet[int rand($#inet+1)];
A Perl programmer would write this as
return $inet[rand #inet];
I think there are a couple of issues here - one with your Exim syntax and one with your Perl syntax.
Exim is giving you this error:
failed to expand "interface" option for dkim_remote_smtp transport: missing '}' after 'perl'
I don't know anything about calling Perl from Exim, but this page mentions a syntax like ${perl{foo}} (which is similar to the one used in the page you are copying from) and one like ${perl{foo}{argument}} for calling a subroutine and passing it an argument. Nowhere does it mention syntax like yours:
${perl{randomhosts}%:*}
I'm not sure where you have got that syntax from, but it seems likely that this is what is causing your first error.
In a comment, you say
I am stying to get first part of string before colon for each random array value for "interface" and part after colon for "helo_data"
It seems to me that Exim doesn't support this requirement. You would need to call the function twice to get the two pieces of information that you require. You might be able to do this in the Perl using something like state variables - but it would be far more complex than the code you currently have there.
Secondly, your Perl code has a syntax error, so even if Exim was able to call your code, it wouldn't work.
The code you're copying sets up #inet like this:
#inet = ("x.x.x.1", "x.x.x.2", "x.x.x.3", "x.x.x.4");
Your equivalent code is this:
#inet = (
"x.x.x.1:hostname1.domain.com",
"x.x.x.2:hostname2.domain.com,
x.x.x.3:hostname3.domain.com
);
I've reformatted it, to make the problems more obvious. You are missing a number of quote marks around the elements of the array. (Note: I see that while I have been writing this answer, you have fixed that.)
Update: Ok, here is some code to put into exim.pl that does what you want.
use feature qw[state];
sub randomhosts {
state $current;
my #inet = (
"x.x.x.1:hostname1.domain.com",
"x.x.x.2:hostname2.domain.com",
"x.x.x.3:hostname3.domain.com"
);
if ($_[0] eq 'generate') {
shift;
#{$current}{qw[ip host]} = split /:/, $inet[rand #inet];
}
return $current->{$_[0]};
}
It generates a new ip/host pair if its first argument is 'generate'. It will then return either the hostname or the ip address from the generated pair. I think you can probably call it from your Exim config file like this:
dkim_remote_smtp:
driver = smtp
interface = "${perl{randomhosts}{generate}{ip}}"
helo_data = "${perl{randomhosts}{host}}"
But I'm no expert in Exim, so that syntax might need tweaking.
First I would like to note I have not worked with exim so I cannot say what exactly you are trying to do and why you have done things exactly so.
In the link you posted, a method called 'randinet' is added to exim.pl and the interface line in exim.conf is replaced by
interface = "${perl{randinet}}"
You have implemented a 'randomhosts' method and replaced the interface line with
interface = "${perl{randomhosts}%:*}"
Now the parser complains about not finding the closing bracket. That is likely due to the symbols you felt free to add but the parser does not have the freedom to ignore.
I suggest you try
interface = "${perl{randomhosts}}"

Loop over a range of IPs with perl IP library

I'm trying to create an IP table list where I can see which IPs are in use and which aren't.
I use the Net::IP library for this. Code snippet:
my #IPsinrange = &getAllIPs($range) ; #range is x.x.x.x/subnet format
sub getAllIPs {
my $ip = new Net::IP ($range) || die;
my #IPs ;
# Loop
do {
push #IPs, $ip->ip() ;
} while (++$ip);
return #IPs ;
}
This works for a x.x.x.0/24 network and this works for 1.2.3.4/32 but when I use 1.2.3.4/29 for instance, the loop just dies. I thought it could be because of the fact that there were no IP adresses in use, but there are 4 IPs in that range that are alive.
The reason this isn't working is due to the fact that the module requires you to send in a proper network address as the starting point of a given IP prefix.
Since you want to use a /29 prefix, valid ranges would be:
1.2.3.0/29
1.2.3.8/29
1.2.3.16/29
...etc
As mentioned in the comments, the documentation states that the proper usage of the constructor to get proper diagnostic output is:
$ip = Net::IP->new('1.2.3.0/29') or die (Net::IP::Error());
I suggest that you use the Net::CIDR function library instead. The function Net::CIDR::cidr2octets does exactly what you need, and doesn't insist that the base address for a range is the network address
Here's an example that uses your test data 1.2.3.4/29 for the range
use strict;
use warnings 'all';
use feature 'say';
use Net::CIDR;
my #range = Net::CIDR::cidr2octets('1.2.3.4/29');
say for #range;
output
1.2.3.0
1.2.3.1
1.2.3.2
1.2.3.3
1.2.3.4
1.2.3.5
1.2.3.6
1.2.3.7
If you want to "normalize" a CIDR block that may not use the network address as the prefix, you can use Net::CIDR::cidr2range followed by Net::CIDR::range2cidr. Given '1.2.3.4/29', the first returns 1.2.3.0-1.2.3.7, and when that result is passed into Net::CIDR::range2cidr we get a normalised result
Like so
Net::CIDR::range2cidr(Net::CIDR::cidr2range('1.2.3.4/29'));
output
1.2.3.0/29

Translation rule

I like to create a translation rule to a VoIP system to obtain the following result:
If someone dials the 4545 the system must convert this to 1234545, I managed to do this with the following rule: s/^4545/1234545/
My problem now is if someone dials 454567 my rule will convert this to 123454567 and I want to get 1234545
thx
Not clear on why should 454567 become 1234545? Should a string with a run of 4545 anywhere in it be come 1234545?
If you just want to change the exact string 4545 to 1234545 then you can use s/^4545$/1234545/.
If you want a string with a run of 4545 anywhere in it to become 1234545 then you can use s/.*4545.*/1234545.
$number='1234545' if ($number eq '4545'); #eq because phone number can contain non-digits.
If you want to convert any number that starts with '4545', use this code:
s/^4545.*/1234545/

How do I create an absolute URL from two components, in Perl?

Suppose I have:
my $a = "http://site.com";
my $part = "index.html";
my $full = join($a,$part);
print $full;
>> http://site.com/index.html
What do I have to use as join, in order to get my snippet to work?
EDIT: I'm looking for something more general. What if a ends with a slash, and part starts with one? I'm sure in some module, someone has this covered.
I believe what you're looking for is URI::Split, e.g.:
use URI::Split qw(uri_join);
$uri = uri_join('http', 'site.com', 'index.html')
use URI;
URI->new("index.html")->abs("http://site.com")
will produce
"http://site.com/index.html"
URI->abs will take care of merging the paths properly following your uri specification,
so
URI->new("/bah")->abs("http://site.com/bar")
will produce
"http://site.com/bah"
and
URI->new("index.html")->abs("http://site.com/barf")
will produce
"http://site.com/barf/index.html"
and
URI->new("../uplevel/foo")->abs("http://site.com/foo/bar/barf")
will produce
"http://site.com/foo/uplevel/foo"
alternatively, there's a shortcut sub in URI namespace that I just noticed:
URI->new_abs($url, $base_url)
so
URI->new_abs("index.html", "http://site.com")
will produce
"http://site.com/index.html"
and so on.
No need for ‘join‘, just use string interpolation.
my $a = "http://site.com";
my $part = "index.html";
my $full = "$a/$part";
print $full;
>> http://site.com/index.html
Update:
Not everything requires a module. CPAN is wonderful, but restraint is needed.
The simple approach above works very well if you have clean inputs. If you need to handle unevenly formatted strings, you will need to normalize them somehow. Using a library in the URI namespace that meets your needs is probably the best course of action if you need to handle user input. If the variance is minor File::Spec or a little manual clean-up may be good enough for your needs.
my $a = 'http://site.com';
my #paths = qw( /foo/bar foo //foo/bar );
# bad paths don't work:
print join "\n", "Bad URIs:", map "$a/$_", #paths;
my #cleaned = map s:^/+::, #paths;
print join "\n", "Cleaned URIs:", map "$a/$_", #paths;
When you have to handle bad stuff like $path = /./foo/.././foo/../foo/bar; is when you want definitely want to use a library. Of course, this could be sorted out using File::Spec's cannonical path function.
If you are worried about bad/bizarre stuff in the URI rather than just path issues (usernames, passwords, bizarre protocol specifiers) or URL encoding of strings, then using a URI library is really important, and is indisputably not overkill.
You might want to take a look at this, an implementation of a function similar to Python's urljoin, but in Perl:
http://sveinbjorn.org/urljoin_function_implemented_using_Perl
As I am used to Java java.net.URL methods, I was looking for a similar way to concatenate URI without any assumption about scheme, host or port (in my case, it is for possibly complex Subversion URL):
http://site.com/page/index.html
+ images/background.jpg
=> http://site.com/page/images/background.jpg
Here is the way to do it in Perl:
use URI;
my $base = URI->new("http://site.com/page/index.html");
my $result = URI->new_abs("images/background.jpg", $base);

How do I reset my LDAP password from Perl?

My company, like everyone else's, requires password resets from time to time. This is all good and well for security's sake, but I'd like to explore the challenge of resetting it through a script (notably because we can't use our previous 25 passwords; Perl is much quicker about cycling through a list two-dozen deep than my fingers are).
I'm trying to use Perl and Win32::OLE's LDAP connectors to reset my password. I've followed a couple of examples online, and have, briefly:
use strict;
use Win32::OLE;
my $dn = 'cn=name,dc=corp,dc=com';
my $ldap = Win32::OLE->GetObject('LDAP:');
my $ldap_user = $ldap->OpenDSObject('LDAP://' . $dn,'username','password',1);
$ldap_user->SetPassword('mySw337NewPassword');
And all I get for my troubles is:
Win32::OLE(0.1707) error 0x80070005: "Access is denied"
in METHOD/PROPERTYGET "SetPassword" at pw.change.pl line 8
Is this something that can be worked around? I've located the Net::LDAP::Extension::SetPassword module, but no dice there.
Thanks!
Update for Leon (Max, you're next):
You're correct, I should have specified better. I tried Win32::OLE, failed, then separately tried Net::LDAP::Extension::SetPassword and failed even harder.
As for my server: I'm not certain, I'm not the LDAP guy :) By running ->root_dse->get_value('supportedExtension') I can see that the setPassword OID is not set, so maybe it's just not meant to be.
Final props to barneyton!
Final solution:
use strict;
use Win32::OLE;
my $orig_password = 'password123Test';
my $target_password = 'password321Test';
my $dn = 'cn=myname,dc=corp,dc=com';
my $ldap = Win32::OLE->GetObject('LDAP:');
my $ldap_user = $ldap->OpenDSObject('LDAP://'.$dn,'myname',$orig_password,1);
my $tmp_password = '';
for ( my $i = 0; $i < 30; ++$i )
{
$tmp_password = 'password' . $i . 'ABC';
print 'Changing to ' . $tmp_password . "\n";
$ldap_user->ChangePassword($orig_password,$tmp_password);
$orig_password = $tmp_password;
sleep 1;
}
$ldap_user->ChangePassword($tmp_password,$target_password);
When you said you were trying to "reset" your password, I think you really meant change password rather than set password. There is a difference between the two. "SetPassword" requires god/admin privilege since you are setting a user's password to a new value regardless of whether the old password is known, while "ChangePassword" requires the user to actually know the old password. I'm assuming your account does not have admin privilege, else you would not have gotten 0x80070005: "Access is denied"
So instead of:
$ldap_user->SetPassword('mySw337NewPassword');
try this:
$ldap_user->ChangePassword('password', 'mySw337NewPassword');
By the way, I've never done this stuff in perl, so I'm just guessing. Hope this helps you out.
Net::LDAP::Extension::SetPassword doesn't have anything to do with any OLE LDAP object. Either you use Net::LDAP, or you use Win32::OLE->GetObject('LDAP:').
You didn't mention what server you're using. Setting passwords requires an extension to LDAP, so that is relevant.
Another thing to keep in mind is Active Directory does not let you set passwords unless you bind to port 636 using LDAPS.
You could try to write the value to userPassword which would be a password reset, and you might not have rights to do that.
Otherwise you could try in one operation (LDIF would show it as separated by a single dash on a line) remove the value of the old password and then add the value of the new password. That would be a password change event.