Perl - DBI and .pgpass - perl

I can successfully create a connection to a Postgres db using the following:
my $settings = {
host => 'myhost',
db => 'mydb',
user => 'myuser',
passwd => 'mypasswd'
};
my $connection = DBI->connect(
'DBI:Pg:dbname=' . $settings->{'db'} . ';host=' . $settings->{'host'},
$settings->{'user'},
$settings->{'passwd'},
{
RaiseError => 1,
ShowErrorStatement => 0,
AutoCommit => 0
}
) or die DBI->errstr;
But I'm left with valuable login credentials exposed (yes, I changed them) in my Perl module. Currently, I use psql to issue queries interactively. And to save on having to remember my username/password, I have placed the credentials in a file (~/.pgpass) with permissions 600. The file looks like this:
# host:port:database:user:passwd
myhost:5432:mydb:myuser:mypasswd
How can I safely use this file ("$ENV{HOME}/.pgpass") and the DBI module to hide my credentials? Can it be done? What is best practice?

YES! There IS a better way.
Change between test & live servers easily.
keep passwords in ~/.pgpass (for psql & pg_dump)
other config info in ~/.pg_service.conf (or /etc/pg_service.conf)
e.g:
#!/usr/bin/perl -T
use strict;
use warnings;
use DBI;
my $dbh = DBI->connect
(
#"dbi:Pg:service=live",
"dbi:Pg:service=test",
undef,
undef,
{
AutoCommit => 0,
RaiseError => 1,
PrintError => 0
}
) or die DBI->errstr;
~/.pg_service.conf:
# http://www.postgresql.org/docs/9.2/static/libpq-pgservice.html
# /usr/local/share/postgresql/pg_service.conf.sample
# http://search.cpan.org/dist/DBD-Pg/Pg.pm
#
[test]
dbname=hotapp_test
user=hotusr_test
# localhost, no TCP nonsense needed:
host=/tmp
[live]
dbname=hotapp_live
user=hotusr_live
host=pgsql-server.example.org
~/.pgpass:
# http://www.postgresql.org/docs/9.2/static/libpq-pgpass.html
# hostname:port:database:username:password
localhost:5432:hotapp_test:hotusr_test:kq[O2Px7=g1
pgsql-server.example.org:5432:hotapp_live:hotusr_live:Unm£a7D(H

Put your login credentials in a file called ~/.pgpass as per the question above.
To open a connection, you'll need to hard-code in the host, database and username. But that's ok, because at least you don't need to code in the password field. This field stays hidden in your ~/.pgpass file.
Make sure to set the connection instance's password field to undef.
Here's what worked for me:
my $settings = {
host => 'myhost',
db => 'mydb',
user => 'myuser'
};
my $connection = DBI->connect(
'DBI:Pg:dbname=' . $settings->{'db'} . ';host=' . $settings->{'host'},
$settings->{'user'},
undef,
{
RaiseError => 1,
ShowErrorStatement => 0,
AutoCommit => 0
}
) or die DBI->errstr;
The connections establishes successfully because for some reason, unknown to me at least, the instance searches the ~/.pgpass file when attempting the connection. I knew there was some magic with this file, I was just unsure about what to do with it. Doc link:
http://search.cpan.org/dist/DBI/DBI.pm#data_string_diff
Notice how a search for "pgpass" on that page does not return? And I refuse to read all of it. Well, one day maybe..

open(my $fh, '<', "$ENV{HOME}/.pgpass") or die $!;
my $settings;
while (<>) {
chomp;
next if /^\s*(?:#.*)?\z/s;
#{$settings}{qw( host port database user passwd )} = split /:/;
}
die "No settings" if !$settings;
Any user capable of running the script would still be able to see the creds.

Related

Not able to execute command in remote machine Using Net::SSH::Expect

I wanted to make the remote connection and automate the change password of any user account in that remote machine so I am using Perl Net::SSH:: Expect module. My connection is happening but I'm not able to perform any command on the remote machine. I am new to Perl language so there might be some mistakes in my code. I have tried searching for this but wasn't able to find something related to it.
Initially, I tried using Net:: OpenSSH and Expect module in combination but wasn't able to achieve the use-case.
#!/usr/bin/perl
use strict;
use warnings;
use Net::OpenSSH;
use Expect;
use Net::SSH::Expect;
my $uName= "user";
my $ssh = Net::SSH::Expect->new (
host => "IPAddress",
password=> "userPassword",
user => $uName,
raw_pty => 1
);
# logon to the SSH server using those credentials.
my $login_output = $ssh->login();
if ($login_output !~ /Welcome/) {
die "Login has failed. Login output was $login_output";
}
#Random line display
print "Connected to $uName\n";
#This is where I was trying the change password use case
$ssh->send("passwd") or die "Cannot execute the passwd command: $!\n";
$ssh->waitfor("Changing password for user.\n
(current) UNIX password: ", 3) or die "prompt 'password' not found after 1 second";
$ssh->send("oldPassword\n");
$ssh->waitfor("\n
Enter new UNIX password: " , 2) or die "prompt 'New password:' not found";
$ssh->send("newPassword");
$ssh->waitfor("\n
Retype new UNIX password: ", 1) or die "prompt 'Confirm new password:' not found";
$ssh->send("newPassword");
#$ssh->send("") or die "Cannot spawn: $!\n";
$ssh->close();
This the output i am getting
Cannot execute the passwd command
New Update
I tried executing a simple command just to check whether any command is getting executed or not. It is but still, my change password use case using send(passwd) is not.
#!/usr/bin/perl
use strict;
use warnings;
#use Net::OpenSSH;
#use Expect;
use Net::SSH::Expect;
#
# You can do SSH authentication with user-password or without it.
#
my $uName= "admin1";
# Making an ssh connection with user-password authentication
# 1) construct the object
my $ssh = Net::SSH::Expect->new (
host => "192.168.56.101",
password=> "Passw0rd1",
user => "admin1",
raw_pty => 1
);
# 2) logon to the SSH server using those credentials.
# test the login output to make sure we had success
my $login_output = $ssh->login();
if ($login_output !~ /Welcome/) {
die "Login has failed. Login output was $login_output";
}
print "Connected to $uName\n";
This is the new simple command i am trying to execute
my $who = $ssh->exec("who");
print ($who);
My old use case with die removed from send()
$ssh->send("passwd");
$ssh->waitfor("Changing password for $uName.\n(current) UNIX password: ", 2) or die "prompt 'password' not found after 2 second";
$ssh->send("Passw0rd1\n");
$ssh->waitfor("\nEnter new UNIX password: " , 2) or die "prompt 'New password:' not found";
$ssh->send("qwerty123");
$ssh->waitfor("\nRetype new UNIX password: ", 1) or die "prompt 'Confirm new password:' not found";
$ssh->send("qwerty123");
$ssh->close();
Now the output is coming like this
admin3#admin3-VirtualBox:~/Desktop$ perl NetSSHExpect.pl
Connected to admin1
admin1 :0 2019-07-19 11:49 (:0)
admin1 pts/1 2019-07-19 12:41 (192.168.56.1)
admin1#admin1-VirtualBox:~$ prompt 'password' not found after 2 second at NetSSHExpect.pl line 36.
admin3#admin3-VirtualBox:~/Desktop$
According to the documentation:
void send($string) - sends $string to the SSH server, returns nothing
Sends the string to the SSH server.
So don't check the return value, just remove the or die .. part after the send():
$ssh->send("passwd"); # <-- no error code is returned here..

multiple connection strings to a database with different password

Just trying to connect a database with multiple connection strings with different passwords. The passwords have different privileges. If one password fails, it should try with another one.
The code has been written as below. Though it works fine, how can we refactor code (eval & DBH) to handle multiple connection strings?
my %config = do 'dbconfig.pl';
my $dbh = eval { DBI->connect("dbi:Pg:dbname=".$config{db}.";host=$socket_nm;port=".$config{port}."", $config{user},$config{password},{RaiseError=>1,PrintError=>0}) };
if (!$dbh) {
$dbh = eval { DBI->connect("dbi:Pg:dbname=".$config{db}.";host=$socket_nm;port=".$config{port}."",$config{user},$config{password},{RaiseError=>1,PrintError=>0}) };
}
if ( $# ) {
#Handle Exceptions
}
dbconfig.pl contains :
db => 'db',
port => '5432',
user => 'db_ro',
password => 'Pass01',
password2 => 'Pass02'
You need to use a loop and retry until you get a working connection. In each loop, you need to grab the next set of config values and try to connect with it.
my #configs = (
{
# ...
user => 'user1',
password => 'password1',
},
{
# ...
user => 'user2',
password => 'password2',
},
);
my $dbh;
while ( not $dbh ) {
my $config = shift #configs; # grab the next config
if ( not $config ) {
# we ran out of configs to try
die "Couldn't connect to database";
}
# try the config
$dbh = eval {
DBI->connect(
"dbi:Pg:dbname=" . $config->{db} . ";host=$socket_nm;port=" . $config->{port} . "",
$config->{user}, $config->{password}, { RaiseError => 1, PrintError => 0 } );
};
# we don't need to look at $# here, but if we care we can still do it
}
The configs are now stored in an array #configs. Inside, there are hash references. In your loop we have a lexical $config, which contains the current one we want to try. Note that this also is a hash reference, so the you need to use $config->{...} with the arrow in the dsn.
We loop as long as $dbh is not set. That's the case until the eval inside of the loop returns a working database handle object.
We also need to exit the loop if we run out of configs. Dying seemed like a good idea for that.
If you want, you can handle the errors that eval is catching for you, but for this to work you don't have to do it. If you all you care about is that you get a working connection in the end, this should be sufficient.
Note: Your %config = do 'dbconfig.pl' is horrible. Please use a proper config file format like JSON and a module to read it. I really like Config::ZOMG as it supports lots of different formats and lets you combine multiple files into one config hash. But Config::Simple might be enough for you here.

Resolving issue with Net::OpenSSH and passing multiple commands to a router

I'm working on moving a Perl script that pushed commands to routers. We have turned off telnet, so I'm working on getting SSH to work. After looking at a number of SSH libraries in Perl, I've opted to use Net::OpenSSH. I have no problem logging in and passing commands to the routers, but the problem I'm having is with entering config mode and subsequently passing a command.
The problem is that with each command entered, the underlying system appears to logout then reenter with the next subsequent command. For example with a Juniper router I'm trying to do the following:
edit private
set interfaces xe-1/3/2 description "AVAIL: SOMETHING GOES HERE"
commit
exit
quit
Tailing the syslog from the router I'm seeing something like this...
(...)
UI_LOGIN_EVENT: User 'tools' login, class 'j-remote-user' [65151], ssh-connection 'xxx.xxx.xxx.xxx 42247 xxx.xxx.xxx.xxx 22', client-mode 'cli'
UI_CMDLINE_READ_LINE: User 'tools', command 'edit private '
UI_DBASE_LOGIN_EVENT: User 'tools' entering configuration mode
UI_DBASE_LOGOUT_EVENT: User 'tools' exiting configuration mode
UI_LOGOUT_EVENT: User 'tools' logout
UI_AUTH_EVENT: Authenticated user 'remote' at permission level 'j-remote-user'
UI_LOGIN_EVENT: User 'tools' login, class 'j-remote-user' [65153], ssh-connection 'xxx.xxx.xxx.xxx 42247 xxx.xxx.xxx.xxx 22', client-mode 'cli'
UI_CMDLINE_READ_LINE: User 'tools', command 'set interfaces '
UI_LOGOUT_EVENT: User 'tools' logout
(...)
As you notice I'm getting a LOGOUT_EVENT after each command entered. Of course exiting config mode immediately after entering it causes the set interfaces command to fail as it's no longer in config mode.
The Perl code I'm using is as follows...
#!/usr/bin/perl -w
use strict;
use lib qw(
/usr/local/admin/protect/perl
/usr/local/admin/protect/perl/share/perl/5.10.1
);
use Net::OpenSSH;
my $hostname = "XXXXX";
my $username = "tools";
my $password = "XXXXX";
my $timeout = 60;
my $cmd1 = "edit private";
my $cmd2 = 'set interfaces xe-1/3/2 description "AVAIL: SOMETHING GOES HERE"';
my $cmd3 = "commit";
my $cmd4 = "exit";
my $ssh = Net::OpenSSH->new($hostname, user => $username, password => $password, timeout => $timeout,
master_opts => [-o => "StrictHostKeyChecking=no"]);
$ssh->error and die "Unable to connect to remote host: " . $ssh->error;
my #lines = eval { $ssh->capture($cmd1) };
foreach (#lines) {
print $_;
};
#lines = eval { $ssh->capture($cmd2) };
foreach (#lines) {
print $_;
};
#lines = eval { $ssh->capture($cmd3) };
foreach (#lines) {
print $_;
};
#lines = eval { $ssh->capture($cmd4) };
foreach (#lines) {
print $_;
};
$ssh->system("quit");
The sequence of events is the same as when telnet was used. The only real change was in using SSH objects verses Telnet objects. I'm stumped. Any ideas you could provide would be quite helpful.
[SOLVED, sort of]
The suggestion let Net::Telnet do the driving was the correct one. The following code works...
#!/usr/bin/perl -w
use strict;
use Net::OpenSSH;
use Net::Telnet;
use Data::Dumper;
my $promptEnd = '/\w+[\$\%\#\>]\s{0,1}$/o';
my $cmd1 = "show system uptime | no-more";
my $cmd2 = "show version brief | no-more";
my $hostname = "xxx.xxx";
my $username = "xxxxxxx";
my $password = "xxxxxxx";
my $timeout = 60;
my $ssh = Net::OpenSSH->new(
$hostname,
user => $username,
password => $password,
timeout => $timeout,
master_opts => [ -o => "StrictHostKeyChecking=no" ]
);
$ssh->error and die "Unable to connect to remote host: " . $ssh->error;
my ( $fh, $pid ) = $ssh->open2pty( { stderr_to_stdout => 1 } );
my %params = (
fhopen => $fh,
timeout => $timeout,
errmode => 'return',
);
$conn = Net::Telnet->new(%params);
$conn->waitfor($promptEnd);
#lines = $conn->cmd($cmd1);
foreach (#lines) {
print $_;
}
#lines = $conn->cmd($cmd2);
foreach (#lines) {
print $_;
}
$conn->cmd("quit");
The problem I'm having is that I can't seem to separate the code into subroutines. Once the $conn object is returned from a subroutine, the underlying ssh connection drops. I need to separate this logic in order to not have to rewrite many, many programs and lines of code that relay on this pusher routine. However that problem I'll direct to another question.
[Edit, fully solved]
Just an update in case anyone needs to do something similar.
While the above worked very well when run under a single subroutine, I found that any time I passed the handle to another subroutine, the telnet handle remained open, but the ssh connection dropped.
To solve this I found that if I passed the ssh handle to another subroutine, then later attached the open2pty, and attached Net::Telnet, then I could pass the Net::Telnet handle between subroutines without the underlying ssh connection dropping. This also worked for Net::Telnet::Cisco as well. I have this code working well with Cisco, Juniper, and Brocade routers.
You should also consider adding a few more parameters to the Net::Telnet->new() because it is interacting with ssh rather than a TELNET server.
-telnetmode => 0
-output_record_separator => "\r",
-cmd_remove_mode => 1,
Because there is no TELNET server on remote side, -telnetmode => 0 turns off TELNET negotiation.
The end-of-line is most likely just a carriage-return (i.e. -output_record_separator => "\r") rather than the TCP or TELNET combination of carriage-return linefeed ("\r\n").
Always strip the echoed back input -cmd_remove_mode => 1
There are several possibilities:
Some routers accept having the sequence of commands sent up front via stdin:
my $out = $ssh->capture({stdin_data => join("\r\n", #cmds, '')})
In other cases you will have to use something like Expect to send a command, wait for the prompt to appear again, send another command, etc.
If you were using Net::Telnet before, the Net::OpenSSH docs explain how to integrate both (though I have to admit that combination is not very tested).
Also, some routers provide some way to escape to a full Unix-like shell. I.e., preppending the commands with a bang:
$ssh->capture("!ls");

check cpanel authorisation with perl script and write file

i was working on a perl script that check multi cpanel accounts authorisation for weak passwords , like that a server owner can check if his users are using a weak password and when connected to one write a file in the /public_html/ dir to informe the user .
i was thinking of using cPanel::PublicAPI :
my $cp = cPanel::PublicAPI->new(
'user' => $username,
'pass' => $password,
'host' => $host,
);
but i didn't know how to check if connected and how to write file in it ( i've looked here)
i found a script that check for authorisation :
$authx = encode_base64($user.":".$passwd);
my $sock = IO::Socket::INET->new(Proto => "tcp",PeerAddr => "$host", PeerPort => "$port") || print " [-] Can not connect to the host";
print $sock "GET / HTTP/1.1";
print $sock "Authorization: Basic $authx";
print $sock "Connection: Close";
read $sock, $answer, 128;
close($sock);
if ($answer =~ /Moved/) {
print " passord is : $passwd\n";
}
but this is too slow and can't write file using it .
sorry for my english :) . regards
i didn't know how to check if connected
Just make a request/method call on the $cp object. If it fails, the $cp->{error} attribute is set.
how to write file
use autodie qw(:all);
open my $fh, '>', '/home/user/message_from_the_admin';
$fh->print('You made a boo-boo.');
See chapter 9.3: Files in Modern Perl.

can't connect through socket /opt/lampp/var/mysql/mysql.sock in perl

I am having trouble connecting to a database. Below is the Perl script that doesn't work:
use DBI;
my $dbh = DBI->connect(
'dbi:mysql:orders;mysql_socket:/opt/lampp/var/mysql/mysql.sock',
$username,
$password,
{ RaiseError => 0, AutoCommit => 0 },
)
or die $DBI::errstr;
Normally when I try to open the mysql.sock file using open function there is a error but I can't open it.
You should use '=' for extra parameters in dsn, so be it:
'dbi:mysql:orders;mysql_socket=/opt/lampp/var/mysql/mysql.sock',