#!/usr/local/bin/perl
#
# $Id: SyslogMailStatFilter.pl,v 1.5 2003/10/07 02:58:29 rdilley Exp $ [$Date: 2003/10/07 02:58:29 $ - ]
#
# author: ron dilley
#
# desc: this perl script generates mail statistics from syslog output
#
# Copyright (C) 2001  Ron A. Dilley
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ron.dilley@uberadmin.com
#
############################################################################

#
# modules
#
use Getopt::Std;

#
# pragmas
#
use strict;

#
# set environment
#
$ENV{PATH} = "/usr/bin:/bin:/usr/sbin:/sbin:/usr/ucb";

#
# turn on autoflush
#
select STDERR; $|;
select STDOUT; $|;

#
# defines
#
$::TRUE = 1;
$::FALSE = 0;
$::FAILED = -1;

$::VERSION = '$Id: SyslogMailStatFilter.pl,v 1.5 2003/10/07 02:58:29 rdilley Exp $';
$::PROGNAME = "SyslogMailStatFilter.pl";

%::Config = ();
$::Config{'debug'} = $::FALSE;

%::mail_records = ();
%::mail_records_by_host = ();
%::mail_records_by_msgid = ();

@::mail_record_list = ();

#
# main routine
#
if ( &main() != $::TRUE ) {
  exit( 1 );
}

exit( 0 );

############################################################################
#
# sub-routines
#

#
# main routine
#
sub main {
  my $arg;

  #
  # display script banner
  #
  &show_banner();

  #
  # parse command-line
  #
  &parse_command_line();

  # process args that are left
  while( $arg = shift( @::ARGV ) ) {
    # do something
    #
    # parse syslog file
    #
    &syslog_parser( $arg, "sendmail" );
  }

  #
  # display mail stats
  #
  &show_mail_stats();

  # done
  return $::TRUE;
}

#
# display banner info
#
sub show_banner {
  print "$::PROGNAME $::VERSION\n";
  print "By: Ron Dilley\n";
  print "\n";
  print "$::PROGNAME comes with ABSOLUTELY NO WARRANTY.\n";
  print "This is free software, and you are welcome\n";
  print "to redistribute it under certain conditions;\n";
  print "See the GNU General Public License for details.\n";
  print "\n";

  return $::TRUE;
}

#
# display help info
#
sub show_help {
  print "Syntax:\n";
  print "\n";
  print "$::PROGNAME [options] {file} [{file} ...]\n";
  print "\n";
  print "-d {0-9}   Display debug information during program run\n";
  print "\n";

  return $::TRUE;
}

#
# parse command-line arguments
#
sub parse_command_line {
  no strict 'vars';

  if ( getopts( 'd:' ) == $::FALSE ) {
    &show_help();
    return $::FAILED;
  }
  if ( defined $opt_d ) {
    if ( $opt_d > 0 ) {
      # set debug mode
      $::Config{'debug'} = $opt_d;
    }
  }
  return $::TRUE;
}

#
# parse syslog file looking for regex matches
#
sub syslog_parser {
  my ( $file, $service ) = @_;
  my $line;

  if ( $file eq "-" ) {
    # use stdin
    while( $line = <STDIN> ) {
      chomp( $line );
      if ( $::Config{'debug'} >= 9 ) {
	print "DEBUG - Reading [$line]\n";
      }

      if ( $line =~ $service ) {
	if ( $::Config{'debug'} >= 8 ) {
	  print "DEBUG - Parsing [$line]\n";
	}
	&process_syslog_line( $line );
      } else {
	if ( $::Config{'debug'} >= 8 ) {
	  print "DEBUG - Ignoring [$line]\n";
	}
      }
    }
  } else {
    # open a file
    if ( open( FILE, $file ) != $::TRUE ) {
      print "ERROR - Unable to open [$file]\n";
      return $::FAILED;
    }

    while( $line = <FILE> ) {
      chomp( $line );
      if ( $::Config{'debug'} >= 9 ) {
	print "DEBUG - Reading [$line]\n";
      }

      if ( $line =~ $service ) {
	if ( $::Config{'debug'} >= 8 ) {
	  print "DEBUG - Parsing [$line]\n";
	}
	&process_syslog_line( $line );
      } else {
	if ( $::Config{'debug'} >= 8 ) {
	  print "DEBUG - Ignoring [$line]\n";
	}
      }
    }

    close( FILE );
  }

  return $::TRUE;
}

