mirror of
https://github.com/webmin/webmin.git
synced 2025-07-25 01:23:45 +00:00
364 lines
8.7 KiB
Perl
364 lines
8.7 KiB
Perl
# postgresql-lib.pl
|
|
# Common PostgreSQL functions
|
|
|
|
do '../web-lib.pl';
|
|
&init_config();
|
|
if ($config{'plib'}) {
|
|
$ENV{$gconfig{'ld_env'}} .= ':' if ($ENV{$gconfig{'ld_env'}});
|
|
$ENV{$gconfig{'ld_env'}} .= $config{'plib'};
|
|
}
|
|
if ($config{'psql'} =~ /^(.*)\/bin\/psql$/ && $1 ne '' && $1 ne '/usr') {
|
|
$ENV{$gconfig{'ld_env'}} .= ':' if ($ENV{$gconfig{'ld_env'}});
|
|
$ENV{$gconfig{'ld_env'}} .= "$1/lib";
|
|
}
|
|
|
|
%access = &get_module_acl();
|
|
|
|
if (!$config{'nodbi'}) {
|
|
# Check if we have DBD::Pg
|
|
eval <<EOF;
|
|
use DBI;
|
|
\$driver_handle = DBI->install_driver("Pg");
|
|
EOF
|
|
}
|
|
|
|
# is_postgresql_running()
|
|
# Returns 1 if yes, 0 if no, -1 if the login is invalid, -2 if there
|
|
# is a library problem
|
|
sub is_postgresql_running
|
|
{
|
|
local $temp = &tempname();
|
|
local $host = $config{'host'} ? "-h $config{'host'}" : "";
|
|
local $cmd;
|
|
if ($config{'login'}) {
|
|
open(TEMP, ">$temp");
|
|
print TEMP "$config{'login'}\n$config{'pass'}\n";
|
|
close(TEMP);
|
|
local $out;
|
|
$cmd = "$config{'psql'} -u -c '' $host $config{'basedb'} <$temp";
|
|
}
|
|
else {
|
|
$cmd = "$config{'psql'} -c '' $host $config{'basedb'}";
|
|
}
|
|
if ($config{'unix'} && getpwnam($config{'unix'})) {
|
|
$cmd = "su $config{'unix'} -c \"$cmd\"";
|
|
}
|
|
open(OUT, "$cmd 2>&1 |");
|
|
while(<OUT>) { $out .= $_; }
|
|
close(OUT);
|
|
unlink($temp);
|
|
if ($out =~ /setuserid:/i || $out =~ /no\s+password\s+supplied/i ||
|
|
$out =~ /no\s+postgres\s+username/i || $out =~ /authentication\s+failed/i ||
|
|
$out =~ /password:.*password:/i || $out =~ /database.*does.*not/i) {
|
|
return -1;
|
|
}
|
|
elsif ($out =~ /connect.*failed/i || $out =~ /could not connect to server:/) {
|
|
return 0;
|
|
}
|
|
elsif ($out =~ /lib\S+\.so/i) {
|
|
return -2;
|
|
}
|
|
else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
# get_postgresql_version()
|
|
sub get_postgresql_version
|
|
{
|
|
local $v = &execute_sql($config{'basedb'}, 'select version()');
|
|
$v = $v->{'data'}->[0]->[0];
|
|
if ($v =~ /postgresql\s+([0-9\.]+)/i) {
|
|
return $1;
|
|
}
|
|
else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
# list_databases()
|
|
# Returns a list of all databases
|
|
sub list_databases
|
|
{
|
|
local $t = &execute_sql($config{'basedb'}, 'select * from pg_database order by datname');
|
|
return map { $_->[0] } @{$t->{'data'}};
|
|
}
|
|
|
|
# list_tables(database)
|
|
# Returns a list of tables in some database
|
|
sub list_tables
|
|
{
|
|
#local $t = &execute_sql($_[0], 'select relname from pg_class where relkind = \'r\' and relname not like \'pg_%\' and relhasrules = \'f\' order by relname');
|
|
local $t = &execute_sql($_[0], 'select tablename from pg_tables where tablename not like \'pg_%\' order by tablename');
|
|
return map { $_->[0] } @{$t->{'data'}};
|
|
}
|
|
|
|
# list_types()
|
|
# Returns a list of all available field types
|
|
sub list_types
|
|
{
|
|
local $t = &execute_sql($config{'basedb'}, 'select typname from pg_type where typrelid = 0 and typname !~ \'^_.*\' order by typname');
|
|
return map { $_->[0] } @{$t->{'data'}};
|
|
}
|
|
|
|
# table_structure(database, table)
|
|
# Returns a list of hashes detailing the structure of a table
|
|
sub table_structure
|
|
{
|
|
local $t = &execute_sql($_[0], "select a.attnum, a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef FROM pg_class c, pg_attribute a, pg_type t WHERE c.relname = '$_[1]' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid order by attnum");
|
|
local (@rv, $r);
|
|
foreach $r (@{$t->{'data'}}) {
|
|
local $arr;
|
|
$arr++ if ($r->[2] =~ s/^_//);
|
|
local $sz = $r->[4] - 4;
|
|
if ($sz >= 65536) {
|
|
$sz = int($sz/65536).",".($sz%65536);
|
|
}
|
|
push(@rv, { 'field' => $r->[1],
|
|
'arr' => $arr ? 'YES' : 'NO',
|
|
'type' => $r->[4] < 0 ? $r->[2]
|
|
: $r->[2]."($sz)",
|
|
'null' => $r->[5] =~ /f|0/ ? 'YES' : 'NO' } );
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# execute_sql(database, sql)
|
|
sub execute_sql
|
|
{
|
|
if ($driver_handle && !$config{'unix'} &&
|
|
$_[1] !~ /^(create|drop)\s+database/ && $_[1] !~ /^\\/) {
|
|
# Use the DBI interface
|
|
local $cstr = "dbname=$_[0]";
|
|
$cstr .= ";host=$config{'host'}" if ($config{'host'});
|
|
local $dbh = $driver_handle->connect($cstr,
|
|
$config{'login'}, $config{'pass'});
|
|
$dbh || &error("DBI connect failed : ",$DBI::errstr);
|
|
$dbh->{'AutoCommit'} = 0;
|
|
local $cmd = $dbh->prepare($_[1]);
|
|
if (!$cmd->execute()) {
|
|
&error(&text('esql', "<tt>".&html_escape($_[1])."</tt>",
|
|
"<tt>".&html_escape($dbh->errstr)."</tt>"));
|
|
}
|
|
local (@data, @row);
|
|
local @titles = @{$cmd->{'NAME'}};
|
|
while(@row = $cmd->fetchrow()) {
|
|
push(@data, [ @row ]);
|
|
}
|
|
$cmd->finish();
|
|
$dbh->commit();
|
|
$dbh->disconnect();
|
|
return { 'titles' => \@titles,
|
|
'data' => \@data };
|
|
}
|
|
else {
|
|
# Call the psql program
|
|
local $temp = &tempname();
|
|
open(TEMP, ">$temp");
|
|
print TEMP "$config{'login'}\n$config{'pass'}\n";
|
|
close(TEMP);
|
|
local $host = $config{'host'} ? "-h $config{'host'}" : "";
|
|
local $cmd = "$config{'psql'} -u -c \"$_[1]\" $host $_[0] <$temp";
|
|
if ($config{'unix'}) {
|
|
$cmd =~ s/"/\\"/g;
|
|
$cmd = "su $config{'unix'} -c \"$cmd\"";
|
|
}
|
|
open(OUT, "$cmd <$temp 2>&1 |");
|
|
local ($line, $rv, @data);
|
|
do {
|
|
$line = <OUT>;
|
|
} while($line =~ /^(username|password|user name):/i ||
|
|
$line =~ /(warning|notice):/i || $line !~ /\S/);
|
|
if ($line =~ /^ERROR:\s+(.*)/ || $line =~ /FATAL.*:\s+(.*)/) {
|
|
&error(&text('esql', "<tt>$_[1]</tt>", "<tt>$1</tt>"));
|
|
}
|
|
else {
|
|
local $dash = <OUT>;
|
|
if ($dash =~ /^\s*\+\-/) {
|
|
# mysql-style output
|
|
$line = <OUT>;
|
|
$line =~ s/^[\s\|]+//; $line =~ s/[\s\|]+$//;
|
|
local @titles = split(/\|/, $line);
|
|
map { s/^\s+//; s/\s+$// } @titles;
|
|
$line = <OUT>; # skip useless dashes
|
|
while(1) {
|
|
$line = <OUT>;
|
|
last if (!$line || $line =~ /^\s*\+/);
|
|
$line =~ s/^[\s\|]+//; $line =~ s/[\s\|]+$//;
|
|
local @row = split(/\|/, $line);
|
|
map { s/^\s+//; s/\s+$// } @row;
|
|
push(@data, \@row);
|
|
}
|
|
$rv = { 'titles' => \@titles, 'data' => \@data };
|
|
}
|
|
elsif ($dash !~ /^-/) {
|
|
# no output, such as from an insert
|
|
$rv = undef;
|
|
}
|
|
else {
|
|
# psql-style output
|
|
local @titles = split(/\|/, $line);
|
|
map { s/^\s+//; s/\s+$// } @titles;
|
|
while(1) {
|
|
$line = <OUT>;
|
|
last if (!$line || $line =~ /^\(\d+\s+\S+\)/);
|
|
local @row = split(/ \| /, $line);
|
|
map { s/^\s+//; s/\s+$// } @row;
|
|
push(@data, \@row);
|
|
}
|
|
$rv = { 'titles' => \@titles, 'data' => \@data };
|
|
}
|
|
}
|
|
close(OUT);
|
|
unlink($temp);
|
|
return $rv;
|
|
}
|
|
}
|
|
|
|
# execute_sql_logged(database, command)
|
|
sub execute_sql_logged
|
|
{
|
|
&additional_log('sql', $_[0], $_[1]);
|
|
return &execute_sql(@_);
|
|
}
|
|
|
|
# run_as_postgres(command)
|
|
sub run_as_postgres
|
|
{
|
|
pipe(OUTr, OUTw);
|
|
local $pid = fork();
|
|
if (!$pid) {
|
|
untie(*STDIN);
|
|
untie(*STDOUT);
|
|
untie(*STDERR);
|
|
close(STDIN);
|
|
open(STDOUT, ">&OUTw");
|
|
open(STDERR, ">&OUTw");
|
|
|
|
local @u = getpwnam($config{'user'});
|
|
$( = $u[3]; $) = "$u[3] $u[3]";
|
|
($>, $<) = ($u[2], $u[2]);
|
|
|
|
exec(@_);
|
|
print "Exec failed : $!\n";
|
|
exit 1;
|
|
}
|
|
close(OUTw);
|
|
return OUTr;
|
|
}
|
|
|
|
sub can_edit_db
|
|
{
|
|
local $d;
|
|
return 1 if ($access{'dbs'} eq '*');
|
|
foreach $d (split(/\s+/, $access{'dbs'})) {
|
|
return 1 if ($d eq $_[0]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# get_hba_config()
|
|
# Parses the postgres host access config file
|
|
sub get_hba_config
|
|
{
|
|
local $lnum = 0;
|
|
open(HBA, $config{'hba_conf'});
|
|
while(<HBA>) {
|
|
s/\r|\n//g;
|
|
s/^\s*#.*$//g;
|
|
if (/^\s*host\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)(\s+(\S+))?/) {
|
|
push(@rv, { 'type' => 'host',
|
|
'index' => scalar(@rv),
|
|
'line' => $lnum,
|
|
'db' => $1,
|
|
'address' => $2,
|
|
'netmask' => $3,
|
|
'auth' => $4,
|
|
'arg' => $6 } );
|
|
}
|
|
elsif (/^\s*local\s+(\S+)\s+(\S+)(\s+(\S+))?/) {
|
|
push(@rv, { 'type' => 'local',
|
|
'index' => scalar(@rv),
|
|
'line' => $lnum,
|
|
'db' => $1,
|
|
'auth' => $2,
|
|
'arg' => $4 } );
|
|
}
|
|
$lnum++;
|
|
}
|
|
close(HBA);
|
|
return @rv;
|
|
}
|
|
|
|
# create_hba(&hba)
|
|
sub create_hba
|
|
{
|
|
local $lref = &read_file_lines($config{'hba_conf'});
|
|
push(@$lref, &hba_line($_[0]));
|
|
&flush_file_lines();
|
|
}
|
|
|
|
# delete_hba(&hba)
|
|
sub delete_hba
|
|
{
|
|
local $lref = &read_file_lines($config{'hba_conf'});
|
|
splice(@$lref, $_[0]->{'line'}, 1);
|
|
&flush_file_lines();
|
|
}
|
|
|
|
# modify_hba(&hba)
|
|
sub modify_hba
|
|
{
|
|
local $lref = &read_file_lines($config{'hba_conf'});
|
|
splice(@$lref, $_[0]->{'line'}, 1, &hba_line($_[0]));
|
|
&flush_file_lines();
|
|
}
|
|
|
|
# swap_hba(&hba1, &hba2)
|
|
sub swap_hba
|
|
{
|
|
local $lref = &read_file_lines($config{'hba_conf'});
|
|
local $line0 = $lref->[$_[0]->{'line'}];
|
|
local $line1 = $lref->[$_[1]->{'line'}];
|
|
$lref->[$_[1]->{'line'}] = $line0;
|
|
$lref->[$_[0]->{'line'}] = $line1;
|
|
&flush_file_lines();
|
|
}
|
|
|
|
sub hba_line
|
|
{
|
|
if ($_[0]->{'type'} eq 'host') {
|
|
return join(" ", 'host', $_[0]->{'db'}, $_[0]->{'address'},
|
|
$_[0]->{'netmask'}, $_[0]->{'auth'},
|
|
$_[0]->{'arg'} ? ( $_[0]->{'arg'} ) : () );
|
|
}
|
|
else {
|
|
return join(" ", 'local', $_[0]->{'db'}, $_[0]->{'auth'},
|
|
$_[0]->{'arg'} ? ( $_[0]->{'arg'} ) : () );
|
|
}
|
|
}
|
|
|
|
# split_array(value)
|
|
sub split_array
|
|
{
|
|
if ($_[0] =~ /^\{(.*)\}$/) {
|
|
local @a = split(/,/, $1);
|
|
return @a;
|
|
}
|
|
else {
|
|
return ( $_[0] );
|
|
}
|
|
}
|
|
|
|
# join_array(values ..)
|
|
sub join_array
|
|
{
|
|
local $alpha;
|
|
map { $alpha++ if (!/^-?[0-9\.]+/) } @_;
|
|
return $alpha ? '{'.join(',', map { "'$_'" } @_).'}'
|
|
: '{'.join(',', @_).'}';
|
|
}
|
|
|
|
1;
|
|
|