#!/usr/local/bin/perl                                                -*-perl-*-
#-----------------------------------------
# mysqlaccess
# ~~~~~~~~~~~
# Copyright (C) 1997 Yves.Carlier@rug.ac.be
#                    University of Ghent, Belgium
#                    Administrative Informatieverwerking
#
# report the access-privileges for a USER from a  HOST to a DB
# by:   Yves.Carlier@rug.ac.be
#
#---------------------------------------------------------------
#
# Release Notes:
#

# 1.1-beta2: (1997-04-11)
# - new options:
#            --password  : validate password for user 
# - bugfix: Fieldnames which should be skipped on the output can now have
#           a first capital letter.
# - bugfix: any option with a '.' (eg ip-number) was interpreted as
#           a wildcard-expression.
# - layout: long messages are wrapped on the report.
#
# 1.1-beta1: (1997-04-xx) [preview]
# - bugfix: When no entry was found in the db-table, the default accessrights are
#           N, instead of the faulty Y in a previous version.
# - new options:
#            --all_users : report access-rights for all possible users
#            --all_dbs   : report access-rights for all possible dbs
#            --all_hosts : report access-rights for all possible hosts
#            --brief     : as brief as possible, don't mention notes,warnings and rules
#            --password  : validate password for user 
# - more descriptive notes and warnings
# - wildcards (*,?) are allowed in the user,host and db options
#            setting xxxx=* is equiv to using option --all_xxxx
#            note: to make sure the shell does not interpret your wildcards,
#                  put them between "'" ;-)
#
# 1.0-beta5: (1997-04-04)
# - bugfix: The table sorting was only being applied to the "user" table; all
#           the tables need to be sorted.  Rewrote the sort algorithm, and
#           the table walk algorithm (no temp file anymore), and various
#           other cleanups.  I believe the access calculation is 100% correct.
#           (by Paul D. Smith <psmith@baynetworks.com>)
# - Allow the debug level to be set on the cmd line with --debug=N.
#           (by Paul D. Smith <psmith@baynetworks.com>)
# - More -w cleanups; should be totally -w-clean.
#           (by Paul D. Smith <psmith@baynetworks.com>)
#
# 1.0-beta4: (1997-03-14)
# - bugfix: in an expression "$i = $j or $k", the '=' binds tighter than the or
#           which results in problems...
#           (by Monty)
# - running mysqlaccess with "perl -w" gives less warnings...   ;-)
#
# 1.0-beta3: (1997-03-14)
# - bugfix: the user-field should not be treated as a sql-regexpr,
#           but as a plain string.
# - bugfix: the host-table should not be used if the host isn't empty in db
#                                         or  if the host isn't emty in user
#           (Monty)
#
# 1.0-beta2: (1997-03-14)
# - bugfix: translation to reg.expr of \_ and \%.
# - bugfix: error in matching regular expression and string given
#           by user which resulted in
#           'test_123' being matched with 'test'
#
# 1.0-beta1: (1997-03-12)
# suggestions by Monty:
# - connect as superuser with superpassword.
# - mysqlaccess could also notice if all tables are empty. This means
#   that all user have full access!
# - It would be nice if one could optionally start mysqlaccess without
#   any options just the arguments 'user db' or 'host user db', where
#   host is 'localhost' if one uses only two arguments.
#
# 0.1-beta4: (1997-03-11)
# - inform the user if he has not enough priv. to read the mysql db
#
# 0.1-beta3: (1997-03-10)
# - more information on the rules which are used.
# - 'localhost' and the name/ip of the local machine are now equiv.
#
# 0.1-beta2: (1997-02-27)
# - complete rewrite of the granting-rules, based on the documentation
#   found in de FAQ.
# - IP-number and name for a host are equiv.
#
# 0.1-beta1: internal
# - first trial.
#---------------------------------------------------------------
# TODO:
# -add some more functionality for DNS.
# -add a parameter 'report' for more reporting/information
#
# suggestions by Monty:
#- Why not use the mysqlperl module this would make the following
#  things much easier:
#  (Actually I would like to use the DBD module, but until the new DBD
#   module is released it's better to stick with mysqlperl. At least this
#   will always work.)
#  In this case you don't have to do any parsing or be dependent on
#  the 'mysql' program.
#  The program would also be a lot faster (no big deal!)
#
#  >>I'd like to see this optional; not everyone will want to install the perl
#  >>stuff, but everyone will want to use this script! <psmith>
#
#  >>>>For the moment I'll stick with the 'mysql' program.  <ycarlier>
# ==================================================================================
#
# From the FAQ: the Grant-algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# The host table is mainly to maintain a list of "secure" servers.
# At TCX hosts contain a list of all machines on local network. These are granted
# all privileges.
# Technically the user grant is calculated by:
#
#    1.First sort all entries by host by putting host without wildcards first,
#      after this host with wildcards and entries with host = ".
#      Under each host sort user by the same criterias.
#    2.Get grant for user from the "db" table.
#    3.If hostname is "empty" for the found entry, AND the privileges with
#      the privileges for the host in "host" table.
#      (Remove all which is not "Y" in both)
#    4.OR (add) the privileges for the user from the "user" table.
#     (add all privileges which is "Y" in "user")
#
#    When matching, use the first found match.
#
# -----------------------------------------------------------------------------------

use Getopt::Long;
use Socket;
use Sys::Hostname;

# ****************************
# debugging flag
# can be set to 0,1,2,3
# a higher value gives more info
# ! this can also be set on the command-line
	$DEBUG   = 0;

# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>8
#  Normaly nothing should be changed benieth this line

# ****************************
# static information...
	$VERSION = "1.1-beta2, 11 Apr 1997";
	$0       =~ /(.*\/)*([a-zA-Z\.]+)$/;
	$script  = $2;