#
# process syslog line matches by regex
#
sub process_syslog_line {
  my ( $line ) = @_;

  # message container
  my %mail;
  my $option;
  my $i;
  my $process_message = $::FALSE;

  # syslog fields
  my $time;
  my $host;
  my $message;

  #
  # look for valid sendmail syslog lines with queue entries
  #
  if ( $line =~ m/.*(\d{2}\:\d{2}\:\d{2}) (\S+) sendmail\[\d+\]\: ([A-Z]{3}\d{5})\: (.*)$/ ) {
    # valid syslog line with queue entry
    $mail{'time'} = $1;
    $mail{'host'} = $2;
    $mail{'queueid'} = $3;
    $message = $4;
    $process_message = $::TRUE;
  } elsif ( $line =~ m/.*(\d{2}\:\d{2}\:\d{2}) (\S+) sendmail\[\d+\]\: \[ID \d+ \b\D+\.\D+\] ([a-z|A-Z|0-9]{7}\d{5})\: (.*)$/ ) {
    # valid syslog line with queue entry
    $mail{'time'} = $1;
    $mail{'host'} = $2;
    $mail{'queueid'} = $3;
    $message = $4;
    $process_message = $::TRUE;
  } elsif ( $line =~ m/.*(\d{2}\:\d{2}\:\d{2}) (\S+) sendmail\[\d+\]\: (.*)$/ ) {
    # valid syslog line without queue entry (must be some form of error)
    $time = $1;
    $host = $2;
    $message = $3;
    if ( $::Config{'debug'} >= 7 ) {
      print "DEBUG -  Non-queued mail line\n";
      print "DEBUG -  Time: $time\n";
      print "DEBUG -  Host: $host\n";
    }
    if ( $::Config{'debug'} >= 8 ) {
      print "DEBUG -   Message: $message\n";
    }
    $process_message = $::FALSE;
  } else {
    # invalid syslog line
    if ( $::Config{'debug'} >= 8 ) {
      print "DEBUG - Non-sendmail syslog line\n";
    }
    $process_message = $::FALSE;
  }

  if ( $process_message ) {
    if ( $::Config{'debug'} >= 7 ) {
      print "DEBUG -  Time: $mail{'time'}\n";
      print "DEBUG -  Host: $mail{'host'}\n";
      print "DEBUG -  Queue: $mail{'queueid'}\n";
    }

    if ( $::Config{'debug'} >= 8 ) {
      print "DEBUG -  Message: $message\n";
    }

    #
    # parse the message portion of the syslog line
    #
    if ( $message =~ m/.*\bto\=(.*?)[\s|,]+\w+\=.*/ ) { # to
      # break up multiple to addresses
      my ( @tolist ) = split( /\,/, $1 );
      # remove leading '<' and trailing '>'
      for( $i = 0; $i < @tolist; $i++ ) {
	$tolist[$i] =~ s/^\<(.*)/\1/;
	$tolist[$i] =~ s/(.*)\>$/\1/;
      }
      $mail{'to'} = \@tolist;
    }
    if ( $message =~ m/.*\bfrom\=(.*?)[\s|,]+\w+\=.*/ ) { # from
      $mail{'from'} = $1;
      # remove leading '<' and trailing '>'
      $mail{'from'} =~ s/^\<(.*)/\1/;
      $mail{'from'} =~ s/(.*)\>$/\1/;
    }
    if ( $message =~ m/.*\bdelay\=(.*?)[\s|,]+\w+\=.*/ ) { # delay
      $mail{'delay'} = $1;
    }
    if ( $message =~ m/.*\bxdelay\=(.*?)[\s|,]+\w+\=.*/ ) { # xdelay
      $mail{'xdelay'} = $1;
    }
    if ( $message =~ m/.*\bsize\=(.*?)[\s|,]+\w+\=.*/ ) { # size
      $mail{'size'} = $1;
    }
    if ( $message =~ m/.*\bclass\=(.*?)[\s|,]+\w+\=.*/ ) { # class
      $mail{'class'} = $1;
    }
    if ( $message =~ m/.*\bpri\=(.*?)[\s|,]+\w+\=.*/ ) { # priority
      $mail{'priority'} = $1;
    }
    if ( $message =~ m/.*\bmailer\=(.*?)[\s|,]+\w+\=.*/ ) { # mailer
      $mail{'mailer'} = $1;
    }
    if ( $message =~ m/.*\brelay\=(.*?)[\s|,]+\w+\=.*/ ) { # relay on a 'to' message
      $mail{'relay'} = $1;
      if ( $mail{'relay'} =~ m/.* \[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]$/ ) {
	$mail{'relay_addr'} = $1;
      } else {
	# no ip addr
      }
    } elsif ( $message =~ m/.*\brelay\=(.*)$/ ) { # relay on a 'from' message
      $mail{'relay'} = $1;
      if ( $mail{'relay'} =~ m/.* \[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]$/ ) {
	$mail{'relay_addr'} = $1;
      } else {
	# no ip addr
      }
    }
    if ( $message =~ m/.*\bstat\=(.*)$/ ) { # status
      $mail{'status'} = $1;
    }
    if ( $message =~ m/.*\bnrcpts\=(.*?)[\s|,]+\w+\=.*/ ) { # nrcpts
      $mail{'nrcpts'} = $1;
    }
    if ( $message =~ m/.*\bmsgid\=\<*(.*?)\>*[\s|,]+\w+\=.*/ ) { # msgid
      $mail{'msgid'} = $1;
      # remove '/' from msgid just in case
      $mail{'msgid'} =~ s/\//\./g;
    }
    if ( $message =~ m/.*\bctladdr\=(.*?)[\s|,]+\w+\=.*/ ) { # ctladdr
      $mail{'ctladdr'} = $1;
    }
    if ( $message =~ m/.*\bbodytype\=(.*?)[\s|,]+\w+\=.*/ ) { # bodytype
      $mail{'bodytype'} = $1;
    }
    if ( $message =~ m/.*\bproto\=(.*?)[\s|,]+\w+\=.*/ ) { # protocol
      $mail{'protocol'} = $1;
    }

    # display message record
    if ( $::Config{'debug'} >= 7 ) {
      foreach $option ( sort( keys( %mail ) ) ) {
	if ( $option eq "to" ) {
	  for( $i = 0; $mail{'to'}[$i] ne "";$i++ ) {
	    print "DEBUG -   Key: $option=$mail{'to'}[$i]\n";
	  }
	} else {
	  print "DEBUG -   Key: $option=$mail{$option}\n";
	}
      }
    }
    &insert_mail_record( \%mail );
    return $::TRUE;
  }

  return $::FALSE;
}

