ngcpcfg/sbin/ngcp-sync-constants

369 lines
10 KiB

#!/usr/bin/perl -w
#----------------------------------------------------------------------
# Syncronizes passwords from constants.yml with MySQL
#----------------------------------------------------------------------
use strict;
use DBI;
use YAML::Tiny;
use Getopt::Long;
use Sys::Hostname::Long;
use Data::Dumper;
use IPC::System::Simple qw(system);
#----------------------------------------------------------------------
use constant CONSTANTS_YML => "/etc/ngcp-config/constants.yml";
use constant MYSQL_CREDENTIALS => "/etc/mysql/sipwise.cnf";
use constant MYSQL_DATA => {
voisniff => { dbuser => 'dbpassword' },
cleanuptools => { dbuser => 'dbpassword' },
rsyslog => { dbuser => 'dbpassword' },
sems => { dbuser => 'dbpassword',
prepaid_dbuser => 'prepaid_dbpassword' },
rateomat => { accountingdb => { user => 'pass' } },
faxserver => { hylafax => { db_user => 'db_pass' } },
cdrexport => { dbuser => 'dbpassword' },
checktools => { dbuser => 'dbpassword' },
mysql => { repuser => 'reppassword' },
kamailio => { proxy => { dbrwuser => 'dbrwpw',
dbrouser => 'dbropw' } },
mediator => { dbuser => 'dbpassword' },
asterisk => { odbc => { dbuser => 'dbpassword' } },
mysql => { repuser => 'reppassword' },
ossbss => { provisioning => {
billingdb => { user => 'pass' } } },
prosody => { dbuser => 'dbpassword' },
};
use constant COPY_PASSWORDS => [ # pairs of from/to
{ rateomat => { accountingdb => { user => 'pass' }}},
{ rateomat => { billingdb => { user => 'pass' }}},
{ kamailio => { proxy => { dbrwuser => 'dbrwpw' }}},
{ kamailio => { lb => { dbrwuser => 'dbrwpw' }}},
{ kamailio => { proxy => { dbrouser => 'dbropw' }}},
{ kamailio => { lb => { dbrouser => 'dbropw' }}},
{ ossbss => { provisioning => { billingdb => { user => 'pass' }}}},
{ ossbss => { provisioning => { database => { user => 'pass' }}}},
{ ossbss => { provisioning => { billingdb => { user => 'pass' }}}},
{ ossbss => { provisioning => { openserdb => { user => 'pass' }}}},
{ ossbss => { provisioning => { billingdb => { user => 'pass' }}}},
{ reminder => { dbuser => 'dbpassword' }}
];
sub Usage {
print <<USAGE;
==
Syncronizes passwords from constants.yml with MySQL
==
$0 [options]
Options:
-help|-h|-? -- this help
-root|-r -- use mysql root user without password as DB credentials
-init-passwords|-i -- generate new passwords (constants.yml is updated)
-test|-t -- test mode (no updates)
-verbose|-v -- verbose mode
USAGE
exit 0;
}
my $dbh;
my $mysql_db = 'mysql';
my $mysql_host = 'localhost';
my $mysql_port = 3360;
my $mysql_user = 'sipwise';
my $mysql_pass = '';
my $mysql_root;
my $yml = {};
my $yml_ref = {};
my $password_length = 20;
my $init_passwords = 0;
my $debug = 0;
my $test_mode = 0;
GetOptions("h|?|help" => \&Usage,
"i|init-passwords" => \$init_passwords,
"r|root" => \$mysql_root,
"t|test" => \$test_mode,
"v|verbose" => \$debug);
#----------------------------------------------------------------------
sub pwgen {
my @list = ("a".."z",0..9,"A".."Z");
my @randoms;
for (1..$password_length) {
push @randoms, $list[int(rand($#list))];
}
return join "", @randoms;
}
sub get_mysql_credentials {
if(defined $mysql_root) {
$mysql_user='root';
return;
}
open(my $fh, "<", MYSQL_CREDENTIALS)
or die "Can't open ".MYSQL_CREDENTIALS.": ".$!;
($mysql_pass = <$fh>) =~ s/^.+='(.+?)'\s*$/$1/;
close $fh;
}
sub connect_db {
get_mysql_credentials();
$dbh = DBI->connect("DBI:mysql:database=$mysql_db;
host=$mysql_host;
port=$mysql_port",
$mysql_user, $mysql_pass,
{ PrintError => 0, AutoCommit => 0 })
or die "Can't connect to MySQL database $mysql_db: ". $DBI::errstr;
}
sub sync_mysql_data {
my $sth_sel = $dbh->prepare(<<SQL);
SELECT count(*) from user
WHERE User = ?
SQL
my $sth_upd = $dbh->prepare(<<SQL);
UPDATE user
SET Password=PASSWORD(?)
WHERE User = ?
SQL
my $sth_master = $dbh->prepare(<<SQL);
CHANGE MASTER TO
MASTER_HOST=?,
MASTER_USER=?,
MASTER_PASSWORD=?,
MASTER_CONNECT_RETRY=10
SQL
foreach my $key (keys %{MYSQL_DATA()}) {
$yml_ref = $yml->[0]->{$key};
print $key." => " if $debug;
my $opts = { init_passwords => $key eq "mysql" ? 0 : $init_passwords };
my $data = get_user_pass(MYSQL_DATA->{$key}, $opts);
foreach my $pair (@$data) {
my $user = $pair->{'user'};
my $pass = $pair->{'pass'};
next unless ($user && $pass);
# special handling for getting replication set up or in sync
if($user eq 'replicator') {
my $minfo = '/var/lib/mysql/master.info';
my $mhost = hostname_long();
if($mhost eq 'sp1') {
$mhost = 'sp2';
} elsif($mhost eq 'sp2') {
$mhost = 'sp1';
} else {
print " --> skipping '$user' for unknown hostname '$mhost'\n" if $debug;
next;
}
print " --> syncing replication user '$user' for master host '$mhost'\n" if $debug;
if(-f $minfo) {
my $mfh;
unless(open $mfh, '<', $minfo) {
print STDERR "Failed to open $minfo for syncing, replication not synced!\n";
next;
}
my @minfo_content = <$mfh>;
close $mfh;
unless(grep {/^${pass}\s*$/} @minfo_content) {
print " ---> update master info to master_host='$mhost', master_user='$user', master_password='$pass'\n" if $debug;
$dbh->do('SLAVE STOP') unless $test_mode;
$sth_master->execute($mhost, $user, $pass) unless $test_mode;
$dbh->do('SLAVE START') unless $test_mode;
} else {
print " --> replication password in master.info already in sync for '$user' at master '$mhost'\n" if $debug;
}
} else {
print " ---> initialize master info to master_host='$mhost', master_user='$user', master_password='$pass'\n";
$sth_master->execute($mhost, $user, $pass) unless $test_mode;
$dbh->do('SLAVE START') unless $test_mode;
}
}
$sth_sel->execute($user);
(my $count) = $sth_sel->fetchrow_array();
if ($count) {
print " ---> updating $user => $pass\n" if $debug;
$sth_upd->execute($pass, $user) unless $test_mode;
}
}
}
$sth_sel->finish;
$sth_upd->finish;
return if $test_mode;
$dbh->do('FLUSH PRIVILEGES')
or die "Can't flush MySQL privileges: ". $DBI::errstr;
}
sub get_user_pass {
my $h_ref = shift || "No data passed to fetch_mysql_data";
my $opts = shift;
my @data;
foreach my $ref (keys %$h_ref) {
if (ref($ref) eq 'HASH') {
return get_user_pass($ref, $opts);
} elsif (ref($h_ref->{$ref}) eq 'HASH') {
print $ref." => " if $debug;
$yml_ref = $yml_ref->{$ref};
return get_user_pass($h_ref->{$ref}, $opts);
} else {
print " ".$ref." -- ".$h_ref->{$ref} if $debug;
$opts->{'init_passwords'} and $yml_ref->{$h_ref->{$ref}} = pwgen();
my %pair;
$pair{'user'} = $yml_ref->{$ref};
$pair{'pass'} = $yml_ref->{$h_ref->{$ref}};
$pair{'user_key'} = $ref;
$pair{'pass_key'} = $h_ref->{$ref};
push @data, \%pair;
}
}
print "\n" if $debug;
return \@data;
}
sub copy_passwords {
print "Copying internal passwords\n" if $debug;
my $saved_init_passwords = $init_passwords;
$init_passwords = 0;
my $pairs_count = $#{+COPY_PASSWORDS};
for (my $idx=0;$idx<$pairs_count+1;$idx++) {
next if $idx % 2;
die "Incorrect from/to pair" if $idx+1 >= $pairs_count+1;
$yml_ref = $yml->[0];
print "from => " if $debug;
my $from_data = get_user_pass(COPY_PASSWORDS->[$idx]);
die "No 'from' user/pass data available" if $#$from_data == -1;
$yml_ref = $yml->[0];
print "to => " if $debug;
my $to_data = get_user_pass(COPY_PASSWORDS->[$idx+1]);
die "No 'from' user/pass data available" if $#$to_data == -1;
my $user = $from_data->[0]{'user'};
my $pass = $from_data->[0]{'pass'};
my $user_key = $to_data->[0]{'user_key'};
my $pass_key = $to_data->[0]{'pass_key'};
if ($user && $pass && $user_key && $pass_key) {
print " ---> updating $user => $pass\n" if $debug;
$yml_ref->{$user_key} = $user;
$yml_ref->{$pass_key} = $pass;
}
}
$init_passwords = $saved_init_passwords;
}
sub main {
$yml = new YAML::Tiny;
$yml = YAML::Tiny->read(CONSTANTS_YML)
or die "Can't read constants file: $!\n";
if ($init_passwords and not $test_mode and not -w CONSTANTS_YML) {
die CONSTANTS_YML . " is not writable";
}
print "[TEST MODE]\n" if $test_mode;
system("/usr/share/ngcp-ngcpcfg/helper/check-for-mysql");
connect_db();
eval {
$dbh->begin_work;
print "Syncing ".CONSTANTS_YML." -> MySQL ... ";
print "\n" if $debug;
sync_mysql_data();
};
if ($@) {
$dbh->rollback;
die "\nError during syncronization: " . $@;
} else {
$test_mode ? $dbh->rollback : $dbh->commit;
}
$dbh->disconnect if $dbh;
print "Done.\n";
return unless $init_passwords;
copy_passwords();
return if $test_mode;
print "Writing new passwords into ".CONSTANTS_YML." ... ";
$yml->write(CONSTANTS_YML);
print "Done\n";
# print Data::Dumper->Dumpxs([$yml]),"\n";
}
#----------------------------------------------------------------------
main();
exit 0;
__END__
=pod
=head1 NAME
ngcp-sync-constants - syncronizes passwords from constants.yml with MySQL
=head1 SYNOPSIS
ngcp-sync-constants [ options ... ]
=over 8
=item B<--root>
Use mysql root user without password as DB credentials
=item B<--init-passwords>
New passwords are generated (passwords for "mysql" is not generated to avoid replication problems)
=item B<--test>
No real updates, only for checks
=item B<--verbose>
Verbose mode where all changes are written to STDOUT
=back
=head1 DESCRIPTION
B<This program> reads constants.yml file, parses it and syncronizes all required passwords with MySQL
=head1 EXIT STATUS
=over 8
=item B<exit code 0>
Everything is ok
=item B<exit code != 0>
Something is wrong, an error message raises
=back
=head1 INCOMPATIBILITIES
No known at this time.
=head1 BUGS AND LIMITATIONS
Please report problems you notice to the Sipwise Development Team <support@sipwise.com>.
=head1 AUTHOR
Kirill Solomko <ksolomko@sipwise.com>
=head1 LICENSE AND COPYRIGHT
GPL-3+, Sipwise GmbH, Austria
=cut