# ****************************
# no caching on STDOUT
	$|=1;

# ****************************
# information on mysql
	$MYSQL     = '/usr/pkg/mysql-3.20.17-beta/bin/mysql'; # path to mysql executable
	$ACCESS_DB = 'mysql';		 # name of DB with grant-tables
	$ACCESS_H  = 'host';		 # 
	$ACCESS_U  = 'user';	       # 
	$ACCESS_D  = 'db';             #
	$CONNECT   = '';               # connect to DB as user with password
 
# ***********************************
# translation-table for poss. answers
%Answer =  ('Y' =>  1 , 'N' =>  0
           , 1  => 'Y',  0  => 'N'
           ,'?' => '?'
           );

# ****************************
# Notes and warnings
%Messages = ( 
  'insecure_user' 
   => "Everybody can access your DB as user '\$user'\n"
     ."WITHOUT supplying a password"
     ."Be very carefull about it!!"
 ,'password_required' 
   => "A password is required for user '\$user'!!"
 ,'invalid_password'
   => "The password '\$password' for user '\$user' is invalid!"
 , 'valid_password'
   => "The password you supplied is valid for user '\$user'!"
 ,'public_database' 
   => "Any user with the appropriate permissions has access to your DB!\n"
     ."Check your users!"
 ,'full_access' 
   => "All grant-tables are empty, which gives full access to ALL users !!"
 ,'no_rule_found'
   => "No matching rule"
 ,'no_equiv_host' 
   => "Not processed: host-field is not empty in db-table."
 ,'least_priv'
   => "If the final priveliges of the user are more then you gave the user,\n"
     ."check the priveliges in the db-table.'\$db'."
 ,'minimum_priv'
   => "The privileges for any new user are AT LEAST\n"
     ."the ones shown in the table above\n"
 );
@Errors   = ();
@Warnings = ();
@Notes    = ();

# ****************************
# information on used rules
$full_access = 1; #if the grant-tables are empty, full access is granted to everyone
%Rules = ( 'db'   => 'no_rule_found'
         , 'host' => 'no_rule_found'
         , 'user' => 'no_rule_found'
         );


# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #
#  mysqlaccess:                                              #
#  ~~~~~~~~~~~                                               #
#  Lets get to it,                                           #
#  and start the program by processing the parameters        #
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #

# ****************************
# the copyright message should
# always be printed (once)
print Header();

# ----------------------------
# Get options from commandline
$Getopt::Long::ignorecase=0; #case sensitive options
if ( grep(/\-\?/,@ARGV) ) { help(); exit 0; }
GetOptions("help" => \$help
          ,"host|h=s" => \$host
          ,"user|u=s" => \$user
          ,"password|p=s" => \$password
          ,"db|d=s"   => \$db
          ,"superuser|U=s" => \$superuser
          ,"spassword|P=s" => \$spassword
          ,"debug=i" => \$DEBUG
          ,"all_dbs"   => \$all_dbs
          ,"all_hosts" => \$all_hosts
          ,"all_users" => \$all_users
          ,"brief"     => \$brief
	  );

# -----------------------------
# catch the options all_xxxxx
$user = '*' if defined($all_users);
$db   = '*' if defined($all_dbs);
$host = '*' if defined($all_hosts);


# -----------------------------
# check for things which aren't
# declared as options:
# 1 argument: can be any of (host,user,db)
if ($#ARGV == 0) {
   print "$script called with 1 argument:\n" if ($DEBUG>2);
   if (!defined($user)) { $user=$ARGV[0]; }
   else {
   if (!defined($db))   { $db  =$ARGV[0]; }
   else {
   if (!defined($host) and defined($user) and defined($db)) { $host=$ARGV[0]; }
   }
   }
}
# 2 arguments: (user,db) -> ('localhost','user','db')
if ($#ARGV == 1) {
   print "$script called with 2 arguments:\n" if ($DEBUG>2);
   if (!defined($host)) { $host='localhost'; }
   if (!defined($user)) { $user=$ARGV[0]; }
   if (!defined($db))   { $db  =$ARGV[1]; }
}
# 3 arguments: (host,user,db)
if ($#ARGV == 2) {
   print "$script called with 3 arguments:\n" if ($DEBUG>2);
   if (!defined($host)) { $host=$ARGV[0]; }
   if (!defined($user)) { $user=$ARGV[1]; }
   if (!defined($db))   { $db  =$ARGV[2]; }
}

# ----------------------
# if no host is given
# assume we mean 'localhost'
if (!defined($host)) { $host='localhost'; }

#-----------------------
# get info/help if necc.
# and exit.
if ( ( !(defined($user) or defined($all_users)) 
       or !(defined($host) or defined($all_hosts))
       or !(defined($db) or defined($all_dbs)) 
     )
     or defined($help) 
   ) {
   help();
   exit 0;
}

# ----------------------------
# get hostname and local-ip
# for localhost
$localhost = LocalHost();
$local_ip  = Name2IP($localhost);
	print "  --localhost=$localhost\n" if ($DEBUG>2);
	print "  --local_ip =$local_ip\n" if ($DEBUG>2);

#---------------------------------
# Inform user if he has not enough
# privileges to read the access-db
if (! HaveReadAccess() ) {
    Print_Error_Access();
    exit 0;
}
else {
    @sorted_db_table  =Sort_table($ACCESS_D, 'host', 'user', 'db');
    @sorted_host_table=Sort_table($ACCESS_H, 'host', 'db');
    @sorted_user_table=Sort_table($ACCESS_U, 'host', 'user');
}