#
# insert mail record
#
sub insert_mail_record {
  my ( $mail ) = @_;

  my %new_mail_ptr = %$mail;
  my $ndx_string = "$new_mail_ptr{'host'}-$new_mail_ptr{'queueid'}";

  my $array_ptr;

  #
  # add mail record to sequential list
  #
  push( @::mail_records, \%new_mail_ptr );

  #
  # create index by queueid
  #
  if ( defined $::mail_records_by_queueid{$ndx_string} ) {
    # part or mail record already exists, add to existing array
    if ( $::Config{'debug'} >= 5 ) {
      print "DEBUG - Mail records already exist for [$ndx_string]\n";
    }
    $array_ptr = $::mail_records_by_queueid{$ndx_string};
    push ( @$array_ptr, \%new_mail_ptr );
  } else {
    # new mail record
    if ( $::Config{'debug'} >= 5 ) {
      print "DEBUG - New mail record [$ndx_string]\n";
    }
    $::mail_records_by_queueid{$ndx_string}[0] = \%new_mail_ptr;
  }

  #
  # create index by host
  #
  if ( defined $::mail_records_by_host{$new_mail_ptr{'host'}} ) {
    # add index string to array
    $array_ptr = $::mail_records_by_host{$new_mail_ptr{'host'}};
    push ( @$array_ptr, $ndx_string );
  } else {
    # new host, create array
    $::mail_records_by_host{$new_mail_ptr{'host'}}[0] = $ndx_string;
  }

  #
  # create index by msgid
  #
  if ( defined $new_mail_ptr{'msgid'} ) {
    if ( defined $::mail_records_by_msgid{$new_mail_ptr{'msgid'}} ) {
      # add index string to array
      $array_ptr = $::mail_records_by_msgid{$new_mail_ptr{'msgid'}};
      push( @$array_ptr, $ndx_string );
    } else {
      # new msgid, create array
      $::mail_records_by_msgid{$new_mail_ptr{'msgid'}}[0] = $ndx_string;
    }
  }

  return $::TRUE;
}

