Subversion Repositories greylisting

Rev

Rev 138 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

#!/usr/bin/perl

#############################################################################
#
# File: db_maintenance.pl
#
# Version: 1.0
#
# Programmer: Evan J. Harris <eharris@puremagic.com>
#
# Description:
#   Performs nightly cleanup and maintenace on the greylisting database
#   as created from dbdef.sql.  Will copy all rows out of the main
#   relaytofrom table into the reporting table, and then delete the
#   expired ones from the main table.  Not required for the implementation,
#   but helps keep the active database smaller/faster without losing any
#   data that may be useful for profiling.
#
# References:
#   For Greylisting info, see http://projects.puremagic.com/greylisting/
#   For SMTP info, see RFC821, RFC1891, RFC1893
#
# Notes:
#   - The new parameters chunk_size and sleep_secs helps limit the impact
#     that the maintenance has on the db, since the db intensive copy/delete
#     queries lock the db during their execution.  If your db is very large,
#     you will want to set these accordingly.
#   - If you want to optimize the active table (relaytofrom), keep in mind
#     that it will cause the table to be locked from updates for a few
#     seconds to several minutes depending on table size and speed of the
#     db machine and the network connection to it.
#   - May also be run more or less often than nightly if desired.
#
# Bugs:
#   None known.
#
#
# *** Copyright 2003-2004 by Evan J. Harris - All Rights Reserved ***
# *** No warranties expressed or implied, use at your own risk    ***
#
#############################################################################

use Errno qw(ENOENT);
use DBI;

use strict;

###############################################
# Our global settings file
###############################################
my $config_file = "/etc/mail/relaydelay.conf";


#################################################################
# Our global settings that may be overridden from the config file
#################################################################

# If you do/don't want to see debugging messages printed to stdout,
#   then set this appropriately.
my $verbose = 1;

# Database connection params
my $database_type = 'mysql';
my $database_name = 'relaydelay';
my $database_host = 'localhost';
my $database_port = 3306;
my $database_user = 'db_user';
my $database_pass = 'db_pass';


#############################################################
# End of options for use in external config file
#############################################################

# Set this to the chunk size you want to process records in
my $chunk_size = 1000;

# Set this to the number of seconds to sleep between copy/delete
#   chunks (so other clients can get some work done)
my $sleep_secs = 1;

# Set this to nonzero if you wish to optimize the active table
#   after deleting the rows moved to the reporting table.
my $optimize_active_table = 0;

# Global vars that should probably not be in the external config file
my $global_dbh;
my $config_loaded;


#######################################################################
# Database functions
#######################################################################

sub db_connect($) {
  my $verbose = shift;

  return $global_dbh if (defined $global_dbh);

  my $dsn = "DBI:$database_type:database=$database_name:host=$database_host:port=$database_port";
  print "DBI Connecting to $dsn\n" if $verbose;

  # Note: We do all manual error checking for db errors
  my $dbh = DBI->connect($dsn, $database_user, $database_pass,
                         { PrintError => 0, RaiseError => $verbose });

  $global_dbh = $dbh;
  return $global_dbh;
}

sub db_disconnect {
  $global_dbh->disconnect() if (defined $global_dbh);
  $global_dbh = undef;
  return 0;
}


sub load_config() {

  # make sure the config is only loaded once per instance
  return if ($config_loaded);

  print "Loading Config File: $config_file\n";

  # Read and setup our configuration parameters from the config file
  my($msg);
  my($errn) = stat($config_file) ? 0 : 0+$!;
  if ($errn == ENOENT) { $msg = "does not exist" }
  elsif ($errn)        { $msg = "inaccessible: $!" }
  elsif (! -f _)       { $msg = "not a regular file" }
  elsif (! -r _)       { $msg = "not readable" }
  if (defined $msg) { die "Config file $config_file $msg" }

  open INFILE, "<$config_file";
  while (<INFILE>) {
    my $tstr = $_;
    if ($tstr =~ /\A\s*(\$database_\w+)\s*=/) {
      eval $tstr;
      if ($@ ne '') { die "Error in config file $config_file: $@" }
    }
  }

  $config_loaded = 1;
}


BEGIN:
{
  # load config file before we start
  load_config();

  # Flush output at every write
  $| = 1;

  # connect to the database
  my $dbh = db_connect(1);
  die "$DBI::errstr\n" unless($dbh);

  # Get the current DB server time for use in later queries (minus one second to
  #   work around possible race)
  my $now = $dbh->selectrow_array("SELECT NOW()");
  print "DB Server Time: $now\n";

  my $highest_id = $dbh->selectrow_array("SELECT MAX(id) FROM relaytofrom");
  print "Highest ID: $highest_id\n";

  my $first = 1;
  my $last = 0;
  my $copied = 0;
  my $deleted = 0;
  while ($last < $highest_id) {
 
    # Query to find out what the last id was that will be copied in this iteration
    my $ids = $dbh->selectcol_arrayref("SELECT id FROM relaytofrom WHERE id >= $first ORDER BY id LIMIT $chunk_size");

    my $maxindex = $#$ids;
    $last = $$ids[$maxindex];
    $copied += $maxindex + 1;
    print "Last Row: $last";

    # Copy selected row range to the reporting table, replacing any existing rows
    $dbh->do("REPLACE INTO relayreport SELECT * FROM relaytofrom WHERE id >= $first AND id <= $last");
    print " - Copied: " . ($maxindex + 1);

    my $rows = $dbh->do("DELETE FROM relaytofrom WHERE record_expires < '$now' AND origin_type = 'AUTO' AND id <= $last");
    $rows += 0;
    $deleted += $rows;
    print " - Deleted: $rows\n";

    sleep($sleep_secs);

    $first = $last + 1;
  }

  print "\nSummary: \n";
  print "  Total Copied: $copied\n";
  print "  Total Deleted: $deleted\n";

  if ($optimize_active_table) {
    $dbh->do("OPTIMIZE TABLE relaytofrom");
    print "Optimized active table.\n";
  }

}