# -----------------------------
# Get record-layout 
%H = %{Show_Fields($ACCESS_H)};
%D = %{Show_Fields($ACCESS_D)};
%U = %{Show_Fields($ACCESS_U)};
@H = @{Show_Fields($ACCESS_H,1)};
@D = @{Show_Fields($ACCESS_D,1)};
@U = @{Show_Fields($ACCESS_U,1)};
@privlist=@{Make_Privlist()};

# -----------------------------
# Build list of users,dbs,hosts
# to process...
@all_dbs   = @{Get_All_dbs($db)};
@all_users = @{Get_All_users($user)};
@all_hosts = @{Get_All_hosts($host)};

# -----------------------------
# Report access-rights for each
# tuple (host,user,db)
foreach $host (@all_hosts) {
  foreach $user (@all_users) {
    foreach $db (@all_dbs) {
      @Errors=();
      @Warnings=();
      @Notes=();
      Report_Access_Rights($host,$user,$db);
    }
  }
}

# -----------------------------
# End script
print Footer();
exit 0;

#############################################################
#  FUNCTIONS  #
###############
# ===========================================================
# sub Get_All_db(template): 
#  return all db the grant-tables are working on,
#  which conform to the template
# ===========================================================
sub Get_All_dbs {
   my ($template) = @_;
   my @db=();
   print "Debug Get_All_dbs:\n" if ($DEBUG>1);

   # get all db for which access-rights can be calculated,
   # which conform to the template.
   # !! these db's don't have to exist yet, so it's not
   #    enough to look which db already exist on the system
   $reg_expr = Wild2Reg($template);
   if ( ! ($template =~ /[\*\?]/) ) {
      push(@db,$template);
      return \@db;
   }

   print "#Reading db-table...\n" if ($DEBUG>1);
   foreach $record (@sorted_db_table) {
    my @record=split(/\t/,$record);
    my $db = $record[1];
    print "> $db " if ($DEBUG>2);
    if ( (!grep(/$db/,@db)) and ($db =~/$reg_expr/) ) {
       push(@db,$db);
       print "added\n" if ($DEBUG>2);
    } 
    else {
       print "skipped\n" if ($DEBUG>2);
    }
   }
   # if no rule is found for a certain db in the db-table,
   # the rights of the user are used, so we should inform
   # the user for 
   push(@db,"any_new_db");
   return \@db;
}

# ===========================================================
# sub Get_All_users(template): 
#  return all users the grant-tables are working on,
#  which conform to the template
# ===========================================================
sub Get_All_users {
   ($template) = @_; # nog verder uitwerken!!!
   my @user=();

   print "Debug Get_All_users:\n" if ($DEBUG>1);
   # get all db for which access-rights can be calculated.
   # !! these db's don't have to exist yet, so it's not
   #    enough to look which db already exist on the system
   $reg_expr = Wild2Reg($template);
   if ( ! ($template =~ /[\*\?]/) ) {
      push(@user,$template);
      return \@user;
   }

   print "#Reading user-table...\n" if ($DEBUG>1);
   foreach $record (@sorted_user_table) {
    my @record=split(/\t/,$record);
    my $user = $record[1];
    print "> $user " if ($DEBUG>2);
    if ( (!grep(/$user/,@user)) and ($user=~/$reg_expr/)) {
       push(@user,$user);
       print "added\n" if ($DEBUG>2);
    } 
    else {
       print "skipped\n" if ($DEBUG>2);
    }
   }
   # Any user means also:
   # - the 'empty' user, ie without supplying a username
   # - any user still to be defined/created
   push(@user,'');               #without_suplying_a_username
   push(@user,"any_new_user");
   push(@Warnings,'minimum_priv');
   return \@user;
}

# ===========================================================
# sub Get_All_hosts(template): 
#  return all hosts the grant-tables are working on,
#  which conform to the template
# ===========================================================
sub Get_All_hosts {
   my ($template) = @_;
   my @host=();

   print "Debug Get_All_hosts:\n" if ($DEBUG>1);
   # get all db for which access-rights can be calculated.
   # !! these db's don't have to exist yet, so it's not
   #    enough to look which db already exist on the system
   $reg_expr = Wild2Reg($template);
   if ( ! ($template =~ /[\*\?]/) ) {
      push(@host,$template);
      return \@host;
   }

   print "#Reading db-table...\n" if ($DEBUG>1);
   foreach $record (@sorted_db_table) {
    my @record=split(/\t/,$record);
    my $host = $record[0];
    print "> $host " if ($DEBUG>2);
    if (! grep(/$host/,@host)) {
       push(@host,$host);
       print "added\n" if ($DEBUG>2);
    } 
    else {
       print "skipped\n" if ($DEBUG>2);
    }
   }
   print "#Reading host-table...\n" if ($DEBUG>1);
   foreach $record (@sorted_host_table) {
    my @record=split(/\t/,$record);
    my $host = $record[0];
    print "> $host " if ($DEBUG>2);
    if ( (!grep(/$host/,@host)) and ($host=~/$reg_expr/)) {
       push(@host,$host);
       print "added\n" if ($DEBUG>2);
    } 
    else {
       print "skipped\n" if ($DEBUG>2);
    }
   }
   # DOUBT:
   print "#Reading user-table...\n" if ($DEBUG>1);
   foreach $record (@sorted_user_table) {
    my @record=split(/\t/,$record);
    my $host = $record[0];
    print "> $host " if ($DEBUG>2);
    if ( (!grep(/$host/,@host)) and ($host=~/$reg_expr/)) {
       push(@host,$host);
       print "added\n" if ($DEBUG>2);
    } 
    else {
       print "skipped\n" if ($DEBUG>2);
    }
   }
   # Any host also means:
   # - any host still to be defined/created
   push(@host,"any_other_host");
   return \@host;
}