#
# display mail statistics
#
sub show_mail_stats {
  my $ndx_string;
  my $array_ptr;
  my $mail_ptr;
  my $i;
  my @tmp_array;

  if ( $::Config{'debug'} >= 6 ) {
    # display mail record counts
    foreach $ndx_string ( keys( %::mail_records ) ) {
      $array_ptr = $::mail_records{$ndx_string};
      print "DEBUG - [$ndx_string] contains " . @$array_ptr . " record(s)\n";
    }
  }

  if ( $::Config{'debug'} >= 2 ) {
    # display mail record msgid record counts
    foreach $ndx_string ( keys( %::mail_records_by_msgid ) ) {
      $array_ptr = $::mail_records_by_msgid{$ndx_string};
      print "DEBUG - [$ndx_string] contains " . @$array_ptr . " record(s)\n";
    }
  }

  if ( $::Config{'debug'} >= 2 ) {
    # display mail record host record counts
    foreach $ndx_string ( keys( %::mail_records_by_host ) ) {
      $array_ptr = $::mail_records_by_host{$ndx_string};
      print "DEBUG - [$ndx_string] contains " . @$array_ptr . " record(s)\n";
    }
  }

  #
  # report on mail volume and sent/not-sent ratio
  #
  &show_mail_volume_by_relay( 10 );

  #
  # report on mail delay by relay
  #
  &show_mail_delay_by_relay( 10 );

  #
  # report on mail size by relay
  #
  &show_mail_size_by_relay( 10 );

  #
  # report mail volume by sender, bytes and mail counts
  #
  &show_user_mail_stats( 25 );

  return $::TRUE;
}

#
# display mail volume by relay
#
sub show_mail_volume_by_relay {
  my ( $line_count ) = @_;

  my $ndx_string;
  my $array_ptr;
  my $mail_ptr;
  my $sent;
  my $host;
  my $sent_count;
  my $failed_count;
  my $queued_count;
  my $i;
  my @tmp_array;

  #
  # report on mail volume and sent/not-sent ratio
  #
  print "Mail volume by host: Top $line_count host(s)\n";
  print "========================================\n";
  print "\n";
  print "    Hostname                    Queued          Sent        Failed\n";
  print "------------------------------------------------------------------\n";
  @tmp_array = sort by_array_size( keys( %::mail_records_by_host ) );

  for ( $i = 0; defined $tmp_array[$i] && $i < $line_count; $i++ ) {
    $array_ptr = $::mail_records_by_host{$tmp_array[$i]};
    $host = $tmp_array[$i];
    $sent_count = $failed_count = $queued_count = 0;
    foreach $ndx_string ( @$array_ptr ) {
      $sent = $::FALSE;
      $queued_count++;
      $array_ptr = $::mail_records_by_queueid{$ndx_string};
      foreach $mail_ptr ( @$array_ptr ) {
	if ( defined $mail_ptr->{'status'} ) {
	  if ( $mail_ptr->{'status'} =~ /^Sent/ ) {
	    $sent = $::TRUE;
	  } else {
	    $sent = $::FAILED;
	  }
	} elsif ( defined $mail_ptr->{'from'} ) {
	  # do nothing
	} else {
	  # do nothing
	}
      }

      if ( $sent == $::TRUE ) {
	$sent_count++;
      } elsif ( $sent == $::FAILED ) {
	$failed_count++;
      } else {
	# mail has not been sent yet
      }
    }
    # show data for each relay
    printf( "%3d %-20s %13s %13s %13s\n",
	    $i,
	    $host,
	    &number_to_pretty_string( $queued_count ),
	    &number_to_pretty_string( $sent_count ),
	    &number_to_pretty_string( $failed_count )
	  );
  }
  print "\n";

  return $::TRUE;
}

