#!/usr/bin/perl
#
# by Jakub Wartak 17.02.2006
# Licensed under GPLv2
#
# requires root mysql privileges
# ver 0.0.2
# TODO: MySQL-5.x support, functions checks


#########################################################################################

use DBI;
use warnings;
use strict;
use Fcntl ':mode';

# location of my.cnf
my @locini = (
	"/etc/debian/my.cnf",
	"/etc/my.cnf",
	"/var/db/mysql/my.cnf",
	"/var/lib/mysql/my.cnf",
);

my ($db, $mysql);

$| = 1;
print "Enter MySQL admin login [root]: ";
my $sql_login = <STDIN>;
chomp $sql_login if ($sql_login);
$sql_login = "root" if(!$sql_login || $sql_login eq '');

print "Enter MySQL passwd for $sql_login: ";
my $sql_pw = <STDIN>;
chomp $sql_pw if($sql_pw);
$sql_pw = "" if(!$sql_pw || $sql_pw eq '');

print "Enter MySQL host (ip or hostname) [localhost]: ";
my $sql_server = <STDIN>;
chomp $sql_server if($sql_server);
$sql_server = 'localhost' if(!$sql_server || $sql_server eq '');

print "\n";

print "Connecting to $sql_login\@$sql_server...";

$db = DBI->connect("DBI:mysql::$sql_server", $sql_login, $sql_pw) or die $DBI::errstr;
$mysql = DBI->connect("DBI:mysql:mysql:$sql_server", $sql_login, $sql_pw) or die $DBI::errstr;

print "connected!\n\n";
$| = 0;

### DB table checks ###
my $dbtab = $mysql->prepare("SELECT Host, Db, User, Grant_Priv FROM db") or die $mysql->errstr;
$dbtab->execute or die $mysql->errstr;

while ( my ($db_host, $db_db, $db_user, $grant ) = $dbtab->fetchrow_array) {
	my $login = $db_user . '\@' . $db_host;
	print "WARNING: $login has GRANT defined on $db_db !!\n" if ($grant eq 'Y');
}
$dbtab->finish;

### USER table checks ###
my $user = $mysql->prepare("SELECT Host, User, Password, Grant_Priv, Reload_Priv, Shutdown_Priv, Process_Priv, Super_Priv FROM user") or die $mysql->errstr;
$user->execute or die $mysql->errstr;

while ( my ($db_host, $db_user, $db_pw, $grant, $reload, $shutdown, $process, $super ) = $user->fetchrow_array) {
	my $login = $db_user . '@' . $db_host;
	print "WARNING: ANONYMOUS login $login\n" if $db_user eq '';
	print "WARNING: REMOTE ROOT $login !!\n" if ($db_user eq 'root' && $db_host ne 'localhost');
	print "WARNING: Empty password for $login !!\n" if ($db_pw eq '');
	print "WARNING: $login can give GRANTs\n" if ($grant eq 'Y' and $db_user ne 'root');
	
	print "NOTICE: $login can reload/shutdown MySQL\n" if ($reload eq 'Y' || $shutdown eq 'Y');
	print "NOTICE: $login can use 'SHOW PROCESSLIST'\n" if ($process eq 'Y');
	print "NOTICE: $login is an administrative account ( has Super privilege )\n" if ($super eq 'Y');
}
$user->finish;

my $vars = $mysql->prepare("SHOW VARIABLES") or die $mysql->errstr;
$vars->execute or die $mysql->errstr;
while ( my ($name, $value) = $vars->fetchrow_array) {
	if($name eq 'socket') {
		my $sock = $value;
		# TODO ( perms )
	}

	if($name eq 'local_infile' && $value eq 'OFF') {
		print "WARNING: You could disable LOCAL INFILE in my.cnf to improve security\n";	
	}
	
	if($name eq 'skip_show_database' && $value eq 'NO') {
		print "NOTICE: You could enable 'skip_show_database' in my.cnf to improve security\n";
	}
	
	if($name eq 'old_passwords' && $value eq 'YES') {
		print "NOTICE: Old format (MySQL-4.0) of passwords is used\n";
	}
	
	if($name eq 'pid_file' && -f $value) {
		# TODO: check for world writeable
		print "WARNING: Pid file ($value) is readable by others\n" if(&getperm($value) & S_IROTH);
		print "WARNING: Pid file ($value) is writeable by others\n" if(&getperm($value) & S_IWOTH);

	}
	
	# should be 0770 
	if($name eq 'datadir' && -d $value) {
		print "WARNING: Datadir ($value) is readable by others\n" if(&getperm($value) & S_IROTH);
		print "WARNING: Datadir ($value) is writeable by others\n" if(&getperm($value) & S_IWOTH);
		print "WARNING: Datadir ($value) can be accessed by others\n" if(&getperm($value) & S_IXOTH);
	}
	
	if($name eq 'character_sets_dir' && -d $value ) {
		# TODO
	}
	
	# IMHO: this dir should be separated from /tmp
	if($name eq 'tmpdir' && -d $value ) {
		print "WARNING: Tmpdir for MySQL ($value) is readable by others\n" if(&getperm($value) & S_IROTH);
		print "WARNING: Tmpdir for MySQL ($value) is writeable by others\n" if(&getperm($value) & S_IWOTH);
		print "WARNING: Tmpdir for MySQL ($value) can be accessed by others\n" if(&getperm($value) & S_IXOTH);
	}
	
}

# db will be used in future to check/enumerate non-'mysql' specific databases
# eg. 'is accesible' => don't have broken InnoDB/ARCHIVE tables etc.

$db->disconnect();
$mysql->disconnect();

exit 0;

sub getperm {
	my $d = shift;
	return (stat($d))[2];
}