# ####################################
# SORTING RECORDS IN TABLES
# ==========================================================
# sub Sort_fields: (rewritten by psmith)
#  Build the query for an ordered list of entries
# ==========================================================
sub Sort_fields {
  my ($start, $end, $sofar, $this, @rest) = (@_);
  my @where = ("((FIELD not like '\\%') AND (FIELD <> ''))",
               "((FIELD like '%\\%%') OR (FIELD like '%\\_%'))",
               "(FIELD = '')");
  my $res = '';

  $this or return ("$start $sofar $end");

  $sofar .= ' AND ' if $sofar;

  foreach $w (@where) {
    my $f = $w;
    $f =~ s/FIELD/$this/g;

    $res .= Sort_fields($start, $end, "$sofar$f", @rest);
  }

  return ($res);
}

# ===========================================================
# sub Sort_table: (rewritten by psmith)
#  return all entries in the given table,
#  in an ordered fashion
# ===========================================================
sub Sort_table {
  my ($tbl, @order) = @_;

  my $start = "SELECT * FROM $tbl WHERE";
  my $end = 'ORDER BY ' . join(',', @order) . ';';

  print "Sorting table $tbl by `@order'\n" if ($DEBUG>1);

  my $res = Sort_fields($start, $end, '', @order);
  print "Query: $res\n" if ($DEBUG>1);

  #my $CONNECT = ConnectAs();
  open(MYSQL, "$MYSQL $ACCESS_DB $CONNECT -s -e \"$res\" |");

  my $delim = <MYSQL>;
  my @res = grep(!/^\Q$delim\E$/, <MYSQL>);
  foreach $rec (@res) { chop($rec); } #remove \n

  close(MYSQL);
  return @res;
}


# ####################################
# FINDING THE RIGHT GRANT-RULE
# ==========================================================
# sub Get_grant_from_db:
# ==========================================================
sub Get_grant_from_db {
  print "Debug Get_grant_from_db: \n" if ($DEBUG>0);
  my %Access_db    = %{Default_Access_Rights()};
  my $process_host_table=0;
  my $rule_found=0;

  foreach $record (@sorted_db_table) {
    $full_access=0;
    print "db: $record" if ($DEBUG>2);
    #chop($record);
    @record=split(/\t/,$record);

    # check host and db
    # with possible wildcards in field
    # replace mysql-wildcards by reg-wildcards
    my $host_tpl = SQL2Reg($record[0]);
    my $db_tpl   = SQL2Reg($record[1]);
    my $user_tpl = $record[2]; #user field isn't pattern matched!!
    print "\thost_tpl : $record[0] => $host_tpl\n" if ($DEBUG>1);
    print "\tdb_tpl   : $record[1] => $db_tpl\n" if ($DEBUG>1);
    print "\tuser_tpl : $record[2] => $user_tpl\n" if ($DEBUG>1);
    if ($DEBUG>2) {
      if ( Is_localhost($host_tpl)
           or  MatchTemplate($host_tpl,$host_name)
           or  MatchTemplate($host_tpl,$host_ip) )
        { print "\t* host  [$host_name,$host_ip] matched with rule [$host_tpl]\n"; }
      else			   { print "\t* host doesn't match\n"; }
      if ( MatchTemplate($db_tpl,$db) )
        { print "\t* db [$db] matched with rule [$db_tpl]\n"; }
      else                       { print "\t* db doesn't match\n";}
      if ( MatchTemplate($user_tpl,$user) )
        { print "\t* user  [$user] matched with rule [$user_tpl]\n"; }
      else                       { print "\t* user doesn't match\n"; }
    }

    if ( ( Is_localhost($host_tpl)
           or  MatchTemplate($host_tpl,$host_name)
           or  MatchTemplate($host_tpl,$host_ip) )
         and ( MatchTemplate($db_tpl,$db) )
         and ( MatchTemplate($user_tpl,$user) ) ) {
 
      $process_host_table = ($record[0] eq '');

      if ($user_tpl eq '') { push(@Warnings,'public_database'); }

      foreach $field (keys(%D)) {
        printf "\t> %-15s: input=$record[$D{$field}]  =>  $Access{$field}($Answer{$Access_db{$field}}) -> ",$field if ($DEBUG>1);
        $Access_db{$field} = ($Access_db{$field} or $Answer{$record[$D{$field}]});
        print "$Access_db{$field}($Answer{$Access_db{$field}})\n" if ($DEBUG>1);
      }
      print "\n" if ($DEBUG>1);
      $rule_found=1;
      $Rules{'db'} = $record;
      last;
    }
  }

  # -------------------------------
  #  setting privileges to db-priv
  print "Rights after parsing db-table..:\n" if ($DEBUG>1);
  if (! $rule_found ) {
    print "NO rule found in db-table => no access granted!!\n" if ($DEBUG>1);
    #foreach $field (keys(%Access_db)) { $Access_db{$field} = 1; }
    #push(@Notes,'insecure_db');
  }
  foreach $field (keys(%Access)) {
      $Access{$field} = ($Access{$field} or $Access_db{$field});
  }
  #push(@Notes,'least_priv');

  # --------------------------------
  # debugging access-rights in db 
  Print_Access_rights(\%Access) if ($DEBUG>1);

  return $process_host_table;
}