#
# display mail delay by relay
#
sub show_mail_delay_by_relay {
  my ( $line_count ) = @_;

  my $ndx_string;
  my $array_ptr;
  my $mail_ptr;
  my $sent;
  my $host;
  my $sent_count;
  my $failed_count;
  my $queued_count;
  my $delay;
  my $delay_min;
  my $delay_max;
  my $delay_ave;
  my $total_delay;
  my $i;
  my @tmp_array;

  #
  # report on mail delay by relay
  #
  print "Mail delay by host: Top $line_count host(s)\n";
  print "========================================\n";
  print "\n";
  print "    Hostname                 Min Delay     Max Delay      Av Delay\n";
  print "------------------------------------------------------------------\n";
  @tmp_array = sort by_array_size( keys( %::mail_records_by_host ) );

  for ( $i = 0; defined $tmp_array[$i] && $i < $line_count; $i++ ) {
    $array_ptr = $::mail_records_by_host{$tmp_array[$i]};
    $host = $tmp_array[$i];
    $sent_count = $failed_count = $queued_count = $delay = $total_delay = 0;
    undef $delay_min;
    undef $delay_max;
    foreach $ndx_string ( @$array_ptr ) {
      $sent = $::FALSE;
      $queued_count++;
      $array_ptr = $::mail_records_by_queueid{$ndx_string};
      foreach $mail_ptr ( @$array_ptr ) {
	if ( defined $mail_ptr->{'status'} ) {
	  if ( $mail_ptr->{'status'} =~ /^Sent/ ) {
	    $sent = $::TRUE;
	    # generate delay data
	    if ( defined $mail_ptr->{'delay'} ) {
	      $delay = &local_to_sec( $mail_ptr->{'delay'} );
	      if ( defined $delay_min ) {
		if ( $delay < $delay_min ) {
		  $delay_min = $delay;
		}
	      } else {
		$delay_min = $delay;
	      }
	      if ( defined $delay_max ) {
		if ( $delay > $delay_max ) {
		  $delay_max = $delay;
		}
	      } else {
		$delay_max = $delay;
	      }
	      $total_delay += $delay;
	    }
	  } else {
	    $sent = $::FAILED;
	  }
	} elsif ( defined $mail_ptr->{'from'} ) {
	  # do nothing
	}
      }

      if ( $sent == $::TRUE ) {
	$sent_count++;
      } elsif ( $sent == $::FAILED ) {
	$failed_count++;
      } else {
	# mail has not been sent yet
      }
    }
    # show data for each relay
    printf( "%3d %-20s %13s %13s",
	    $i,
	    $host,
	    &sec_to_local( $delay_min ),
	    &sec_to_local( $delay_max )
	  );
    if ( $delay_max > $delay_min ) {
      printf ( " %13s", &sec_to_local( $total_delay/$sent_count ) );
    }
    print "\n";
  }
  print "\n";

  return $::TRUE;
}