# ==========================================================
# sub Get_grant_from_host:
# ==========================================================
sub Get_grant_from_host {
  print "Get_grant_from_host()\n" if ($DEBUG>0);
  my %Access_host = %{Default_Access_Rights()};
  my $rule_found=0;
  my @record = ();

  # the host-table doesn't have to be processed if the host-field
  # in the db-table isn't empty
  if (!$process_host_table) {
    print ">> Host-table doesn't have to be processed!!\n" if ($DEBUG>1);
    $Rules{'host'} = 'no_equiv_host';
    return 1;
  }

  foreach $record (@sorted_host_table) {
    $full_access=0;
    print "host: $record" if ($DEBUG>2);
    #chop($record);
    @record=split(/\t/,$record);

    # check host and db
    # with possible wildcards in field
    # replace mysql-wildcards by reg-wildcards
    my $host_tpl = SQL2Reg($record[0]);
    my $db_tpl   = SQL2Reg($record[1]);
    print "\thost_tpl : $record[0] -> $host_tpl\n" if ($DEBUG>2);
    print "\tdb_tpl   : $record[1] -> $db_tpl\n" if ($DEBUG>2);

    if ($DEBUG>2) {
      if ( Is_localhost($host_tpl)
           or MatchTemplate($host_tpl,$host_name)
           or MatchTemplate($host_tpl,$host_ip) )
        { print "\t* host [$host_name,$host_ip] matched with rule [$host_tpl]\n"; }
      else
        { print "\t* host doesn't match\n"; }
      if ( MatchTemplate($db_tpl,$db) )
        { print "\t* db [$db] matched with rule [$db_tpl]\n"; }
      else
        { print "\t* db doesn't match \n";}
    }

    if ( ( Is_localhost($host_tpl)
           or MatchTemplate($host_tpl,$host_name)
           or MatchTemplate($host_tpl,$host_ip) )
         and ( MatchTemplate($db_tpl,$db) ) ) {

      $Rules{'host'} = $record;
      $rule_found=1;
      foreach $field (keys(%H)) {
        printf "\t> %-15s: input=$record[$H{$field}]  =>  $Access_host{$field}($Answer{$Access_host{$field}}) -> ",$field if ($DEBUG>2);
        $Access_host{$field} = ($Access_host{$field} or $Answer{$record[$H{$field}]});
        print "$Access_host{$field}($Answer{$Access_host{$field}})\n" if ($DEBUG>2);
      }
      print "\n" if ($DEBUG>2);
      last;
    }
  }

  # -------------------------------
  #  setting privileges to host-priv
  print "Rights after parsing host-table..:\n" if ($DEBUG>1);
  if (! $rule_found ) {
     @record=();
     print "NO restrictions found in the host-table!!\n" if ($DEBUG>1);
  }
  foreach $field (keys(%Access)) {
      $Access{$field} = ($Access_host{$field} and $Access{$field});
  }

  # --------------------------------
  # debugging access-rights in db 
  Print_Access_rights(\%Access) if ($DEBUG>1);

  return @record; #matching record in host-table
}

# ==========================================================
# sub Get_grant_from_user:
# ==========================================================
sub Get_grant_from_user {
  print "Debug Get_grant_from_user: \n" if ($DEBUG>0);
  my %Access_user = %{Default_Access_Rights()};
  my $rule_found=0;
  my @record = ();

  foreach $record (@sorted_user_table) {
    $full_access=0;
    print "user: $record" if ($DEBUG);
    #chop($record);
    @record=split(/\t/,$record);

    # check host and db
    # with possible wildcards in field
    # replace mysql-wildcards by reg-wildcards
    my $host_tpl = SQL2Reg($record[0]);
    my $user_tpl = $record[1]; #user field isn't pattern-matched!!
    my $passwd   = $record[2];
    print "\thost_tpl : $record[0] -> $host_tpl\n" if ($DEBUG>1);
    print "\tuser_tpl : $record[1] -> $user_tpl\n" if ($DEBUG>1);
    print "\tpassword : $record[2] -> $passwd\n" if ($DEBUG>1);

    if ($DEBUG>1) {
      if ( Is_localhost($host_tpl)
           or MatchTemplate($host_tpl,$host_name)
           or MatchTemplate($host_tpl,$host_ip) )
        { print "\t* host  [$host_name,$host_ip] matched with rule [$host_tpl]\n"; }
      else
        { print "\t* host doesn't match\n"; }
      if ( MatchTemplate($user_tpl,$user) )
        { print "\t* user  [$user] matched with rule [$user_tpl]\n"; }
      else
        { print "\t* user doesn't match\n"; }
      if ($passwd ne "" )
        { print "\tPassword required\n"; }
      else
        { print "\tNo password required\n"; }
    }

    if ( ( Is_localhost($host_tpl)
           or  MatchTemplate($host_tpl,$host_name)
           or  MatchTemplate($host_tpl,$host_ip) )
         and ( MatchTemplate($user_tpl,$user) ) ) {

      if ($passwd eq '') { push(@Warnings,'insecure_user');  }
      else               { push(@Notes,'password_required'); }

      foreach $field (keys(%U)) {
        printf "\t> %-15s: input=$record[$U{$field}]  =>  $Access_user{$field}($Answer{$Access_user{$field}}) -> ",$field if $DEBUG;
        $Access_user{$field} = ($Access_user{$field} or $Answer{$record[$U{$field}]});
        print "$Access_user{$field}($Answer{$Access_user{$field}})\n" if $DEBUG;
      }
      print "\n" if $DEBUG;
      $Rules{'user'} = $record;
      $rule_found=1;
      last;
    }
  }

  # -------------------------------
  #  setting privileges to user-priv
  print "Rights after parsing user-table..:\n" if ($DEBUG>1);
  if (! $rule_found ) {
     @record=();
     print "NO record found in the user-table!!\n" if ($DEBUG>1);
  }
  foreach $field (keys(%Access)) {
    $Access{$field} = ($Access_user{$field} or $Access{$field});
  }
 
  # --------------------------------
  # debugging access-rights in db   
  print "Rights after parsing user-table..:\n" if ($DEBUG>1);
  Print_Access_rights(\%Access) if ($DEBUG>1);

  return @record; #matching record in user-table
}

# ===========================================================
# sub Report_Access_Rights()
#  report the access_rights for the tuple ($host,$user,$db).
# ===========================================================
sub Report_Access_Rights {
  local ($host,$user,$db) = @_;
  # ******************************************************************************
  # Create default access-rights
  #   default access-rights are no access at all!!
  local %Access       = %{Default_Access_Rights()};

  # ******************************************************************************
  # get hostname for IP-address
  # get IP-address for hostname
  local $host_name = IP2Name($host);
  local $host_ip   = Name2IP($host);

	print "DEBUG $script:\n" if ($DEBUG>0);
	print "  --host=$host\n" if ($DEBUG>0);
	print "    hostname=$host_name\n" if ($DEBUG>0);
	print "    host-ip =$host_ip\n" if ($DEBUG>0);
	print "  --user=$user\n" if ($DEBUG>0);
	print "  --db  =$db\n"  if ($DEBUG>0);

  # ******************************************************************************
  # retrieve information on DB
  #  check all records in mysql::db for matches with the triple (host,db,user)
  #  first match is used.
  # ******************************************************************************
  #    2.Get grant for user from the "db" table.

  $process_host_table=Get_grant_from_db(); #set process_host_table

  # ***********************************************************************
  # retrieve information on HOST
  #  check all records in mysql::host for matches with the tuple (host,db)
  #
  #  ' The host table is mainly to maintain a list of "secure" servers. '
  # ***********************************************************************
  #    3.If hostname is "empty" for the found entry, AND the privileges with
  #      the privileges for the host in "host" table.
  #      (Remove all which is not "Y" in both)

  Get_grant_from_host();

  # ***********************************************************************
  # retrieve information on USER
  #  check all records in mysql::user for matches with the tuple (host,user)
  # ***********************************************************************
  #    4.OR (add) the privileges for the user from the "user" table.
  #     (add all privileges which is "Y" in "user")

  ($_host_,$_user_,$_encpw_) = Get_grant_from_user();

  # *******************************************************
  #  Validate password is this has been asked to do
  # *******************************************************
  if (defined($password)) {
     $valid = Validate_Password($password,$_host_,$_user_,$_encpw_);
     if (!$valid) { push(@Errors,'invalid_password'); }
     else         { push(@Notes,'valid_password'); }
  }

  # *******************************************************
  #  Report the access-rights on STDOUT
  # *******************************************************
  Print_Access_rights(\%Access);
}

# ===========================================================
# sub Default_Access_Rights():
#  return (a reference to) a hash which holds all default
#  priviliges currently defined in the grant-tables.
# ===========================================================
sub Default_Access_Rights {
    my %right = {};
    print "Debug Default_Access_Rights():\n" if ($DEBUG>1);
    # add entry for all fields in the HOST-table
    foreach $field (keys(%H)) {
	$right{$field}='0' unless (defined($right{$field}));
    }
    # add entry for all fields in the DB-table
    foreach $field (keys(%D)) {
	$right{$field}='0' unless (defined($right{$field}));
    }
    # add entry for all fields in the USER-table
    foreach $field (keys(%U)) {
	$right{$field}='0' unless (defined($right{$field}));
    }
    if ($DEBUG>2) {
       foreach $field (keys(%right)) { print "> $field : $right{$field}\n"; }
    }
    return \%right;
}

# ######################################
#  Connecting to the MYSQL DB
# ======================================
# sub ConnectAs
#  if a superuser and a superpasswd is
#  given, use this to connect to the
#  access-rights DB.
# ======================================
sub ConnectAs {
    my $connect="";
    if ($superuser) { $connect .= " -u $superuser"; }
    if ($spassword) { $connect .= " -p $spassword"; }
    print "Connect as: $connect\n" if ($DEBUG>2);
    return $connect;
}

# ======================================
# sub HaveReadAccess
#  Does the user have read-access to
#  the mysql-db
#  !! also sets the $CONNECT 
# ======================================
sub HaveReadAccess {
    $CONNECT  = ConnectAs();        #define the connection-phrase
    my $error = 'ERROR: Access denied';
    open(TEST,"$MYSQL $ACCESS_DB $CONNECT -e \"show tables\" 2>&1 |");
    my $line = <TEST>;
    print "connect: $line" if ($DEBUG>2);
    close(TEST);
    if ($line =~ /$error/) { return 0;}
    else                   { return 1;}
}

# ===========================================================
# sub Show_Fields($table): 
#  return (a reference to) a hash which holds the names
#  of all relevant grant-fields, with their index in the record.
#  if called as a list, return (a reference to) an array.
# ===========================================================
sub Show_Fields {
    my ($table,$wantarray) = @_;
    my @skip = ('user','User','host','Host','db','Db','password','Password');
    my %Struct = {};
    my @Struct = ();
    print "Show_Fields: from $table\n" if ($DEBUG>1);
    open(MYSQL,"$MYSQL $ACCESS_DB $CONNECT -e \"show fields from $table\" 2>&1 |");
    my $line = <MYSQL>;
    print "connect: $line" if ($DEBUG>2);
    my $i=0;
    my $line;
    while ( $line=<MYSQL> ) {
	chop($line);
	print " >read: $line\n" if ($DEBUG>2);
	my ($field,$type,$null,$key,$default,$extra) = split(' ',$line);
	print " <split: $field - $type - $null - $key - $default - $extra\n" if ($DEBUG>2);
	if (! grep(/$field/,@skip) ){
		$Struct{$field} = $i; #hash
	  	push(@Struct,$field); #array
		print " ==> $i: $field ($Struct{$field})\n" if ($DEBUG>1);
	} 
	$i++;
    }
    close(MYSQL);
    return  ($wantarray ? \@Struct : \%Struct); 
}