#
# display mail size by relay
#
sub show_mail_size_by_relay {
  my ( $line_count ) = @_;

  my $ndx_string;
  my $array_ptr;
  my $mail_ptr;
  my $sent;
  my $host;
  my $sent_count;
  my $failed_count;
  my $queued_count;
  my $size;
  my $size_min;
  my $size_max;
  my $size_ave;
  my $total_size;
  my $i;
  my @tmp_array;

  #
  # report on mail size by relay
  #
  print "Mail size by host: Top $line_count host(s)\n";
  print "========================================\n";
  print "\n";
  print "    Hostname                 Min Bytes     Max Bytes      Av Bytes\n";
  print "------------------------------------------------------------------\n";
  @tmp_array = sort by_array_size( keys( %::mail_records_by_host ) );

  for ( $i = 0; defined $tmp_array[$i] && $i < $line_count; $i++ ) {
    $array_ptr = $::mail_records_by_host{$tmp_array[$i]};
    $host = $tmp_array[$i];
    $sent_count = $failed_count = $queued_count = $size = $total_size = 0;
    undef $size_min;
    undef $size_max;
    foreach $ndx_string ( @$array_ptr ) {
      $sent = $::FALSE;
      $queued_count++;
      $array_ptr = $::mail_records_by_queueid{$ndx_string};
      foreach $mail_ptr ( @$array_ptr ) {
	if ( defined $mail_ptr->{'status'} ) {
	  if ( $mail_ptr->{'status'} =~ /^Sent/ ) {
	    $sent = $::TRUE;
	  } else {
	    $sent = $::FAILED;
	  }
	} elsif ( defined $mail_ptr->{'from'} ) {
	  # generate size data
	  if ( defined $mail_ptr->{'size'} ) {
	    $size = $mail_ptr->{'size'};
	    if ( defined $size_min ) {
	      if ( $size < $size_min ) {
		$size_min = $size;
	      }
	    } else {
	      $size_min = $size;
	    }
	    if ( defined $size_max ) {
	      if ( $size > $size_max ) {
		$size_max = $size;
	      }
	    } else {
	      $size_max = $size;
	    }
	    $total_size += $size;
	  }
	}
      }

      if ( $sent == $::TRUE ) {
	$sent_count++;
      } elsif ( $sent == $::FAILED ) {
	$failed_count++;
      } else {
	# mail has not been sent yet
      }
    }
    # show data for each relay
    printf( "%3d %-20s %13s %13s",
	    $i,
	    $host,
	    &bytes_to_pretty_string( $size_min ),
	    &bytes_to_pretty_string( $size_max )
	  );
    if ( $size_max > $size_min ) {
      printf( " %13s",
	      &bytes_to_pretty_string( $total_size/$queued_count )
	    );
    }
    print "\n";
  }
  print "\n";

  return $::TRUE;
}

#
# display user mail statistics
#
sub show_user_mail_stats {
  my ( $counter ) = @_;
  my $record;
  my %mail;
  my %log;
  my $i;
  my @top;

  foreach $record ( @::mail_records ) {
    %mail = %$record;
    if ( defined $mail{'from'} ) {
      # a from record, count them
      if ( defined $::mail_count{$mail{'from'}} ) {
	$::mail_count{$mail{'from'}}++;
      } else {
	$::mail_count{$mail{'from'}} = 1;
      }
    } else {
      # not a from record
    }
  }

  print "Top $counter mail senders by mail sent\n";
  print "================================================================\n";

  %log = %::mail_count;
  @top = sort by_mail_count keys( %log );
  for( $i = 0; $i < $counter & defined $top[$i]; $i++ ) {
    printf( "%3d %45s %10d\n", $i + 1, $top[$i], $log{$top[$i]} );
  }
  print "\n";

  foreach $record ( @::mail_records ) {
    %mail = %$record;
    if ( defined $mail{'from'} ) {
      # a from record, look at size
      if ( defined $::mail_size{$mail{'from'}} ) {
	$::mail_size{$mail{'from'}} += $mail{'size'};
      } else {
	$::mail_size{$mail{'from'}} = $mail{'size'};
      }
    } else {
      # not a from record
    }
  }

  print "Top $counter mail senders by bytes sent\n";
  print "================================================================\n";

  %log = %::mail_size;
  @top = sort by_mail_size keys( %log );
  for( $i = 0; $i < $counter & defined $top[$i]; $i++ ) {
    printf( "%3d %45s %10s\n", $i + 1, $top[$i], &bytes_to_pretty_string( $log{$top[$i]} ) );
  }
  print "\n";

  return $::TRUE;
}

#
# sort by size
#
sub by_mail_size {
  return $::mail_size{$b} <=> $::mail_size{$a};
}

#
# sort by count
#
sub by_mail_count {
  return $::mail_count{$b} <=> $::mail_count{$a};
}


#################################################
#
# utility functions
#
#################################################

#
# convert local time d+hh:mm:ss to total seconds
#
sub local_to_sec {
  my ( $local_time ) = @_;

  my $day;
  my $hour;
  my $min;
  my $sec;

  if ( $local_time =~ m/(\d{1,2})\:(\d{1,2})\:(\d{1,2})/ ) {
    # hh:mm:ss
    $day = 0;
    $hour = $1;
    $min = $2;
    $sec = $3;
  } elsif ( $local_time =~ m/(\d+)\+(\d{1,2})\:(\d{1,2})\:(\d{1,2})/ ) {
    # d+hh:mm:ss
    $day = $1;
    $hour = $2;
    $min = $3;
    $sec = $4;
  } else {
    # unknown format
  }

  return ( &seconds_in_a_day() * $day ) + ( &seconds_in_an_hour() * $hour ) + ( &seconds_in_a_minute() * $min ) + $sec;
}

#
# convert seconds to local time d+hh:mm:ss
#
sub sec_to_local {
  my( $seconds ) = @_;

  my $string;
  my $minutes;
  my $hours;
  my $days;

  if ( $seconds >= &seconds_in_a_day() ) {
    if ( ( $days = int( $seconds / &seconds_in_a_day() ) ) > 0 ) {
      # day
      $seconds = $seconds - ( $days * &seconds_in_a_day() );
    }
  }

  if ( $seconds >= &seconds_in_an_hour() ) { 
    if ( ( $hours = int( $seconds / &seconds_in_an_hour() ) ) > 0 ) {
      # hours
      $seconds = $seconds - ( $hours * &seconds_in_an_hour() );
    }
  }

  if ( $seconds >= &seconds_in_a_minute() ) {
    if ( ( $minutes = int( $seconds / &seconds_in_a_minute() ) ) > 0 ) {
      # minutes
      $seconds = $seconds - ( $minutes * &seconds_in_a_minute() );
    }
  }

  if ( $days > 0 ) {
    $string = sprintf( "%d+%02d:%02d:%02d", $days, $hours, $minutes, $seconds );
  } else {
    $string = sprintf( "%02d:%02d:%02d", $hours, $minutes, $seconds );
  }

  return $string;
}

#
# sort by array size
#
sub by_array_size {
  my $a_ptr;
  my $b_ptr;

  $a_ptr = $::mail_records_by_host{$a};
  $b_ptr = $::mail_records_by_host{$b};

  return @$b_ptr <=> @$a_ptr;
}

#
# do some time calcs
#
sub seconds_in_a_minute { return 60; }
sub seconds_in_an_hour { return &seconds_in_a_minute * 60; }
sub seconds_in_a_day { return &seconds_in_an_hour * 24; }
sub seconds_in_a_week { return &seconds_in_a_day * 7; }

#
# convert seconds to d+hh:mm:ss
#
sub seconds_to_pretty_string {
  my ( $seconds ) = @_;
  my $string;

  
  return $string;
}

#
# convert bytes to xGb yMb zKb
#
sub bytes_to_pretty_string {
  my ( $bytes ) = @_;
  my $string;

  if ( $bytes < 1000 ) {
    # bytes
    $string = int( $bytes );
  } elsif ( $bytes < 1000000 ) {
    # Kb
    $string = sprintf( "%1.2fKb", $bytes / "1000" );
  } elsif ( $bytes < 1000000000 ) {
    # Mb
    $string = sprintf( "%1.2fMb", $bytes / "1000000" );
  } else {
    # Gb
    $string = sprintf( "%1.2fGb", $bytes / "1000000000" );
  }

  return $string;
}

#
# convert number 1000000 to 1,000,000
#
sub number_to_pretty_string {
  my ( $number ) = @_;

  if ( $number =~ m/(\d+)(\d{3})$/ ) {
    $number = "$1,$2";
  }
  while ( $number =~ m/(\d+)(\d{3}\,.{3,})/ ) {
    $number = "$1,$2";
  }

  return $number;
}