# ======================================
# sub Make_Privlist
#  Make an ordered list of the privileges
#  that should be reported
# ======================================
sub Make_Privlist {
    # layout:
    #'select_priv',     'create_priv',
    #'insert_priv',     'drop_priv',
    #'update_priv',     'reload_priv',
    #'delete_priv',     'process_priv',
    #'file_priv',       'shutdown_priv');
    my $right;
    my @privlist=();
    foreach $right (@U) {
	if (! grep(/$right/,@privlist)) { push(@privlist,$right); }
    };
    foreach $right (@D) {
	if (! grep(/$right/,@privlist)) { push(@privlist,$right); }
    };
    foreach $right (@H) {
	if (! grep(/$right/,@privlist)) { push(@privlist,$right); }
    };
    if ($DEBUG>2) {
       print "Privileges:\n";
       foreach $field (@privlist) { print " > $field\n"; }
    }
    return \@privlist;
}

# ======================================
# sub Validate_Password($passwd,$host,$user,$encpw)
#  Validate the given password 
#  for user '$user' 
#  connecting from host '$host'
# ======================================
sub Validate_Password {
    my ($password,$host,$user,$encpw) = @_;
    print "Debug: Validate_Password ($password)\n" if ($DEBUG>0);
    my $sql = "select host,user,password from user having "
             ."host='$host' and user='$user' and password='$encpw' "
             ."and password=PASSWORD('$password')";
    print "sql=$sql\n" if ($DEBUG>2);
    open(MYSQL,"$MYSQL $ACCESS_DB $CONNECT -e \"$sql\" 2>&1 |");
    my $line = <MYSQL>;
    # column-headers...
    print "connect: $line" if ($DEBUG>2);

    # if password is valid, 1 row returns, otherwise empty set!!
    $line=<MYSQL>;
    print ">>line = $line" if ($DEBUG>2);
    my $valid = defined($line); 
    close (MYSQL);    
    return $valid;
}

# ######################################
#  REPORTING 
# =====================================
# sub Header:
#  print header info
# =====================================
sub Header {
    my $header;
    $header="$script Version $VERSION\n"
           ."By RUG-AIV, by Yves Carlier (Yves.Carlier\@rug.ac.be)\n"
           ."This software comes with ABSOLUTELY NO WARRENTY.\n";
    return $header;
}

# =====================================
# sub Footer:
#  print footer info
# =====================================
sub Footer {
    my $footer;
    $footer="\n"
           ."\t>> BUGs can be reported by email to Yves.Carlier\@rug.ac.be\n"
           ."\t>> Don't forget to mention the version $VERSION!\n";
    return $footer;
}


# =====================================
# sub help:
#  print some information on STDOUT
# =====================================
sub help {
    print "\n";
    print "Usage: $script OPTIONS [host] [user] [db]\n";
    print "\n";
    print "  -?, --help           display this help and exit\n";
    print "  -u, --user=#         user for login\n";
    print "  -p, --password=#     password to verify\n";
    print "  -h, --host=#         connection from host\n";
    print "  -d, --db=#           connection to database\n";
    print "  -U, --superuser=#    connect as superuser\n";
    print "  -P, --spassword=#    password for superuser\n";
    print "\n";
    print "  --all_dbs            check access-rights for all dbs\n";
    print "  --all_hosts          check access-rights for all hosts\n";
    print "  --all_users          check access-rights for all users\n";
    print "  --brief              don't mention notes, warnings and rules\n";
    print "\n";
    print "  --debug=N            enter debuglevel N (0..3)\n";
    print "\n";
    print "  At least the user and the db must be given\n";
    print "  If no host is given, 'localhost' is assumed\n";
    print "\n";
    print Footer();
    return 1;
}

# ======================================
# sub Print_Access_rights:
#  print the access-rights on STDOUT
# ======================================
sub Print_Access_rights {
    my $a = $_[0];
    my $column=2;
    
    # -----------------------------
    # print table of access-rights
    my $rows = int(@privlist/2);  #round up
    my @table=();
    $j=0;
    for $i (0 .. $rows-1) {
      $table[$j]=$privlist[$i];
      $j = $j+2;
    }
    $j=1;
    for $i ($rows .. $#privlist) {
      $table[$j]=$privlist[$i];
      $j = $j+2;
    }
    print "\n";
    print "Access-rights\n";
    print "for USER '$user', from HOST '$host', to DB '$db'\n";
    print      "\t+-----------------+---+\t+-----------------+---+";
    foreach $field (@table) {
	if ($column==2) { print "\n\t"; $column=1;}
	else            { print "\t";   $column=2;}
	printf "| %-15s | %s |",$field,$Answer{$a->{$field}}; 
    }
    print      "\n";
    print      "\t+-----------------+---+\t+-----------------+---+\n";

    if (!defined($brief)) {
       # ---------------
       # print notes:
       foreach $note (@Notes) {
         my $message = $Messages{$note};
         $message =~ s/\$user/$user/g; 
         $message =~ s/\$db/$db/g;
         $message =~ s/\$password/$password/g;
         $PREFIX='NOTE';
         my @lines = split(/\n/,$message);
         foreach $line (@lines) { 
           print "$PREFIX:\t $line\n"; 
           $PREFIX='    ';
         }
       } 

       # ---------------
       # print warnings:
       foreach $warning (@Warnings) {
         my $message = $Messages{$warning};
         $message =~ s/\$user/$user/g;
         $message =~ s/\$db/$db/g;
         $message =~ s/\$password/$password/g;
         $PREFIX='WARN';
         my @lines = split(/\n/,$message);
         foreach $line (@lines) { 
           print "$PREFIX:\t $line\n"; 
           $PREFIX='    ';
         }
       }

       # ---------------
       # print errors:
       foreach $error (@Errors) {
         my $message = $Messages{$error};
         $message =~ s/\$user/$user/g;
         $message =~ s/\$db/$db/g;
         $message =~ s/\$password/$password/g;
         $PREFIX='ERROR';
         my @lines = split(/\n/,$message);
         foreach $line (@lines) { 
           print "$PREFIX:\t $line\n"; 
           $PREFIX='    ';
         }
       }

       # ---------------
       # inform if there are no rules ==> full access for everyone.
       if ($full_access) { print "$Messages{'full_access'}\n"; }

       # ---------------
       # print the rules used
       print "\n";
       print "The following rules are used:\n";
       foreach $field (keys(%Rules)) {
         my $rule = (defined($Messages{$Rules{$field}}) ? $Messages{$Rules{$field}} : $Rules{$field});
         $rule =~ s/\t/','/g;
	   printf " %-5s : '%s'\n",$field,$rule;
       }
    }
    return 1;
}

# ======================================
# sub Print_Error_Access:
# ======================================
sub Print_Error_Access {
    print "\n";
    print "Sorry,\n";
    print "An error occured when trying to connect to the database with the grant-tables:\n";
    print "Maybe you do not have READ-access to this database?\n";
    if ($superuser) {
       print "OR maybe the supplied superuser-id is invalid?\n";
    }
    if ($spassword) {
       print "OR maybe the supplied password for the superuser is invalid?\n";
    }
    print "\n";
    return 1;
}

# ######################################
# HOSTNAMES, IP-NUMBERS and ALIKE
# ======================================
# sub IP2Name
#  return the Name with the corr. IP-nmbr
#  (no aliases yet!!)
# ======================================
sub IP2Name {
    my ($ip) = @_;
    print "DEBUG IP2Name():\n" if $DEBUG>2;
    if ($ip !~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/o) {
      print "not an ip-number, returning $ip\n" if $DEBUG>2;
      return $ip;
    }
    print "* ip=$1.$2.$3.$4\n" if $DEBUG>2;
    $ip = pack "C4",$1,$2,$3,$4;
    print "* ==>>$ip<<\n" if $DEBUG>2;
    my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyaddr($ip, AF_INET);

    print "* name=$name\n* aliases=$aliases\n* addrtype=$addrtype\n* length=$length\n"
      if $DEBUG>2;

    return ($name || undef);
}

# ======================================
# sub Name2IP
#  return the IP-number of the host
# ======================================
sub Name2IP {
    my ($name) = @_;
    print "DEBUG Name2IP():\n" if $DEBUG;
    print "* name=$name\n" if $DEBUG;
    my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($name);
    print "* aliases=$aliases\n" if $DEBUG;
    print "* addrtype=$addrtype\n" if $DEBUG;
    print "* length=$length\n" if $DEBUG;
    my ($a,$b,$c,$d) = unpack('C4',$addrs[0]);
    my $ip = "$a.$b.$c.$d";
    print "* ip=$ip\n" if $DEBUG;
    if ($ip ne "") { return "$ip"; }
    else           { return undef; }
}

# ========================================
# sub LocalHost
#  some special action has to be taken for
#  the localhost
# ========================================
sub LocalHost {
    my $host = hostname();
    print "localhost = $host\n" if $DEBUG>2;
    return $host;
}

# ========================================
# check if the given hostname (or ip)
# corresponds with the localhost
# ========================================
sub Is_localhost {
    my ($host_tpl) = @_;
    if (($host_name eq $localhost) or ($host_ip eq $local_ip)) {
	print "Checking for localhost\n" if $DEBUG>2;
        print " 'localhost' =?= $host_tpl\n" if $DEBUG>2;
	if ('localhost' =~ /$host_tpl/ ) { return 1;}
	else			         { return 0;}
    }
    else {
	print "Not checking for localhost\n" if $DEBUG>2;
	return 0;
    }
}

# ############################################
# SQL, WILDCARDS and REGULAR EXPRESSIONS 
# ============================================
# translage SQL-expressions to Reg-expressions
# ============================================
sub SQL2Reg {
    my ($expr) = @_;
    print "SQL2Reg: $expr " if ($DEBUG>2);
    $expr  =~ s/\./\\./g;
    $expr  =~ s/\\%/\002/g;
    $expr  =~ s/%/.*/g;
    $expr  =~ s/\002/%/g;
    $expr  =~ s/\\_/\002/g;
    $expr  =~ s/_/.+/g;
    $expr  =~ s/\002/_/g;
    print " --> $expr\n" if ($DEBUG>2);
    return $expr;
}

# translage WILDcards to Reg-expressions
# ============================================
sub Wild2Reg {
    my ($expr) = @_;
    print "Wild2Reg: $expr " if ($DEBUG>2);
    $expr  =~ s/\./\\./g;
    $expr  =~ s/\\\*/\002/g;
    $expr  =~ s/\*/.*/g;
    $expr  =~ s/\002/*/g;
    $expr  =~ s/\\\?/\002/g;
    $expr  =~ s/\?/.+/g;
    $expr  =~ s/\002/?/g;
    print " --> $expr\n" if ($DEBUG>2);
    return $expr;
}

# =============================================
# match a given string with a template
# =============================================
sub MatchTemplate {
    my ($tpl,$string) = @_;
    my $match=0;
    print "MatchTemplate: $tpl <-> $string : " if ($DEBUG>2);
    if ($string=~ /^$tpl$/ or $tpl eq '') { $match=1; }
    else                                  { $match=0;}
    print "result=$match\n" if ($DEBUG>2);
    return $match;
}
