#!/usr/bin/perl -w
###
# Project: pflogstats
# Module: pflogstats-statistics-accounting.pm
# Type: statistics
# Description: Statistics module for accounting
# Copyright: Dr. Peter Bieringer <pbieringer at aerasec dot de>
# AERAsec GmbH <http://www.aerasec.de/>
# License: GNU GPL v2
# CVS: $Id: pflogstats-statistics-accounting.pm,v 1.48 2005/05/02 12:53:08 peter Exp $
###
###
# ChangeLog:
# 0.01
# - initial creation
# - add special "indented" print function
# 0.02
# - import printstatistics function
# 0.03
# - import acc_virtual code
# 0.04
# - support new format code style
# 0.05
# - some coding speed-ups
# - enable intermediate statistics update to prevent much memory consumption
# - do not count cloned qids for SASL
# - enhance/fix cloning (still was too much accounted)
# - review garbage collection
# - fix qid reusage issue (qids are completly cloned now)
# - catch also local pickup
# 0.06
# - reimplement logline parser for standalone usage (no longer old pflogsumm code is used)
# - tag some match patterns with "o"
# 0.07
# - fix a bug in qid match (to catch older postfix versions loglines, too)
# - fix a possible die on empty relay_ip
# 0.08
# - enhance bugfix in qid match
# - rewrite ipaddress check calling code to make this support optional
# - fix a bug in cmd match (now "qmgr" is also supported)
# - extend logline parser matches (nqmgr/bounces)
# - fix bug in debug code
# - fix scoping bug of virtual support (check_domain)
# - fix delay matching regex (negative allowed for clock strangeness issues)
# 0.09
# - minor copy&paste bugfix in waring message
# 0.10
# - fix clone id debugging
# - fix not working localhost detection
# 0.11
# - account mail 'tara' (envelope data), account around additional 1.5 %
# - account envelope data of RCPT rejects (around 2%)
# - estimate TCP overhead also (MTU 1500, 40 Byte TCP/IP, 10% ACKs from receiver)
# 0.12
# - make TCP overhead accounting switchable
# 0.13
# - fix misaccounting on local configured forwarding via a local relay
# - print accounting overhead options
# 0.14
# - apply some debug code
# 0.15
# - extend message-id based mapping with "to" address
# - minor adjustment of debug code
# 0.16
# - fix minor bug in message id mapping
# 0.17
# - replace all hash references with proper code
# 0.18
# - fix problem with <>@host loglines like "from=<<>@uke.uni-hamburg.de>"
# 0.19
# - make Perl 5.0 compatible
# 0.20
# - max width of customer list 79->75 chars
# - fix bug on users accouting
# 0.21
# - add support for new authentication method "simpleauth"
# 0.22
# - add support for option print-max-width
# 0.23
# - fix bug in a printf
# 0.24
# - add support for accounting discarded messages (at least the header)
# - fix bug in reject header accounting (8 bytes too much)
# 0.25
# - fix bug with messages longer in queue than analysis time frame
# - add counter for "smtp_to_deferred"
# 0.26
# - remove extract2ndlevel domain for customer domain file
# 0.27
# - more fixes required for 0.18 case
# 0.28
# - proper handling of bad e-mail addresses (0.18 case for rejects)
# 0.29
# - account also messages blocked by amavis
# - minor code optimization
# 0.30
# - reorganize hook names
# 0.31
# - fix problem if amavis reports 2 destination addresses (take only the first one)
# - add support for authenticated users using postfix proxy
# - add debug option to print accounting data which doesn't match customers
###
use strict;
## Local constants
my $module_type = "statistics";
my $module_name = $module_type . "-accounting";
my $module_version = "0.31";
package pflogstats::statistics::accounting;
## Export module info
$main::moduleinfo{$module_name}->{'version'} = $module_version;
$main::moduleinfo{$module_name}->{'type'} = $module_type;
$main::moduleinfo{$module_name}->{'name'} = $module_name;
## Global prototyping
#sub print_users_indented();
## Local prototyping
sub print_summary_acc_users_treeview();
sub print_domains_users_indented($;@);
## Register options
$main::options{'acc_virtual'} = \$main::opts{'acc_virtual'};
$main::options{'acc_virtualfile=s'} = \$main::opts{'acc_virtualfile'};
$main::options{'acc_customerfile=s'} = \$main::opts{'acc_customerfile'};
$main::options{'acc_nomemopt'} = \$main::opts{'acc_nomemopt'};
$main::options{'acc_noenvelopes'} = \$main::opts{'acc_noenvelopes'};
$main::options{'acc_norejects'} = \$main::opts{'acc_norejects'};
$main::options{'acc_notcpoverhead'} = \$main::opts{'acc_notcpoverhead'};
## Register calling hooks
$main::hooks{'print_users_indented'}->{$module_name} = \&print_users_indented;
$main::hooks{'checkoptions'}->{$module_name} = \&checkoptions;
$main::hooks{'beforemainloopstarts'}->{$module_name} = \&beforemainloopstarts;
$main::hooks{'print_result'}->{$module_name} = \&print_result;
$main::hooks{'help'}->{$module_name} = \&help;
$main::hooks{'loglineparser'}->{$module_name} = \&loglineparser;
# Define type
$main::types{'acc'} = 0;
## Global variables
## Local variables
my %messageUserStats;
my %messageDomainStats;
my %counters;
my %check_domains;
my %domains_customer;
# Lookup table for postfix proxy authentications
my %clients_authenticated;
my %messagelog; # Message log hash per qid
# $messagelog{$qid}->{0} : master e-mail (first occurance)
# $messagelog{$qid}->{1..x} : clones, created on e-mails with more than one recipient
my %messageid_origto;
my $qid;
my ($client, $smtpauth_username, $smtpauth_method, $smtpauth_sender);
my ($from_domain, $to_domain, $from, $to);
my $mailtarasize;
# Define localhost notation
my %localhost_notations;
$localhost_notations{'::ffff:127.0.0.1'} = 1;
$localhost_notations{'::1'} = 1;
$localhost_notations{'127.0.0.1'} = 1;
my %localrelay_notations;
$localrelay_notations{'avcheck'} = 1; # Kasperski avcheck
$localrelay_notations{'avcheckss'} = 1; # Kasperski avcheck with Spamassassin
$localrelay_notations{'spamassassin'} = 1; # Spamassassin
$localrelay_notations{'local'} = 1; # Local relay
$localrelay_notations{'none'} = 1; # No relay
$localrelay_notations{'spamss'} = 1; # No relay
## Global callable functions
# Help
sub help() {
my $helpstring = "
Type: acc
[--acc_customerfile <file>] File containing customer domains
(will be preferred for accouting)
[--acc_virtual] Use virtual file for match accounting
[--acc_virtualfile <file>] Virtual file to use (default: /etc/postfix/virtual)
[--acc_noenvelopes] Don't account estimated size of envelope on sent e-mail
[--acc_noreject] Don't account estimated size of envelope on rejected connect
[--acc_notcpoverhead] Don't account estimated TCP overhead
[--acc_nomemopt] Do no memory optimization (only for debug and check purposes recommended)
[-e] Extended logging mode parsing logfile
[--debug <debug>] Debug value
| 0x0010 : display accounting lines
| 0x0020 : information about IP address handling
| 0x0040 : information about domain name and customer file
| 0x0080 : show IPv6 addresses of clients and relays
| 0x0100 : show garbage collection statistics on memory optimization
| 0x0200 : show information about qidreusage
| 0x0400 : display reject accounting lines
| 0x0800 : display accounted messages
| 0x0001 : display matched log line
| 0x0002 : display matched amavis log line
| 0x0008 : display accounting information not matching customers
";
return $helpstring;
}
# Print users indented
sub print_users_indented() {
print_summary_acc_users_treeview();
};
sub print_message($$;$) {
my $qid = $_[0];
my $cloneid = $_[1];
print "DEBUG(acc)";
if (defined $_[2]) {
print " [" . $_[2] . "]";
};
print ": ";
my $time = ::unixtime2string($messagelog{$qid}->{'time'}) || "";
$qid = $qid || "";
if (! defined $cloneid) { $cloneid = "<undefined>" };
my $from = $messagelog{$qid}->{'from'} || "";
my $to = $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} || "";
my $size = $messagelog{$qid}->{'size'} || -1;
my $client = $messagelog{$qid}->{'client'} || "";
my $relay = $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} || "";
my $status = $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'} || "";
my $smtpauth_method = $messagelog{$qid}->{'smtpauth_method'} || "";
my $smtpauth_username = $messagelog{$qid}->{'smtpauth_username'} || "";
my $smtpauth_sender = $messagelog{$qid}->{'smtpauth_sender'} || "";
my $client_ip = $messagelog{$qid}->{'client_ip'} || "";
my $relay_ip = $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} || "";
printf "time='%s' qid=%s cloneid=%s from=%s to=%s size=%d client=%s relay=%s status=%s smtpauth_method=%s smtpauth_username=%s smtpauth_sender=%s client_ip=%s relay_ip=%s\n", $time, $qid, $cloneid, $from, $to, $size, $client, $relay, $status, $smtpauth_method, $smtpauth_username, $smtpauth_sender, $client_ip, $relay_ip;
};
# Print statistics
sub calculatestatistics(;$) {
# arg1: 1: intermediate run
my $run_intermediate;
if (! defined $_[0]) {
$run_intermediate = 0;
} else {
$run_intermediate = $_[0];
};
my $ip;
my %accounting;
my $qid;
my $time_limit = 0;
my $skip_ip_client;
my $skip_ip_relay;
my $from;
# Debug: run through messagelog and look for unset times
if (0) {
for my $qid ( keys %messagelog ) {
if (! defined $messagelog{$qid}->{'time'} ) {
$messagelog{$qid}->{'time'} = 0;
print_message($qid, 0, "timeunset");
};
};
exit 1;
};
# Run through logged messages
for $qid ( sort { $messagelog{$a}->{'time'} <=> $messagelog{$b}->{'time'} } keys %messagelog ) {
if ($run_intermediate) {
if ( ! defined $messagelog{$qid}->{'from'} ) {
# skip still not complete information on intermediate run
#print_message($qid, 0, "skipnofrom") if ($main::opts{'debug'} & 0x0100 );
next;
};
if ( ! defined $messagelog{$qid}->{'size'} ) {
#print_message($qid, $cloneid, "skipnosize") if ($main::opts{'debug'} & 0x0100 );
next;
};
if ( ! defined %{$messagelog{$qid}->{'rcpt'}}) {
# still no recipients defined, skip
#print_message($qid, 0, "skipnorcpt") if ($main::opts{'debug'} & 0x0100 );
next;
};
# Look for newest time
if (defined $messagelog{$qid}->{'time'} && $messagelog{$qid}->{'time'} > $time_limit) {
$time_limit = $messagelog{$qid}->{'time'};
};
} else {
# Fill undefined values
if ( ! defined $messagelog{$qid}->{'from'} ) { $messagelog{$qid}->{'from'} = "?" };
if ( ! defined $messagelog{$qid}->{'size'} ) { $messagelog{$qid}->{'size'} = "0" };
};
# Get 2nd-Level Domain of "from"
$from = $messagelog{$qid}->{'from'};
$from_domain = ::extract_2ndleveldomain( $from );
# Run through clones (sorry for 4 space indent)
for my $cloneid (keys %{$messagelog{$qid}->{'rcpt'}}) {
# Check for valid logentry
if (! defined $cloneid ) {
#if (! defined $messagelog{$qid}->{'rcpt'}->{$cloneid} ) {
# logentry isn't valid
next;
};
if (! defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'}) {
# Skip message without a status
#print_message($qid, $cloneid, "skipstatus") if ($main::opts{'debug'} & 0x0100 );
next;
};
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'} ne 'sent' ) {
#print_message($qid, $cloneid, "skipnotsent") if ($main::opts{'debug'} & 0x0100 );
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'} eq 'bounced' ) {
# Remove bounced message
# Free some no longer needed information
delete $messagelog{$qid}->{'rcpt'}->{$cloneid};
$counters{'skipped_status_bounced'}++;
};
# Skip message if not sent
next;
};
#print "Clone ID: " . $cloneid . "\n";
if ($run_intermediate) {
# skip still not complete information on intermediate run
if ( ! defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} ) {
#print_message($qid, $cloneid, "skipnoto") if ($main::opts{'debug'} & 0x0100 );
next;
};
if ( ! defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'} ) {
#print_message($qid, $cloneid, "skipnostatus") if ($main::opts{'debug'} & 0x0100 );
next;
};
} else {
if ( ! defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} ) { $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} = "?" };
#if ( ! defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'} ) { $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'} = "?" };
};
if ( ! defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} ) { $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} = "" };
if ( ! defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} ) { $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} = "?" };
if ( ( $messagelog{$qid}->{'from'} eq '?' || $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} eq '?' ) ) {
# Skip messages with invalid from or to
if ( $messagelog{$qid}->{'from'} eq '?' ) {
$counters{'skipped_status_invalid_from'}++;
};
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} eq '?' ) {
$counters{'skipped_status_invalid_to'}++;
};
print_message($qid, $cloneid, "invalid_from_to");
next;
};
$counters{'total'}++;
## Client checks and counters
if (! defined $messagelog{$qid}->{'accounted'}) {
# Check and fill client data with defaults
if ( ! defined $messagelog{$qid}->{'client'} ) { $messagelog{$qid}->{'client'} = "?"; };
if ( ! defined $messagelog{$qid}->{'client_ip'} ) { $messagelog{$qid}->{'client_ip'} = ""; };
if ( ! defined $messagelog{$qid}->{'smtpauth_method'} ) { $messagelog{$qid}->{'smtpauth_method'} = "?"; };
if ( ! defined $messagelog{$qid}->{'smtpauth_username'} ) { $messagelog{$qid}->{'smtpauth_username'} = "?"; };
if ( ! defined $messagelog{$qid}->{'smtpauth_sender'} ) { $messagelog{$qid}->{'smtpauth_sender'} = "?"; };
# IPv6 counters
if ( $messagelog{$qid}->{'client_ip'} ne "" ) {
if ( $ip = new Net::IP( $messagelog{$qid}->{'client_ip'} ) ) {
if ( $ip->version() == 6 && $ip->iptype() ne "IPV4MAP" ) {
$counters{'client_ipv6'}++;
print STDERR "DEBUG: IPv6 client: " . $messagelog{$qid}->{'client_ip'} . "\n" if ($main::opts{'debug'} & 0x80);
} else {
$counters{'client_ipv4'}++;
};
};
};
# SASL counters
if ( $messagelog{$qid}->{'smtpauth_method'} eq 'PLAIN' ) { $counters{'smtpauth_PLAIN'}++; };
if ( $messagelog{$qid}->{'smtpauth_method'} eq 'LOGIN' ) { $counters{'smtpauth_LOGIN'}++; };
if ( $messagelog{$qid}->{'smtpauth_method'} eq 'DIGEST-MD5' ) { $counters{'smtpauth_DIGEST-MD5'}++; };
if ( $messagelog{$qid}->{'smtpauth_method'} eq 'CRAM-MD5' ) { $counters{'smtpauth_CRAM-MD5'}++; };
if ( $messagelog{$qid}->{'smtpauth_method'} ne '!' && $messagelog{$qid}->{'smtpauth_method'} ne '?' ) { $counters{'sasl'}++; };
$messagelog{$qid}->{'accounted'} = 1;
};
## Recipient counters
# IPv6 counters
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} ne "" ) {
print STDERR "DEBUG: relay: " . $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} . "\n" if ($main::opts{'debug'} & 0x80);
if ( $ip = new Net::IP( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} ) ) {
if ( $ip->version() == 6 && ( $ip->iptype() ne "IPV4MAP") ) {
$counters{'relay_ipv6'}++;
print STDERR "DEBUG: IPv6 relay: " . $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} . "\n" if ($main::opts{'debug'} & 0x80);
} else {
$counters{'relay_ipv4'}++;
};
};
};
# Relays
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} ne '?' && $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} ne '!' ) { $counters{'relayed'}++; };
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} eq 'avcheck' ) { $counters{'relayed_avchecked'}++; };
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} eq 'avcheckss' ) { $counters{'relayed_avchecked'}++; };
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} eq 'spamss' ) { $counters{'relayed_spamassassin'}++; };
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'} eq 'local' ) { $counters{'relayed_local'}++; };
$counters{'accounted'}++;
print_message($qid, $cloneid, "opte") if (defined $main::opts{'e'} );
# Handling of virtual
if (defined $main::opts{'acc_virtual'} ) {
# Rewrite recipient for virtual accounting
if ( defined $check_domains{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}} ) {
# Map user
printf "*MAP: " . $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} . " => " . $check_domains{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}} . "\n" if ($main::opts{'debug'} & 0x0010 ) ;
$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} = "@" . $check_domains{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}};
};
};
# Get 2nd-Level Domain of "to"
$to_domain = ::extract_2ndleveldomain( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} );
# Reset flags
$accounting{'from'}->{'flag'} = 0;
$accounting{'from'}->{'direction'} = 'sent';
$accounting{'from'}->{'info'} = "";
$accounting{'to'}->{'flag'} = 0;
$accounting{'to'}->{'direction'} = 'rcvd';
$accounting{'to'}->{'info'} = "";
# print_message($qid, $cloneid, "check");
# Check IP addresses
$skip_ip_client = 0;
$skip_ip_relay = 0;
if ( $messagelog{$qid}->{'client_ip'} ne "" ) {
# Hook "testipaddress"
for my $p_hook (keys %{$main::hooks{'testipaddress'}}) {
if ( &{$main::hooks{'testipaddress'}->{$p_hook}} ($messagelog{$qid}->{'client_ip'}) != 0 ) {
$skip_ip_client = 1;
last;
};
};
};
if ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} ne "" ) {
# Hook "testipaddress"
for my $p_hook (keys %{$main::hooks{'testipaddress'}}) {
if ( &{$main::hooks{'testipaddress'}->{$p_hook}} ($messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'}) != 0 ) {
$skip_ip_relay = 1;
last;
};
};
};
## Main accounting selection code starts here
if ( $messagelog{$qid}->{'client_ip'} ne "" ) {
#print "DEBUG(client_ip): " . $messagelog{$qid}->{'client_ip'} . "\n";
#for my $key (keys %localhost_notations) {
# print $key;
# if ($key eq $messagelog{$qid}->{'client_ip'}) {
# print "+";
# } else {
# print "-";
# };
# print " ";
#};
#print "\n";
# Client connects via IP
if ( ! defined $localhost_notations{$messagelog{$qid}->{'client_ip'}} ) {
#print "DEBUG(client_ip): not localhost\n";
# Incoming e-mail from external
if ( defined $localrelay_notations{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay'}} || defined $localhost_notations{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'}} ) {
# Check for client in exclude range
#print_message($qid, $cloneid, "check1");
if ( $skip_ip_client != 0 ) {
# client_ip should be not accounted
$counters{'notaccounted_client_ip_excluded'}++;
printf "*ACC: --not accounted: client is excluded--\n" if ($main::opts{'debug'} & 0x0010 ) ;
} else {
# Incoming e-mail from external to local
if ($cloneid > 0 ) {
# cloned message, one connect, one from, several to's
printf "*ACC: --not accounted: message is a clone\n" if ($main::opts{'debug'} & 0x0010 ) ;
} else {
if ( $messagelog{$qid}->{'smtpauth_username'} ne '!' && $messagelog{$qid}->{'smtpauth_username'} ne '?' ) {
# Authenticated user, account "from" address
$accounting{'from'}->{'direction'} = 'rcvd';
$accounting{'from'}->{'flag'}++;
$accounting{'from'}->{'info'} .= "*ACC-auth-rcvd";
$from = $messagelog{$qid}->{'smtpauth_username'};
$from_domain = ::extract_2ndleveldomain( $from );
} else {
# Unauthenticated sender, account "to"
$accounting{'to'}->{'direction'} = 'rcvd';
$accounting{'to'}->{'flag'}++;
$accounting{'to'}->{'info'} .= "*ACC-4";
};
};
};
} else {
# Not a local relay
#printf "DEBUG(acc/generate) qid=%s\n", $qid;
#print_message($qid, $cloneid, "NotLocalRelay");
# Check for client and relay in exclude range
#print_message($qid, $cloneid, "check2");
if ( $skip_ip_client != 0 ) {
$counters{'notaccounted_client_ip_excluded'}++;
printf "*ACC-6a: --not accounted: client is in exclude net--\n" if ($main::opts{'debug'} & 0x0010 ) ;
} else {
printf "*ACC-: check client allowed\n" if ($main::opts{'debug'} & 0x0010 );
if ($cloneid > 0 ) {
# cloned message, one connect, one from, several to's
printf "*ACC: --not accounted: message is a clone\n" if ($main::opts{'debug'} & 0x0010 ) ;
} else {
if ( $messagelog{$qid}->{'smtpauth_username'} ne '!' && $messagelog{$qid}->{'smtpauth_username'} ne '?' ) {
# Authenticated user, account "from" address
$accounting{'from'}->{'direction'} = 'rcvd';
$accounting{'from'}->{'flag'}++;
$accounting{'from'}->{'info'} .= "*ACC-auth-rcvd";
# Map from address to ID of authenticated user
$from = $messagelog{$qid}->{'smtpauth_username'};
$from_domain = ::extract_2ndleveldomain( $from );
} else {
# Unauthenticated (by SMTP) sender, but perhaps by SMTP after POP
# Check for customer
if ( defined $domains_customer{$from_domain} ) {
# in list of customer domains
printf "*ACC-: in customer list\n" if ($main::opts{'debug'} & 0x0010 );
$accounting{'from'}->{'flag'}++;
$accounting{'from'}->{'info'} .= "*ACC-cd";
$accounting{'from'}->{'direction'} = 'rcvd';
} else {
# really unauthenticated, account "to"
printf "*ACC-: not in customer list\n" if ($main::opts{'debug'} & 0x0010 );
$accounting{'to'}->{'flag'}++;
$accounting{'to'}->{'info'} .= "*ACC-6b";
$accounting{'to'}->{'direction'} = 'rcvd';
};
};
};
};
#print_message($qid, $cloneid, "check3");
if ( ($messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} ne "") && ( $skip_ip_relay != 0 ) ) {
# relay should be not accounted
$counters{'notaccounted_relay_ip_excluded'}++;
printf "*ACC-6b: --not accounted: relay is in exclude net--\n" if ($main::opts{'debug'} & 0x0010 ) ;
} else {
printf "*ACC-: check mailrouting\n" if ($main::opts{'debug'} & 0x0010 );
if ( $messagelog{$qid}->{'smtpauth_username'} ne '!' && $messagelog{$qid}->{'smtpauth_username'} ne '?' ) {
# Authenticated user, account "from" address
$accounting{'from'}->{'direction'} = 'sent';
$accounting{'from'}->{'flag'}++;
$accounting{'from'}->{'info'} .= "*ACC-auth-sent";
$from = $messagelog{$qid}->{'smtpauth_username'};
$from_domain = ::extract_2ndleveldomain( $from );
} else {
# Check for mailrouting
if ( defined $domains_customer{$to_domain} ) {
# in list of customer domains
printf "*ACC-: in customer list\n" if ($main::opts{'debug'} & 0x0010 );
$accounting{'to'}->{'flag'}++;
$accounting{'to'}->{'info'} .= "*ACC-mr";
$accounting{'to'}->{'direction'} = 'sent';
} else {
# Hopefully Authenticated user, account "from" address
printf "*ACC-: not in customer list\n" if ($main::opts{'debug'} & 0x0010 );
$accounting{'from'}->{'flag'}++;
$accounting{'from'}->{'info'} .= "*ACC-6a";
$accounting{'from'}->{'direction'} = 'sent';
};
};
};
#print_message($qid, $cloneid, "check3a");
};
} else {
# Message send from localhost, outgoing message? perhaps sent by avceck?
if ( defined $localhost_notations{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'}} ) {
# Local
printf "*ACC: --not accounted: relay is local--\n" if ($main::opts{'debug'} & 0x0010 ) ;
} elsif ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} eq "" ) {
# Local
printf "*ACC: --not accounted: relay is not given--\n" if ($main::opts{'debug'} & 0x0010 ) ;
} else {
# Not local, check destination
#print_message($qid, $cloneid, "check4");
if ( $skip_ip_relay != 0 ) {
# relay should be not accounted
$counters{'notaccounted_relay_ip_excluded'}++;
printf "*ACC: --not accounted: relay is excluded--\n" if ($main::opts{'debug'} & 0x0010 ) ;
#} else {
# # accouting
# $messageUserStats{$messagelog{$qid}->{'to'}}->{'rcvd'}->{'count'}++;
# $messageUserStats{$messagelog{$qid}->{'to'}}->{'rcvd'}->{'size'} += $messagelog{$qid}->{'size'};
# $messageDomainStats{$to_domain}->{'rcvd'}->{'count'}++;
# $messageDomainStats{$to_domain}->{'rcvd'}->{'size'} += $messagelog{$qid}->{'size'};
# printf "*ACC: to=%s size=%s\n", $messagelog{$qid}->{'to'}, $messagelog{$qid}->{'size'};
} else {
# Account "from"
$accounting{'from'}->{'flag'}++;
$accounting{'from'}->{'info'} .= "*ACC-5";
$accounting{'from'}->{'direction'} = 'sent';
};
};
};
} else {
# client_ip is empty, locally generated or forwarded
#printf "DEBUG(acc/generate) qid=%s\n", $qid;
#print_message($qid, $cloneid, "check5");
if ( defined $localhost_notations{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'}} ) {
# Local
printf "*ACC: --not accounted: relay is local--\n" if ($main::opts{'debug'} & 0x0010 ) ;
} elsif ( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'relay_ip'} eq "" ) {
# Local
$counters{'notaccounted_relay_local'}++;
printf "*ACC: --not accounted: relay is not given--\n" if ($main::opts{'debug'} & 0x0010 ) ;
} elsif ( $skip_ip_relay != 0 ) {
# relay should be not accounted
$counters{'notaccounted_relay_ip_excluded'}++;
printf "*ACC: --not accounted: relay is excluded--\n" if ($main::opts{'debug'} & 0x0010 ) ;
} else {
if (! defined $messagelog{$qid}->{'mid'}) {
print "WARN : message with qid probably longer in queue than analysis time frame: $qid\n";
} elsif (! defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}) {
print "WARN : message with qid probably longer in queue than analysis time frame: $qid\n";
} elsif (defined $messageid_origto{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}}->{$messagelog{$qid}->{'mid'}} ) {
# Outgoing message, replace to with orig_to
printf "*MAP orig_to: " . $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} . " => " . $messageid_origto{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}}->{$messagelog{$qid}->{'mid'}} . "\n" if ($main::opts{'debug'} & 0x0010 ) ;
$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} = $messageid_origto{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}}->{$messagelog{$qid}->{'mid'}};
# Get 2nd-Level Domain of "to"
$to_domain = ::extract_2ndleveldomain( $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'} );
};
if ( defined $domains_customer{$to_domain} ) {
# in list of customer domains
printf "*ACC-: to_domain in customer list\n" if ($main::opts{'debug'} & 0x0010 );
$accounting{'to'}->{'flag'}++;
$accounting{'to'}->{'info'} .= "*ACC-7a";
$accounting{'to'}->{'direction'} = 'sent';
} else {
# really unauthenticated, account "from"
$accounting{'from'}->{'flag'}++;
$accounting{'from'}->{'info'} .= "*ACC-7a";
$accounting{'from'}->{'direction'} = 'sent';
};
};
};
# Check accounting
if ( $accounting{'from'}->{'flag'} < 0 || $accounting{'to'}->{'flag'} < 0 || ($accounting{'from'}->{'flag'} + $accounting{'to'}->{'flag'} > 2) ) {
die "Accounting trouble, this should never happen";
};
## Calculate mail envelope data bytes (estimation)
#
# 220 hostname.example.com ESMTP [4+sizeof(localhelostring)+6+2=12+sizeof(localhelostring)]
# HELO client [5+sizeof(remotehelostring)+2=7+sizeof(remotehelostring)]
# 250 hostname.example.com [4+sizeof(hostname)+2=6+sizeof(hostname)]
# MAIL FROM: <sender@example.com> [11+1+sizeof(sender)+1+2=15+sizeof(sender)]
# 250 Ok [6+2=8]
# RCPT TO: <recipient@example.com> [9+1+sizeof(rcpt)+1+2=13+sizeof(rcpt)]
# 250 Ok [6+2=8]
# DATA [4+2=6]
# 354 End data with <CR><LF>.<CR><LF> [35+2=37]
# text (size) [=size]
# . [1+2=3]
# 250 Ok: queued as xxxxxxxxxx [18+2+sizeof(qid)+2=22+sizeof(qid)]
# QUIT [4+2=6]
# 221 Bye [7+2=9]
# 3x40 SYN, 4x40 FIN, 12x40 ACK = 760
# Assume MTU 1500 and 10% ACKs by receiver, uncalculable: TCP options
if (! defined $main::opts{'acc_noenvelopes'} ) {
# $mailtarasize = 12 + length($main::opts{'myhostname'}) + 7 + 6 + length($main::opts{'myhostname'}) + 15 + length($messagelog{$qid}->{'from'}) + 8 + 13 + length($messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}) + 8 + 6 + 37 + 3 + 22 + length($qid) + 6 + 9i + 760 + int($messagelog{$qid}->{'size'} / 1500) * 40;
$mailtarasize = 158 + 2* length($main::opts{'myhostname'}) + length($messagelog{$qid}->{'from'}) + length($messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}) + length($qid);
} else {
$mailtarasize = 0;
};
if ( ! defined $main::opts{'acc_notcpoverhead'} ) {
$mailtarasize += 760 + int( ($messagelog{$qid}->{'size'} + 1459) / 1460) * 44;
};
# Account now
if ( $accounting{'from'}->{'flag'} + $accounting{'to'}->{'flag'} > 0 ) {
print_message($qid, $cloneid, "account") if ($main::opts{'debug'} & 0x0010 );
};
if ( $accounting{'from'}->{'flag'} >= 1 ) {
$messageUserStats{$from}->{$accounting{'from'}->{'direction'}}->{'count'} ++;
$messageUserStats{$from}->{$accounting{'from'}->{'direction'}}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
$messageDomainStats{$from_domain}->{$accounting{'from'}->{'direction'}}->{'count'} ++;
$messageDomainStats{$from_domain}->{$accounting{'from'}->{'direction'}}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
if ( $accounting{'from'}->{'flag'} >= 2 ) {
if ( $accounting{'from'}->{'direction'} eq "sent" ) {
$messageUserStats{$from}->{'rcvd'}->{'count'} ++;
$messageUserStats{$from}->{'rcvd'}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
$messageDomainStats{$from_domain}->{'rcvd'}->{'count'} ++;
$messageDomainStats{$from_domain}->{'rcvd'}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
} else {
$messageUserStats{$from}->{'sent'}->{'count'} ++;
$messageUserStats{$from}->{'sent'}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
$messageDomainStats{$from_domain}->{'sent'}->{'count'} ++;
$messageDomainStats{$from_domain}->{'sent'}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
};
};
if ($main::opts{'debug'} & 0x0010 || $main::opts{'debug'} & 0x800 || $main::opts{'debug'} & 0x0008) {
if ( $main::opts{'debug'} & 0x0008 && (defined $domains_customer{$to_domain} || defined $domains_customer{$from_domain})) {
# nothing to do
} else {
printf "%s: from=%s qid=%s size=%s (x %d)\n", $accounting{'from'}->{'info'}, $messagelog{$qid}->{'from'}, $qid, $messagelog{$qid}->{'size'}, $accounting{'from'}->{'flag'};
};
};
};
if ( $accounting{'to'}->{'flag'} >= 1 ) {
$messageUserStats{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}}->{$accounting{'to'}->{'direction'}}->{'count'} ++;
$messageUserStats{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}}->{$accounting{'to'}->{'direction'}}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
$messageDomainStats{$to_domain}->{$accounting{'to'}->{'direction'}}->{'count'} ++;
$messageDomainStats{$to_domain}->{$accounting{'to'}->{'direction'}}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
if ( $accounting{'to'}->{'flag'} >= 2 ) {
if ( $accounting{'to'}->{'direction'} eq "sent" ) {
$messageUserStats{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}}->{'sent'}->{'count'} ++;
$messageUserStats{$messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}}->{'sent'}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
$messageDomainStats{$from_domain}->{'sent'}->{'count'} ++;
$messageDomainStats{$from_domain}->{'sent'}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
} else {
$messageUserStats{$messagelog{$qid}->{'to'}}->{'rcvd'}->{'count'} ++;
$messageUserStats{$messagelog{$qid}->{'to'}}->{'rcvd'}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
$messageDomainStats{$from_domain}->{'rcvd'}->{'count'} ++;
$messageDomainStats{$from_domain}->{'rcvd'}->{'size'} += $messagelog{$qid}->{'size'} + $mailtarasize;
};
};
if ($main::opts{'debug'} & 0x0010 || $main::opts{'debug'} & 0x800 || $main::opts{'debug'} & 0x0008) {
if ( $main::opts{'debug'} & 0x0008 && (defined $domains_customer{$to_domain} || defined $domains_customer{$from_domain})) {
# nothing to do
} else {
printf "%s: to=%s qid=%s size=%s + overhead=%d (x %d)\n", $accounting{'to'}->{'info'}, $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'to'}, $qid, $messagelog{$qid}->{'size'}, $mailtarasize, $accounting{'to'}->{'flag'};
};
};
};
if ( $accounting{'from'}->{'flag'} + $accounting{'to'}->{'flag'} > 0 ) {
# print "\n" if ($main::opts{'debug'} & 0x800 ) ;
} else {
print "\n" if ($main::opts{'debug'} & 0x0010 );
};
# Free some no longer needed information
delete $messagelog{$qid}->{'rcpt'}->{$cloneid};
LABEL_statend:
}; # end of cloneid loop
if (! $run_intermediate) {
# Last run, so remove qid
delete $messagelog{$qid};
};
}; # end of qid loop
if ($run_intermediate) {
if ($time_limit == 0) {
printf "DEBUG(acc): Garbage collection skipped because of missing time limit\n" if ($main::opts{'debug'} & 0x0100 ) ;
return;
};
# offset: (offset is needed to catch clones which appear e.g. on next line after this intermediate run)
#$time_limit -= 7200; # 2 hours
$time_limit -= 14400; # 4 hours
printf "DEBUG(acc): Garbage collection on intermediate run, time limit: %d\n", $time_limit if ($main::opts{'debug'} & 0x0100 ) ;
# Garbage collection
my $stat_qids = 0;
my $stat_qids_removed = 0;
my $stat_qids_extime = 0;
my $stat_qids_deferred = 0;
my $stat_qids_stillrecipients = 0;
my $stat_qids_stillnorecipients = 0;
# Remove all qids which are older than limit
for my $qid ( keys %messagelog ) {
$stat_qids++;
#printf "DEBUG(acc): Garbage collection on intermediate run, check: %d\n", $messagelog{$qid}->{'time'} if ($main::opts{'debug'} & 0x0100 ) ;
if ( $messagelog{$qid}->{'time'} < $time_limit) {
$stat_qids_extime++;
if (! defined %{$messagelog{$qid}->{'rcpt'}}) {
# still no recipients defined after that delay so delete
# normally "client rejects"
#print_message($qid, 0, "skipstillnorecipients");
$stat_qids_stillnorecipients++;
delete $messagelog{$qid};
$stat_qids_removed++;
next;
};
if (scalar (keys %{$messagelog{$qid}->{'rcpt'}}) > 0 ) {
# Still some recipients defined (mean not accounted)
#if ($main::opts{'debug'} & 0x0100) {
# for my $cloneid (keys %{$messagelog{$qid}->{'rcpt'}}) {
# print_message($qid, $cloneid, "skipstillrecipients");
# };
#};
if ($main::opts{'debug'} & 0x0100) {
for my $cloneid (keys %{$messagelog{$qid}->{'rcpt'}}) {
print_message($qid, $cloneid, "skipstillrecipients");
$stat_qids_stillrecipients++;
if (defined $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'} && $messagelog{$qid}->{'rcpt'}->{$cloneid}->{'status'} eq "deferred") {
$stat_qids_deferred++;
};
};
};
next;
};
#printf "DEBUG(acc): Garbage collection on intermediate run, remove: %d\n", $messagelog{$qid}->{'time'} if ($main::opts{'debug'} & 0x0100 ) ;
# old enough, so remove
delete $messagelog{$qid};
$stat_qids_removed++;
};
};
printf "DEBUG(acc): Garbage collection on intermediate run: removed=%d (%d %%) extime=%d count=%d deferred=%d stillrecipients=%d stillnorecipients=%d\n", $stat_qids_removed, (($stat_qids_removed * 100) / $stat_qids), $stat_qids_extime, $stat_qids, $stat_qids_deferred, $stat_qids_stillrecipients, $stat_qids_stillnorecipients if ($main::opts{'debug'} & 0x0100 ) ;
};
};
# Print result
sub print_result() {
return if ( $main::types{'acc'} == 0);
calculatestatistics();
my $format;
# Fill still undefined fields (to avoid warnings on sum)
for my $key (keys %messageUserStats) {
if ( ! defined $messageUserStats{$key}->{'sent'}->{'count'} ) { $messageUserStats{$key}->{'sent'}->{'count'} = 0; };
if ( ! defined $messageUserStats{$key}->{'sent'}->{'size'} ) { $messageUserStats{$key}->{'sent'}->{'size'} = 0; };
if ( ! defined $messageUserStats{$key}->{'rcvd'}->{'count'} ) { $messageUserStats{$key}->{'rcvd'}->{'count'} = 0; };
if ( ! defined $messageUserStats{$key}->{'rcvd'}->{'size'} ) { $messageUserStats{$key}->{'rcvd'}->{'size'} = 0; };
};
for my $key (keys %messageDomainStats) {
if ( ! defined $messageDomainStats{$key}->{'sent'}->{'count'} ) { $messageDomainStats{$key}->{'sent'}->{'count'} = 0 };
if ( ! defined $messageDomainStats{$key}->{'sent'}->{'size'} ) { $messageDomainStats{$key}->{'sent'}->{'size'} = 0 };
if ( ! defined $messageDomainStats{$key}->{'rcvd'}->{'count'} ) { $messageDomainStats{$key}->{'rcvd'}->{'count'} = 0 };
if ( ! defined $messageDomainStats{$key}->{'rcvd'}->{'size'} ) { $messageDomainStats{$key}->{'rcvd'}->{'size'} = 0 };
};
print "\n# Accounting data also contains following overheads:\n";
print "# + envelope data size (partially estimated)\n" if (! defined $main::opts{'acc_noenvelopes'});
print "# + reject data size (partially estimated)\n" if (! defined $main::opts{'acc_norejects'});
print "# + TCP overhead (partially estimated)\n" if (! defined $main::opts{'acc_notcpoverhead'});
# Print statistics
# Format: treeview
if (defined $main::format{"treeview"}) {
$format = "treeview";
print "\n\nWARNING(acc): Format '" . $format . "' is currently not supported!\n\n";
};
# Format: computer
if (defined $main::format{"computer"}) {
$format = "computer";
::print_headline("ACCOUNTING statistics", $format);
::print_timerange($format);
print_summary_acc_domains_short();
};
# Format: indented
if (defined $main::format{"indented"}) {
$format = "indented";
::print_timerange($format);
if ( defined $main::opts{'show_users'} ) {
## Hook 'print_users_indented'
my $hookhere = 'print_users_indented';
my $p_hookshere = $main::hooks{$hookhere};
for my $p_hook (keys %$p_hookshere) {
# print "Call hook: " . $hookhere . "/" . $p_hook . "\n";
my $p_sub = $main::hooks{$hookhere}->{$p_hook};
&$p_sub();
};
} else {
print "WARNING(acc): format 'indented' is currently only supported on using option 'show_users'\n";
};
};
# Format: txttable
if (defined $main::format{"txttable"}) {
$format = "txttable";
::print_headline("ACCOUNTING statistics", $format);
::print_timerange($format);
# Print special status
print '=' x $main::opts{'print-max-width'} . "\n";
print "Summary:\n";
print '-' x $main::opts{'print-max-width'} . "\n";
for my $key (sort keys %counters) {
printf "%-*s : %d\n", $main::opts{'print-max-width'} - 35, $key, $counters{$key};
};
print '=' x $main::opts{'print-max-width'} . "\n";
# Print status
print_summary_acc_users() if ( defined $main::opts{'show_users'} );
print_summary_acc_domains();
print_summary_acc_domains_short();
};
print "\n\n";
};
# Check options
sub checkoptions() {
# Default value for: acc_virtualfile
if (! defined $main::opts{'acc_virtualfile'} ) {
$main::opts{'acc_virtualfile'} = "/etc/postfix/virtual";
};
};
# Init
sub beforemainloopstarts() {
# for my $key (keys %localhost_notations) { print $key . "\n"; };
if (defined $main::opts{'acc_virtual'} ) {
# Read virtual file
print STDERR "INFO: read virtual file: " . $main::opts{'acc_virtualfile'} . " \n" if ($main::opts{'debug'} & 0x0040);
# read in the virtual file and analyse domains
open(VIRTUAL,"<" . $main::opts{'acc_virtualfile'}) || die "ERROR: cannot open: " . $main::opts{'acc_virtualfile'};
while(<VIRTUAL>) {
$_ =~ s/([^#]*)#.*/$1/o; # Remove comments
$_ =~ s/[[:space:][:cntrl:]]+$//o; # Remove trailing spaces and control chars
$_ =~ s/^[[:space:][:cntrl:]]+//o; # Remove leading spaces and control chars
if ( length($_) == 0 ) { next; }; # Skip empty lines
printf STDERR $_ . "\n" if ($main::opts{'debug'} & 0x0040);
# Split
my ($src, $dst) = split /[[:space:]]+/, $_, 2;
print STDERR "DEBUG: src=" . $src . " dst=" . $dst . "\n" if ($main::opts{'debug'} & 0x0040);
# Skip postfix virtual domain style
if ( ! ($src =~ /@/o) ) { next; };
my $domain = ::extract_domain($src);
# Run through multible destinations
print STDERR "Dest: $dst\n" if ($main::opts{'debug'} & 0x0040);
foreach my $d (split /[[:space:],]+/, $dst) {
print STDERR "Destsplit: " . $d . "\n" if ($main::opts{'debug'} & 0x0040);
# Check for dst contains a domain
if ( $d =~ /@/o ) {
} else {
$d .= "@" . $main::opts{'mydomainname'};
print STDERR "DEBUG: append mydomainname: " . $d . "\n" if ($main::opts{'debug'} & 0x0040);
};
if (! defined $check_domains{$d}) {
# Ok, first time
$check_domains{$d} = $domain;
print STDERR "DEBUG: put dst=" . $d . " domain=" . $domain . "\n" if ($main::opts{'debug'} & 0x0040);
} else {
# More than once, check equal
if ( $check_domains{$d} ne $domain ) {
warn "WARN: virtual has a non uniq reverse domain mapping problem:\nAlready mapped: " . $d . " => " . $check_domains{$d} ."\nNow requested but skipped: " . $d . " => " . $domain . "\n";
};
};
};
};
close(VIRTUAL);
print '='x79 . "\n";
print "Virtual user => domain mapping\n";
print '-'x79 . "\n";
for my $key (keys %check_domains ) {
print $key . " => " . $check_domains{$key} . "\n";
};
print '='x79 . "\n";
};
if (defined $main::opts{'acc_customerfile'} ) {
print STDERR "INFO: read customer file: " . $main::opts{'acc_customerfile'} . " \n" if ($main::opts{'debug'} & 0x0040);
# read in the customers file
open(CUSTOMERS,"<" . $main::opts{'acc_customerfile'}) || die "ERROR: cannot open: " . $main::opts{'acc_customerfile'};
while(<CUSTOMERS>) {
$_ =~ s/([^#]*)#.*/$1/o; # Remove comments
$_ =~ s/[[:space:][:cntrl:]]+$//o; # Remove trailing spaces and control chars
$_ =~ s/^[[:space:][:cntrl:]]+//o; # Remove leading spaces and control chars
if ( length($_) == 0 ) { next; }; # Skip empty lines
printf STDERR $_ if ($main::opts{'debug'} & 0x0040);
# Split
if( /^\s*(\S+)\s*[#]*.*$/ ) {
$domains_customer{$1} = 1;
};
};
close(CUSTOMERS);
print '=' x $main::opts{'print-max-width'} . "\n";
print "Customer domains\n";
print '-' x $main::opts{'print-max-width'} . "\n";
for my $key (sort keys %domains_customer ) {
print $key . "\n";
};
print '=' x $main::opts{'print-max-width'} . "\n";
};
};
## Special local parsers
sub logline_cmd_smtp(\$\$\$) {
return if ( $main::types{'acc'} == 0);
#if (! defined $_[0]) { die "Missing qid pointer (arg1)"; };
#if (! defined $_[1]) { die "Missing unixtime pointer (arg2)"; };
#if (! defined $_[2]) { die "Missing logline pointer (arg3)"; };
# Check whether qid was already used
if (defined $messagelog{${$_[0]}}->{'time'}) {
# possible qid reusage (but not probably inbetween 30 min)
if ( abs($messagelog{${$_[0]}}->{'time'} - ${$_[1]}) > 1800 ) {
printf "WARNING(acc/logline_cmd_smtp): qid reusage qid=%s time_old='%s' time_new='%s' delta=%d sec\n", ${$_[0]}, ::unixtime2string($messagelog{${$_[0]}}->{'time'}), ::unixtime2string(${$_[1]}), ${$_[1]} - $messagelog{${$_[0]}}->{'time'} if ($main::opts{'debug'} & 0x0200 );;
# Generate a new qid
my $newqid;
do {
$newqid = sprintf "%s%04x", ${$_[0]}, rand(0x10000);
} until(! defined $messagelog{$newqid});
# Save orig qid content
$messagelog{$newqid} = $messagelog{${$_[0]}};
$counters{'cmd_qidreusage'}++;
#printf "DEBUG(acc/logline_cmd_smt): qid reusage check time=%s\n", $messagelog{$newqid}->{'time'};
# Delete orig qid
delete $messagelog{${$_[0]}};
};
};
# Set time
$messagelog{${$_[0]}}->{'time'} = ${$_[1]};
# Auth found flag
my $flag_auth_extracted = 0;
## Extract optional SMTP authentication data
if ((($client, $smtpauth_method, $smtpauth_username) = ${$_[2]} =~ / client=([^,]+), sasl_method=([^,]+), sasl_username=([^,]+)$/o ) == 3) {
$messagelog{${$_[0]}}->{'client'} = $client;
$messagelog{${$_[0]}}->{'smtpauth_method'} = $smtpauth_method;
$messagelog{${$_[0]}}->{'smtpauth_username'} = $smtpauth_username;
$messagelog{${$_[0]}}->{'smtpauth_sender'} = "!";
$flag_auth_extracted = 1;
} elsif ((($client, $smtpauth_method, $smtpauth_username) = ${$_[2]} =~ / client=([^,]+), simpleauth_method=([^,]+), simpleauth_username=([^,]+)$/o ) == 3) {
$messagelog{${$_[0]}}->{'client'} = $client;
$messagelog{${$_[0]}}->{'smtpauth_method'} = $smtpauth_method;
$messagelog{${$_[0]}}->{'smtpauth_username'} = $smtpauth_username;
$messagelog{${$_[0]}}->{'smtpauth_sender'} = "!";
$flag_auth_extracted = 1;
} elsif ((($client, $smtpauth_sender) = ${$_[2]} =~ / client=([^,]+), sasl_sender=([^,]+)$/o) == 2) {
# SMTP authenticated sender
$messagelog{${$_[0]}}->{'client'} = $client;
$messagelog{${$_[0]}}->{'smtpauth_sender'} = $smtpauth_sender;
$messagelog{${$_[0]}}->{'smtpauth_method'} = "!";
$messagelog{${$_[0]}}->{'smtpauth_username'} = "!";
$flag_auth_extracted = 1;
} elsif ((($client, $smtpauth_sender) = ${$_[2]} =~ / client=([^,]+), simpleauth_sender=([^,]+)$/o) == 2) {
# SMTP authenticated sender
$messagelog{${$_[0]}}->{'client'} = $client;
$messagelog{${$_[0]}}->{'smtpauth_sender'} = $smtpauth_sender;
$messagelog{${$_[0]}}->{'smtpauth_method'} = "!";
$messagelog{${$_[0]}}->{'smtpauth_username'} = "!";
$flag_auth_extracted = 1;
} elsif ((($client) = ${$_[2]} =~ / client=([^,]+)$/o ) == 1) {
$messagelog{${$_[0]}}->{'client'} = $client;
$messagelog{${$_[0]}}->{'smtpauth_method'} = "!";
$messagelog{${$_[0]}}->{'smtpauth_username'} = "!";
$messagelog{${$_[0]}}->{'smtpauth_sender'} = "!";
};
if (! defined $client) {
warn "*** Unmatched client log line:\n" . $_ . "\n*** Fix code!\n";
};
## Check clients_authenticated lookup table
if ($flag_auth_extracted == 1) {
if (defined $clients_authenticated{$client}->{'smtpauth_unixtime'}) {
# Client authentication extracted here, cleanup outdated entry in lookup table
undef $clients_authenticated{$client};
}
} else {
if (defined $clients_authenticated{$client}->{'smtpauth_unixtime'}) {
# Client authentication exists in lookup table
# Compare times
if (${$_[1]} - $clients_authenticated{$client}->{'smtpauth_unixtime'} < 600) {
# Authentication time < 5 min, take information
$messagelog{${$_[0]}}->{'smtpauth_method'} = $clients_authenticated{$client}->{'smtpauth_method'};
$messagelog{${$_[0]}}->{'smtpauth_username'} = $clients_authenticated{$client}->{'smtpauth_username'};
$messagelog{${$_[0]}}->{'smtpauth_sender'} = $clients_authenticated{$client}->{'smtpauth_sender'};
print "DEBUG(acc/logline_cmd_smtp): authenticated client: client=" . $client . " authenticated at unixtime=" . $clients_authenticated{$client}->{'smtpauth_unixtime'} . " message further processed at unixtime=" . ${$_[1]} . "\n" if ($main::opts{'debug'} & 0x10);
# Cleanup no longer required entry
# undef $clients_authenticated{$client};
} else {
print "NOTICE(acc/logline_cmd_smtp): authenticated client too long ago: client=" . $client . " authenticated at unixtime=" . $clients_authenticated{$client}->{'smtpauth_unixtime'} . " message further processed at unixtime=" . ${$_[1]} . "\n";
undef $clients_authenticated{$client};
};
};
};
# Extract IP address
if ( $client =~ /.*\[(.*)\]/o ) {
$messagelog{${$_[0]}}->{'client_ip'} = $1;
};
$counters{'cmd_smtp'}++;
};
sub logline_cmd_cleanup(\$\$\$) {
return if ( $main::types{'acc'} == 0);
#if (! defined $_[0]) { die "Missing qid pointer (arg1)"; };
#if (! defined $_[1]) { die "Missing unixtime pointer (arg2)"; };
#if (! defined $_[2]) { die "Missing logline pointer (arg3)"; };
if (! defined $messagelog{${$_[0]}}->{'time'}) {
$messagelog{${$_[0]}}->{'time'} = ${$_[1]};
};
## Extract message id
if (${$_[2]} =~ / message-id=([^ ]*)$/o ) {
$messagelog{${$_[0]}}->{'mid'} = $1;
};
};
sub logline_cmd_pickup(\$\$\$) {
return if ( $main::types{'acc'} == 0);
#if (! defined $_[0]) { die "Missing qid pointer (arg1)"; };
#if (! defined $_[1]) { die "Missing unixtime pointer (arg2)"; };
#if (! defined $_[2]) { die "Missing logline pointer (arg3)"; };
# Check whether qid was already used
if (defined $messagelog{${$_[0]}}->{'time'}) {
# possible qid reusage (but not probably inbetween 30 min)
if ( abs($messagelog{${$_[0]}}->{'time'} - ${$_[1]}) > 1800 ) {
printf "WARNING(acc/logline_cmd_smtp): qid reusage qid=%s time_old='%s' time_new='%s' delta=%d sec\n", ${$_[0]}, ::unixtime2string($messagelog{${$_[0]}}->{'time'}), ::unixtime2string(${$_[1]}), ${$_[1]} - $messagelog{${$_[0]}}->{'time'} if ($main::opts{'debug'} & 0x0200 );;
# Generate a new qid
my $newqid;
do {
$newqid = sprintf "%s%04x", ${$_[0]}, rand(0x10000);
} until(! defined $messagelog{$newqid});
# Save orig qid content
$messagelog{$newqid} = $messagelog{${$_[0]}};
$counters{'cmd_qidreusage'}++;
#printf "DEBUG(acc/logline_cmd_smt): qid reusage check time=%s\n", $messagelog{$newqid}->{'time'};
# Delete orig qid
delete $messagelog{${$_[0]}};
};
};
# Set time
$messagelog{${$_[0]}}->{'time'} = ${$_[1]};
$counters{'cmd_qid'}++;
$counters{'cmd_pickup'}++;
};
sub logline_cmd_from(\$\$\$\$) {
return if ( $main::types{'acc'} == 0);
#if (! defined $_[0]) { die "Missing qid pointer (arg1)"; };
#if (! defined $_[1]) { die "Missing unixtime pointer (arg2)"; };
#if (! defined $_[2]) { die "Missing addr pointer (arg3)"; };
#if (! defined $_[3]) { die "Missing size pointer (arg4)"; };
if (! defined $messagelog{${$_[0]}}->{'time'}) {
$messagelog{${$_[0]}}->{'time'} = ${$_[1]};
};
$messagelog{${$_[0]}}->{'from'} = ${$_[2]};
$messagelog{${$_[0]}}->{'size'} = ${$_[3]};
$counters{'cmd_from'}++;
};
sub logline_cmd_to(\$\$\$\$\$) {
return if ( $main::types{'acc'} == 0);
#if (! defined $_[0]) { die "Missing qid pointer (arg1)"; };
#if (! defined $_[1]) { die "Missing unixtime pointer (arg2)"; };
#if (! defined $_[2]) { die "Missing addr pointer (arg3)"; };
#if (! defined $_[3]) { die "Missing status pointer (arg4)"; };
#if (! defined $_[4]) { die "Missing relay pointer (arg5)"; };
if (! defined $messagelog{${$_[0]}}->{'time'}) {
$messagelog{${$_[0]}}->{'time'} = ${$_[1]};
};
# Initial
if (! defined $messagelog{${$_[0]}}->{'count'}) {
$messagelog{${$_[0]}}->{'count'} = 0;
};
# Ok, one or more "to" already filled, check for equal and update
for my $cloneid (keys %{$messagelog{${$_[0]}}->{'rcpt'}}) {
if ( $messagelog{${$_[0]}}->{'rcpt'}->{$cloneid}->{'to'} eq ${$_[2]} ) {
# same "to" as already filled, check status
if ( $messagelog{${$_[0]}}->{'rcpt'}->{$cloneid}->{'status'} ne "sent" ) {
if (${$_[3]} eq "bounced") {
# Message bounced
$counters{'cmd_to_bounced'}++;
# Remove this clone
delete $messagelog{${$_[0]}}->{'rcpt'}->{$cloneid};
goto ("LABEL_logline_cmd_to_end");
};
if (${$_[3]} eq "deferred") {
# Message deferred
$counters{'cmd_to_deferred'}++;
};
# update status and relay
$messagelog{${$_[0]}}->{'rcpt'}->{$cloneid}->{'status'} = ${$_[3]};
$messagelog{${$_[0]}}->{'rcpt'}->{$cloneid}->{'relay'} = ${$_[4]};
if ( ${$_[4]} =~ /.*\[(.*)\]/o ) {
$messagelog{${$_[0]}}->{'rcpt'}->{$cloneid}->{'relay_ip'} = $1;
# print STDERR "DEBUG(acc/logline_cmd_to): relay_ip=" . $messagelog{${$_[0]}}->{'rcpt'}->{0}->{'relay_ip'} . "\n";
};
goto ("LABEL_logline_cmd_to_end");
} else {
# Same "to", but first already sent, so double recipient
goto ("LABEL_logline_cmd_to_do_clone");
};
};
};
LABEL_logline_cmd_to_do_clone:
# No entry found
if (${$_[3]} eq "bounced") {
# Message bounced
$counters{'cmd_to_bounced'}++;
# do not create a new entry
return;
};
printf STDERR "DEBUG(acc): create a new clone from=%s to=%s\n", $messagelog{${$_[0]}}->{'from'}, ${$_[2]} if ($main::opts{'debug'} & 0x0040 ) ;
# Create a new rcpt entry
#printf STDERR "INFO: more than one recipient, clone id %s -> %s\n", ${$_[0]}, $newqid if ($main::opts{'debug'} & 0x0040 ) ;
$messagelog{${$_[0]}}->{'rcpt'}->{$messagelog{${$_[0]}}->{'count'}}->{'to'} = ${$_[2]};
$messagelog{${$_[0]}}->{'rcpt'}->{$messagelog{${$_[0]}}->{'count'}}->{'status'} = ${$_[3]};
$messagelog{${$_[0]}}->{'rcpt'}->{$messagelog{${$_[0]}}->{'count'}}->{'relay'} = ${$_[4]};
if ( ${$_[4]} =~ /.*\[(.*)\]/o ) {
$messagelog{${$_[0]}}->{'rcpt'}->{$messagelog{${$_[0]}}->{'count'}}->{'relay_ip'} = $1;
# print STDERR "DEBUG(acc/logline_cmd_to): relay_ip=" . $messagelog{${$_[0]}}->{'rcpt'}->{$messagelog{${$_[0]}}->{'count'}}->{'relay_ip'} . "\n";
};
$messagelog{${$_[0]}}->{'count'}++;
LABEL_logline_cmd_to_end:
$counters{'cmd_to'}++;
if (! defined $main::opts{'acc_nomemopt'}) {
#if (($counters{'cmd_to'} % 1000) == 0) {
if (($counters{'cmd_to'} % 100) == 0) {
print "DEBUG(acc): intermediate statistics calculation\n" if ($main::opts{'debug'} & 0x100);
calculatestatistics(1);
};
};
};
# Parse logline
sub loglineparser(\$\$) {
return if ( $main::types{'acc'} == 0);
if (! defined $_[0]) { die "Missing time pointer (arg1)"; };
if (! defined $_[1]) { die "Missing logline pointer (arg2)"; };
my ($cmd, $qid, $rest);
my ($addr, $orig_to, $relay, $delay, $status, $size);
my ($client_ip, $message, $from, $to, $helo);
# Example: Feb 28 13:19:59 host amavis[1296]: (01296-01) Blocked SPAM, [192.167.219.83] [20.200.15.23] <sender@domain.example> -> <account@domain.example>, Message-ID: <4996irh843k16$08566$x21my96@Clementd725utz505e5b>, Hits: 11.322, Size: 62306, 2629 ms
# Example: Feb 28 14:26:57 host amavis[1088]: (01088-09) Blocked INFECTED (EICAR-Test-File), [IPv6:3ffe::1] [3ffe::1] <sender@domain.example> -> <account@domain.example>, quarantine: virus-20050228-142656-01088-09, Message-ID: <20050228132655.GA7155@host.domain.example>, Hits: -, Size: 1147, 1180 NOTICEif ( ${$_[1]} =~ /^.*\samavis\[.*\]: \(([^\)]+)\) Blocked /io ) {
if ( ${$_[1]} =~ /^.*\samavis\[.*\]: \(([^\)]+)\) Blocked /io ) {
print "DEBUG(acc/loglineparser): matched: '" . ${$_[1]} . "'\n" if ($main::opts{'debug'} & 0x2);
# Special treatment of amavis blocked messages
$rest = "";
if ( ${$_[1]} =~ /^.*\samavis\[.*\]: \(([^\)]+)\) Blocked SPAM, \[([^]]+)\].* <(.*)> -> <([^,]*)>,.*, Size: ([0-9]+),/io ) {
$qid = $1;
$client_ip = $2;
$from = $3;
# Note that only the first recipient will be used here (usually, a second one has the same domain)
$to = $4;
$size = $5;
$message = "SPAM";
} elsif ( ${$_[1]} =~ /^.*\samavis\[.*\]: \(([^\)]+)\) Blocked INFECTED \(([^)]+)\), \[([^]]+)\].* <(.*)> -> <([^,]*)>,.*, Size: ([0-9]+),/io ) {
$qid = $1;
$rest = $2;
$client_ip = $3;
$from = $4;
# Note that only the first recipient will be used here (usually, a second one has the same domain)
$to = $5;
$size = $6;
$message = "INFECTED";
} elsif ( ${$_[1]} =~ /^.*\samavis\[.*\]: \(([^\)]+)\) Blocked SPAM, \[([^]]+)\].* <(.*)> -> <([^,]*)>,/io ) {
$qid = $1;
$client_ip = $2;
$from = $3;
$to = $4;
$size = 0;
$message = "SPAM";
warn "NOTICE: enable logging of 'Size' in amavis: " . ${$_[1]};
} elsif ( ${$_[1]} =~ /^.*\samavis\[.*\]: \(([^\)]+)\) Blocked INFECTED \(([^)]+)\), \[([^]]+)\].* <(.*)> -> <([^,]*)>,/io ) {
$qid = $1;
$rest = $2;
$client_ip = $3;
$from = $4;
$to = $5;
$size = 0;
$message = "INFECTED";
warn "NOTICE: enable logging of 'Size' in amavis: " . ${$_[1]};
} else {
warn "WARN: unsupported log line: " . ${$_[1]};
return;
};
print "DEBUG(acc/loglineparser): id=$qid message=$message rest=$rest client_ip=$client_ip from=$from to=$to size=$size\n" if ($main::opts{'debug'} & 0x2);
# Check IP addresses
if ( $client_ip ne "" ) {
# Hook "testipaddress"
for my $p_hook (keys %{$main::hooks{'testipaddress'}}) {
if ( &{$main::hooks{'testipaddress'}->{$p_hook}} ($client_ip) != 0 ) {
$counters{'notaccounted_client_ip_excluded'}++;
return;
};
};
};
# Adjust addresses
if ($from eq "") { $from = "from=<>" };
if ($from eq "#@[]") { $from = "from=<#@[]>"; };
$counters{'amavis_blocked'}++;
$from = lc ($from);
$to = lc ($to);
# Hook "modifyaddress"
for my $p_hook (keys %{$main::hooks{'modifyaddress'}}) {
$from = &{$main::hooks{'modifyaddress'}->{$p_hook}} ($from);
$to = &{$main::hooks{'modifyaddress'}->{$p_hook}} ($to);
};
$to_domain = ::extract_domain($to);
$from_domain = ::extract_domain($from);
## Calculate mail envelope data bytes (estimation)
#
# 220 server.example.com ESMTP [4+sizeof(localhelostring)+6+2=12+sizeof(localhelostring)]
# HELO test [5+sizeof(remotehelostring)+2=7+sizeof(remotehelostring)] -> note: helostring unknown!
# 250 server.example.com [4+sizeof(hostname)+2=6+sizeof(hostname)]
# MAIL FROM: <sender@example.com> [11+1+sizeof(sender)+1+2=15+sizeof(sender)]
# 250 Ok [6+2=8]
# RCPT TO: <rcpt@example.com> [9+1+sizeof(rcpt)+1+2=13+sizeof(rcpt)]
# DATA [4+2=6]
# 354 End data with <CR><LF>.<CR><LF> [35+2=37]
# text (size) [=size]
# . [1+2=3]
# 550 5.7.1 Message content rejected, id=01088-09 - VIRUS: EICAR-Test-File [44+sizeof(qid)+sizeof(rest)+sizeof(type($message))
# 221 Bye [7+2=9]
# 3x40 SYN, 4x40 FIN, 12x40 ACK = 760
# Assume MTU 1500 and 10% ACKs by receiver, uncalculable: TCP options
# Account blocked message now
if (! defined $main::opts{'acc_noenvelopes'} ) {
$mailtarasize = 160 + 2* length($main::opts{'myhostname'}) + length($from) + length($to) + length($qid);
if ($message eq "INFECTED") {
$mailtarasize += 5 + length($rest); # VIRUS + virus name
} elsif ($message eq "SPAM") {
$mailtarasize += 3; # UBE
};
} else {
$mailtarasize = 0;
};
if ( ! defined $main::opts{'acc_notcpoverhead'} ) {
$mailtarasize += 760 + int(($size + 1459) / 1460) * 44;
};
$messageUserStats{$to}->{'rcvd'}->{'count'} ++;
$messageUserStats{$to}->{'rcvd'}->{'size'} += $mailtarasize + $size;
$messageDomainStats{$to_domain}->{'rcvd'}->{'count'} ++;
$messageDomainStats{$to_domain}->{'rcvd'}->{'size'} += $mailtarasize + $size;
printf "*amavisblockedAcc: to=%s tara=%d size=%d\n", $to, $mailtarasize, $size if ($main::opts{'debug'} & 0x2 ); # orig: 0x0400
return;
};
# Catch Postfix Proxy SMTP auth lines without uniq QID
# Example. May 2 11:23:11 host postfix/smtpd[24062]: NOQUEUE: client=client.domain.example[1.2.3.4], sasl_method=PLAIN, sasl_username=user@domain.example
if (${$_[1]} =~ /^.*\spostfix\/smtpd\[[0-9]+\]: NOQUEUE: client=.*$/o ) {
print "DEBUG(acc/loglineparser): matched: '" . ${$_[1]} . "'\n" if ($main::opts{'debug'} & 0x1);
## Extract optional SMTP authentication data
if ((${$_[1]} =~ /.* client=([^,]+), sasl_method=([^,]+), sasl_username=([^,]+)$/o ) ) {
$client = $1;
$clients_authenticated{$client}->{'smtpauth_method'} = $2;
$clients_authenticated{$client}->{'smtpauth_username'} = $3;
$clients_authenticated{$client}->{'smtpauth_sender'} = "!";
#print "DEBUG(acc/clients_authenticated): match A\n" if ($main::opts{'debug'} & 0x10);
} elsif ((${$_[1]} =~ /.* client=([^,]+), simpleauth_method=([^,]+), simpleauth_username=([^,]+)$/o ) == 3) {
$client = $1;
$clients_authenticated{$client}->{'smtpauth_method'} = $2;
$clients_authenticated{$client}->{'smtpauth_username'} = $3;
$clients_authenticated{$client}->{'smtpauth_sender'} = "!";
#print "DEBUG(acc/clients_authenticated): match B\n" if ($main::opts{'debug'} & 0x10);
} elsif ((${$_[1]} =~ /.* client=([^,]+), sasl_sender=([^,]+)$/o) == 2) {
# SMTP authenticated sender
$client = $1;
$clients_authenticated{$client}->{'smtpauth_sender'} = $2;
$clients_authenticated{$client}->{'smtpauth_method'} = "!";
$clients_authenticated{$client}->{'smtpauth_username'} = "!";
#print "DEBUG(acc/clients_authenticated): match C\n" if ($main::opts{'debug'} & 0x10);
} elsif ((${$_[1]} =~ /.* client=([^,]+), simpleauth_sender=([^,]+)$/o) == 2) {
# SMTP authenticated sender
$client = $1;
$clients_authenticated{$client}->{'smtpauth_sender'} = $smtpauth_sender;
$clients_authenticated{$client}->{'smtpauth_method'} = "!";
$clients_authenticated{$client}->{'smtpauth_username'} = "!";
#print "DEBUG(acc/clients_authenticated): match D\n" if ($main::opts{'debug'} & 0x10);
} elsif ((($client) = ${$_[1]} =~ /.* client=([^,]+)$/o) == 1) {
# no SMTP authenticated sender
if (defined $clients_authenticated{$client}) {
print "DEBUG(acc/clients_authenticated): got: client=" . $client . " with no authentication information, reset\n" if ($main::opts{'debug'} & 0x10);
};
undef $clients_authenticated{$client};
return;
};
if (! defined $client) {
warn "*** Unmatched client log line:\n" . $_ . "\n*** Fix code!\n";
};
if (defined $client) {
# Set timestamp
$clients_authenticated{$client}->{'smtpauth_unixtime'} = ${$_[0]};
print "DEBUG(acc/clients_authenticated): got: unixtime=" . $clients_authenticated{$client}->{'smtpauth_unixtime'} . " client=" . $client . " smtpauth_sender=" . $clients_authenticated{$client}->{'smtpauth_sender'} . " smtpauth_method=" . $clients_authenticated{$client}->{'smtpauth_method'} . " smtpauth_username=" . $clients_authenticated{$client}->{'smtpauth_username'} ."\n" if ($main::opts{'debug'} & 0x10);
};
};
# Example: Feb 7 16:10:17 host postfix/smtpd[31956]: CA9461386E:...
if (! ( ${$_[1]} =~ /^.*\spostfix\/([^\[:]+).*?: ([A-F0-9]{8,11}): (.*)$/o ) ) {
# Unmatched logline
# E.g.: unmatched qid
#print "DEBUG(acc/loglineparser): skipped: '" . ${$_[1]} . "'\n";
return;
};
print "DEBUG(acc/loglineparser): matched: '" . ${$_[1]} . "'\n" if ($main::opts{'debug'} & 0x1);
$cmd = $1;
$qid = $2;
$rest = $3;
if ( $cmd eq "smtpd" ) {
# Example: Feb 7 16:10:17 host postfix/smtpd[31956]: CA9461386E: client=clientname[ipaddress]
if ( $rest =~ /^reject: /o ) {
# Example: Jan 1 01:35:54 host postfix/smtpd[12546]: D126F13866: reject: ...
if (! defined $messagelog{$qid} ) {
# Rejected but qid not in log, skip
# Happens e.g. one e-mail should be delivered to 2 or more recipients but client is rejected
return;
};
# Rejected
$counters{'cmd_smtpd_rejected'}++;
# Account envelope of rejected mail with "RCPT from"
if ( (! $main::opts{'acc_norejects'} ) && ( $rest =~ /^reject: RCPT from /o ) ) {
#printf "*rejectAcc: line=%s\n", $rest if ($main::opts{'debug'} & 0x0400 );
# Example: RCPT from adsl-63-206-123-11.dsl.snfc21.pacbell.net[63.206.123.11]: 554 <adsl-63-206-123-11.dsl.snfc21.pacbell.net[63.206.123.11]>: Client host rejected: Please use SMTP relay of your e-mail provider; from=<paulhpom@aol.com> to=<recipient@example.com> proto=SMTP helo=<aol.com>
($client_ip, $message, $from, $to, $helo) = $rest =~ /^reject: RCPT from .*\[([0-9A-Fa-f\.:]+)\]: ([0-9]{3} .*); from=<(.*)> to=<(.*)> proto=[^ ]* helo=<(.*)>$/o;
# printf "*rejectAcc: client_ip=%s message=%s from=%s to=%s helo=%s\n", $client_ip, $message, $from, $to, $helo if ($main::opts{'debug'} & 0x0400 );
# Check IP address of client
if ( $client_ip ne "" ) {
# Hook "testipaddress"
for my $p_hook (keys %{$main::hooks{'testipaddress'}}) {
if ( &{$main::hooks{'testipaddress'}->{$p_hook}} ($client_ip) != 0 ) {
$counters{'notaccounted_client_ip_excluded'}++;
return;
};
};
};
# Modify addresses
if ($from eq "") { $from = "from=<>" };
if ($from eq "#@[]") { $from = "from=<#@[]>"; };
$from = lc ($from);
$to = lc ($to);
# Hook "modifyaddress"
for my $p_hook (keys %{$main::hooks{'modifyaddress'}}) {
$from = &{$main::hooks{'modifyaddress'}->{$p_hook}} ($from);
$to = &{$main::hooks{'modifyaddress'}->{$p_hook}} ($to);
};
$to_domain = ::extract_domain($to);
$from_domain = ::extract_domain($from);
## Calculate mail envelope data bytes (estimation)
#
# 220 server.example.com ESMTP [4+sizeof(localhelostring)+6+2=12+sizeof(localhelostring)]
# HELO test [5+sizeof(remotehelostring)+2=7+sizeof(remotehelostring)]
# 250 server.example.com [4+sizeof(hostname)+2=6+sizeof(hostname)]
# MAIL FROM: <sender@example.com> [11+1+sizeof(sender)+1+2=15+sizeof(sender)]
# 250 Ok [6+2=8]
# RCPT TO: <rcpt@example.com> [9+1+sizeof(rcpt)+1+2=13+sizeof(rcpt)]
# 554 Service unavailable; Client host [217.232.177.23] blocked using relays.osirusoft.com [=sizeof(message)+2]
# QUIT [=6]
# 221 Bye [=9]
# 3x40 SYN, 4x40 FIN, 9x40 ACK = 640
# Missing, but uncalculable: TCP options
# $mailtarasize = 12 + length($main::opts{'myhostname'}) + 7 + length($helo) + 6 + length($main::opts{'myhostname'}) + 15 + length($from) + 8 + 13 + length($to) + length($message) + 2 + 6 + 9+ 640;
$mailtarasize = 76 + 2*length($main::opts{'myhostname'}) + length($helo) + length($from) + length($to) + length($message);
if ( ! defined $main::opts{'acc_notcpoverhead'} ) {
$mailtarasize += 640;
};
$messageUserStats{$to}->{'rcvd'}->{'count'} ++;
$messageUserStats{$to}->{'rcvd'}->{'size'} += $mailtarasize;
$messageDomainStats{$to_domain}->{'rcvd'}->{'count'} ++;
$messageDomainStats{$to_domain}->{'rcvd'}->{'size'} += $mailtarasize;
printf "*rejectAcc: to=%s tara=%d\n", $to, $mailtarasize if ($main::opts{'debug'} & 0x0400 );
};
if (defined $messagelog{$qid}->{'time'} ) {
if ( abs($messagelog{$qid}->{'time'} - ${$_[0]}) < 1800 ) {
# Remove qid if client was already set (avoid logfile timeline troubles)
# and time distance is short enough (avoid deleting wrong qid)
delete $messagelog{$qid};
#print "DEBUG(acc/loglineparser/" . $cmd . "): rejected/deleted qid=" . $qid . "\n";
return;
};
};
return;
# end of reject
} elsif ( $rest =~ /^discard: /o ) {
# Example: Feb 18 13:02:00 server postfix/smtpd[22323]: 16875BB40: discard: RCPT from client[1.2.3.4]: <sender@domain.example>: Sender address Delivery notification; from=<sender@domain.example> to=<recipient@domain.example> proto=SMTP helo=<test>
if (! defined $messagelog{$qid} ) {
# Discarded but qid not in log, skip
# Happens e.g. one e-mail should be delivered to 2 or more recipients but client is rejected
return;
};
# Discarded
$counters{'cmd_smtpd_discarded'}++;
# Account envelope of discarded mail with "RCPT from"
if ( (! $main::opts{'acc_norejects'} ) && ( $rest =~ /^discard: RCPT from /o ) ) {
#printf "*rejectAcc: line=%s\n", $rest if ($main::opts{'debug'} & 0x0400 );
# Example: RCPT from client[1.2.3.4]: <sender@domain.example>: Sender address Delivery notification; from=<sender@domain.example> to=<recipient@domain.example> proto=SMTP helo=<test>
my ($client_ip, $message, $from, $to, $helo) = $rest =~ /^discard: RCPT from .*\[([0-9A-Fa-f\.:]+)\]: (.*); from=<(.*)> to=<(.*)> proto=[^ ]* helo=<(.*)>$/o;
# printf "*discardAcc: client_ip=%s message=%s from=%s to=%s helo=%s\n", $client_ip, $message, $from, $to, $helo if ($main::opts{'debug'} & 0x0400 );
# Check IP address of client
if ( $client_ip ne "" ) {
# Hook "testipaddress"
for my $p_hook (keys %{$main::hooks{'testipaddress'}}) {
if ( &{$main::hooks{'testipaddress'}->{$p_hook}} ($client_ip) != 0 ) {
$counters{'notaccounted_client_ip_excluded'}++;
return;
};
};
};
# Modify addresses
if ($from eq "") { $from = "from=<>" };
if ($from eq "#@[]") { $from = "from=<#@[]>"; };
$from = lc ($from);
$to = lc ($to);
# Hook "modifyaddress"
for my $p_hook (keys %{$main::hooks{'modifyaddress'}}) {
$from = &{$main::hooks{'modifyaddress'}->{$p_hook}} ($from);
$to = &{$main::hooks{'modifyaddress'}->{$p_hook}} ($to);
};
$to_domain = ::extract_domain($to);
$from_domain = ::extract_domain($from);
## Calculate mail envelope data bytes (estimation)
#
# 220 server.example.com ESMTP [4+sizeof(localhelostring)+6+2=12+sizeof(localhelostring)]
# HELO test [5+sizeof(remotehelostring)+2=7+sizeof(remotehelostring)]
# 250 server.example.com [4+sizeof(hostname)+2=6+sizeof(hostname)]
# MAIL FROM: <sender@example.com> [11+1+sizeof(sender)+1+2=15+sizeof(sender)]
# 250 Ok [6+2=8]
# RCPT TO: <rcpt@example.com> [9+1+sizeof(rcpt)+1+2=13+sizeof(rcpt)]
# 250 Ok [6+2=8]
# DATA [4+2=6]
# 354 End data with <CR><LF>.<CR><LF> [35+2=37]
# +++discarded data, size unknown+++ [?+2=2]
# . [1+2=3]
# 250 Ok: queued as BC389BB40 [27+2=29]
# QUIT [=6]
# 221 Bye [=9]
# 3x40 SYN, 4x40 FIN, 14x40 ACK = 840
# Missing, but uncalculable: TCP options and size of body
$mailtarasize = 167 + 2*length($main::opts{'myhostname'}) + length($helo) + length($from) + length($to);
if ( ! defined $main::opts{'acc_notcpoverhead'} ) {
$mailtarasize += 840;
};
$messageUserStats{$to}->{'rcvd'}->{'count'} ++;
$messageUserStats{$to}->{'rcvd'}->{'size'} += $mailtarasize;
$messageDomainStats{$to_domain}->{'rcvd'}->{'count'} ++;
$messageDomainStats{$to_domain}->{'rcvd'}->{'size'} += $mailtarasize;
printf "*rejectAcc: to=%s tara=%d\n", $to, $mailtarasize if ($main::opts{'debug'} & 0x0400 );
};
if (defined $messagelog{$qid}->{'time'} ) {
if ( abs($messagelog{$qid}->{'time'} - ${$_[0]}) < 1800 ) {
# Remove qid if client was already set (avoid logfile timeline troubles)
# and time distance is short enough (avoid deleting wrong qid)
delete $messagelog{$qid};
#print "DEBUG(acc/loglineparser/" . $cmd . "): rejected/deleted qid=" . $qid . "\n";
return;
};
};
return;
# end of discard
} elsif ( $rest =~ /^client=/o ) {
&logline_cmd_smtp(\$qid, \${$_[0]}, \${$_[1]});
return;
} else {
# not interesting here
die "ERROR(acc/loglineparser/" . $cmd . "): Line contains unrecognized values: qid=" . $qid . " '" . $rest . "' !FIXCODE!";
return;
};
return;
} elsif ($cmd eq 'pickup') {
if (! ( $rest =~ /^(sender|uid)=/o ) ) {
# not interesting here
return;
};
&logline_cmd_pickup(\$qid, \${$_[0]}, \${$_[1]});
return;
} elsif ($cmd eq 'cleanup') {
&logline_cmd_cleanup(\$qid, \${$_[0]}, \${$_[1]});
return;
} elsif ($cmd eq 'pipe' || $cmd eq 'local' || $cmd eq "smtp") {
if ( $rest =~ /^to=<(.*)>, orig_to=<(.*)>, relay=([^,]+), delay=[\-0-9]+, status=([^ ]+)/o ) {
# Example: to=<account@server.example.com>, orig_to=<account@example.com>, relay=avcheck, delay=1, status=sent ...additional text
$addr = lc($1); # force lower case
$orig_to = $2;
$relay = $3;
$status = $4;
# replace address with orig_to address (if origto contains an @)
if ( $orig_to =~ /@/o ) {
# fill in special hash for proper accounting of forwarding via local relay
if (defined $messagelog{$qid}->{'mid'} ) {
$messageid_origto{$addr}->{$messagelog{$qid}->{'mid'}} = $orig_to;
#print "DEBUG: fill mid\n";
};
$addr = lc($orig_to); # force lower case
};
#printf "DEBUG(acc/loglineparser/M1): qid=%s to=%s relay=%s status=%s\n", $qid, $addr, $relay, $status;
} elsif ( $rest =~ /^to=<(.*)>, relay=([^,]+), delay=[\-0-9]+, status=([^ ]+)/o ) {
# Example: to=<account@example.com>, relay=avcheck, delay=1, status=sent ...additional text
$addr = lc($1); # force lower case
$relay = $2;
$status = $3;
#printf "DEBUG(acc/loglineparser/M2): qid=%s to=%s relay=%s status=%s\n", $qid, $addr, $relay, $status;
} elsif ( $rest =~ /^to=<([^>]*)>/o ) {
die "ERROR(acc/loglineparser/" . $cmd . "): Line contains unrecognized values: qid=" . $qid . " '" . $rest . "' !FIXCODE!";
} else {
# not interesting here
return;
};
# Hook "modifyaddress"
for my $p_hook (keys %{$main::hooks{'modifyaddress'}}) {
$addr = &{$main::hooks{'modifyaddress'}->{$p_hook}} ($addr);
};
&logline_cmd_to(\$qid, \${$_[0]}, \$addr, \$status, \$relay);
return;
} elsif ($cmd eq 'nqmgr' || $cmd eq 'qmgr') {
if ( $rest =~ /^from=<(.*)>, size=([0-9]+)/o ) {
# Example: from=<account@example.com>, size=1352, nrcpt=1 ...additional txt
# Catch also (not so valid): from=<<>@uke.uni-hamburg.de>, size=
$addr = lc($1);
$size = $2;
} elsif ( $rest =~ /^from=<(.*)>, status=([^ ]+)/o ) {
# Example: from=<account@example.com>, status=expired, returned to sender
# TODO: remove this qid from accounting
# Return for now
# Todo
return;
} elsif ( $rest =~ /^to=<(.*)>, relay=([^,]+), delay=[\-0-9]+, status=([^ ]+)/o ) {
# Example: to=<xxx@xxx.xx>, relay=none, delay=0, status=deferred ....
# Only occur on older postfix versions (qmgr)
$addr = lc($1); # force lower case
$relay = $2;
$status = $3;
# Update status
&logline_cmd_to(\$qid, \${$_[0]}, \$addr, \$status, \$relay);
return;
} elsif ( $rest =~ /^to=<(.*)>, orig_to=<(.*)>, relay=([^,]+), delay=[\-0-9]+, status=([^ ]+)/o ) {
# Example: to=<account@example.com>, orig_to=<account@example.com>, relay=none, delay=1, status=bounced ...
$addr = lc($1); # force lower case
$orig_to = $2;
$relay = $3;
$status = $4;
# replace address with orig_to address (if origto contains an @)
if ( $orig_to =~ /@/o ) {
# fill in special hash for proper accounting of forwarding via local relay
$messageid_origto{$addr}->{$messagelog{$qid}->{'mid'}} = $orig_to;
$addr = lc($orig_to); # force lower case
};
# Update status
&logline_cmd_to(\$qid, \${$_[0]}, \$addr, \$status, \$relay);
return;
} elsif ( $rest =~ /^to=<([^>]*)>/o ) {
die "ERROR(acc/loglineparser/" . $cmd . "): Line contains unrecognized values: qid=" . $qid . " '" . $rest . "' !FIXCODE!";
} elsif ( $rest =~ /^from=<([^>]*)>/o ) {
die "ERROR(acc/loglineparser/" . $cmd . "): Line contains unrecognized values: qid=" . $qid . " '" . $rest . "' !FIXCODE!";
} else {
# not interesting here
return;
};
if ($addr) {
# Check for bounce addresses
if ($addr eq "#@[]") {
$addr = "from=<#@[]>";
};
} else {
$addr = "from=<>";
};
# Hook "modifyaddress"
for my $p_hook (keys %{$main::hooks{'modifyaddress'}}) {
$addr = &{$main::hooks{'modifyaddress'}->{$p_hook}} ($addr);
};
&logline_cmd_from(\$qid, \${$_[0]}, \$addr, \$size);
return;
};
};
## Local functions
sub print_summary_acc_users_treeview() {
my %tree_acc_users;
# print "DEBUG/" . $module_name . "/print_summary_acc_users_treeview: called\n";
# Create tree
my $secondleveldomain;
my $size;
for my $user (keys %messageUserStats) {
$secondleveldomain = ::extract_2ndleveldomain($user);
$size = $messageUserStats{$user}->{'sent'}->{'size'} + $messageUserStats{$user}->{'rcvd'}->{'size'};
$tree_acc_users{$secondleveldomain}->{$user} = $size;
$tree_acc_users{$secondleveldomain}->{'TOTAL'} += $size;
};
# print "DEBUG/" . $module_name . "/print_summary_acc_users_treeview: tree generated\n";
#for my $key (keys %tree_acc_users) {
# print $key . "\n";
#};
print_domains_users_indent(\%tree_acc_users);
};
# Print users/domains in an indented format
sub print_users_indent($) {
my $p = $_[0] || die "missing hash pointer";
# Sorted by size (biggest first)
foreach my $k0 ( sort { $$p{$b} <=> $$p{$a} } keys %$p ) {
next if ($k0 eq 'TOTAL'); # do not display "total" value here
next if ($$p{$k0} == 0); # do not display entries without any traffic
printf " %-60s %9s\n", substr($k0, 0, 60),
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($$p{$k0});
};
};
sub print_domains_users_indent($;@) {
my $p = $_[0] || die "missing hash pointer";
my $list = $_[1]; # optional list of keys
my $p0;
my $startflag = 0;
if (! defined $p) { die "hash pointer is undefined"; };
if (! defined %$p) { print "WARNING: no data"; };
if (! defined $list) { $list = ""; };
print '=' x $main::opts{'print-max-width'} . "\n";
print "Domains/Users accounting statistics indented\n";
::print_timerange_normal();
print '-' x $main::opts{'print-max-width'} . "\n";
printf "%-*s %9s%9s\n", $main::opts{'print-max-width'} - 24, "Domain/User", "Total", "Single";
print '-' x $main::opts{'print-max-width'} . "\n";
if ($list eq "" ) {
# Sorted by size (biggest first)
for my $k ( sort { $$p{$b}->{'TOTAL'} <=> $$p{$a}->{'TOTAL'} } keys %$p ) {
$p0 = $$p{$k};
next if ($$p{$k}->{'TOTAL'} == 0); # do not display entries without any traffic
if ($startflag == 0) {
$startflag = 1;
} else {
print "\n";
};
printf "%-55s %9s\n", substr($k, 0, 55),
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($$p{$k}->{'TOTAL'});
print_users_indent( \%$p0 );
};
} else {
foreach my $k (split " ", $list) {
if (! defined $$p{$k} ) {
# print " ! no data !\n";
next;
};
$p0 = $$p{$k};
if ($startflag == 0) {
$startflag = 1;
} else {
print "\n";
};
printf "%-55s %9s\n", substr($k, 0, 55),
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($$p{$k}->{'TOTAL'});
print_users_indent( \%$p0 );
};
};
print '=' x $main::opts{'print-max-width'} . "\n";
};
# Print per user statistics
sub print_summary_acc_users() {
my %sum;
print "\n";
print '='x104 . "\n";
printf "%-40s: %8s %11s %8s %11s %8s %11s\n", "User", "NumSent", "BytesSent", "NumRec", "BytesRec", "NumSum", "BytesSum";
print '-'x104 . "\n";
for my $user (sort keys %messageUserStats) {
$sum{'sent'}->{'count'} += $messageUserStats{$user}->{'sent'}->{'count'};
$sum{'sent'}->{'size'} += $messageUserStats{$user}->{'sent'}->{'size'};
$sum{'rcvd'}->{'count'} += $messageUserStats{$user}->{'rcvd'}->{'count'};
$sum{'rcvd'}->{'size'} += $messageUserStats{$user}->{'rcvd'}->{'size'};
printf "%-40s: %8d %11d %8d %11d %8d %11d\n",
$user,
$messageUserStats{$user}->{'sent'}->{'count'},
$messageUserStats{$user}->{'sent'}->{'size'},
$messageUserStats{$user}->{'rcvd'}->{'count'},
$messageUserStats{$user}->{'rcvd'}->{'size'},
$messageUserStats{$user}->{'sent'}->{'count'}
+ $messageUserStats{$user}->{'rcvd'}->{'count'},
$messageUserStats{$user}->{'sent'}->{'size'}
+ $messageUserStats{$user}->{'rcvd'}->{'size'};
};
print '-'x104 . "\n";
printf "%-40s: %8d %11d %8d %11d %8d %11d\n", "Total",
$sum{'sent'}->{'count'},
$sum{'sent'}->{'size'},
$sum{'rcvd'}->{'count'},
$sum{'rcvd'}->{'size'},
$sum{'sent'}->{'count'} + $sum{'rcvd'}->{'count'},
$sum{'sent'}->{'size'} + $sum{'rcvd'}->{'size'};
print '='x104 . "\n";
};
# Print per domain statistics, customers will be separated from others
sub print_summary_acc_domains() {
my %sum;
$sum{'sent'}->{'count'} = 0;
$sum{'sent'}->{'size'} = 0;
$sum{'rcvd'}->{'count'} = 0;
$sum{'rcvd'}->{'size'} = 0;
print "\n";
print '=' x $main::opts{'print-max-width'} . "\n";
printf "%-*s:%6s %9s|%6s %9s|%6s %9s\n", $main::opts{'print-max-width'} - 52, "Domain", "NumSent", "BytesSent", "NumRec", "BytesRec", "NumSum", "BytesSum";
print '-' x $main::opts{'print-max-width'} . "\n";
for my $domain (sort keys %messageDomainStats) {
$sum{'sent'}->{'count'} += $messageDomainStats{$domain}->{'sent'}->{'count'};
$sum{'sent'}->{'size'} += $messageDomainStats{$domain}->{'sent'}->{'size'};
$sum{'rcvd'}->{'count'} += $messageDomainStats{$domain}->{'rcvd'}->{'count'};
$sum{'rcvd'}->{'size'} += $messageDomainStats{$domain}->{'rcvd'}->{'size'};
printf "%-23s: %6d %9s|%6d %9s|%6d %9s\n",
substr ($domain, 0, 23),
$messageDomainStats{$domain}->{'sent'}->{'count'},
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($messageDomainStats{$domain}->{'sent'}->{'size'}),
$messageDomainStats{$domain}->{'rcvd'}->{'count'},
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($messageDomainStats{$domain}->{'rcvd'}->{'size'}),
$messageDomainStats{$domain}->{'sent'}->{'count'}
+ $messageDomainStats{$domain}->{'rcvd'}->{'count'},
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($messageDomainStats{$domain}->{'sent'}->{'size'}
+ $messageDomainStats{$domain}->{'rcvd'}->{'size'});
};
print '-' x $main::opts{'print-max-width'} . "\n";
printf "%-*s: %6d %9s|%6d %9s|%6d %9s\n", $main::opts{'print-max-width'} - 52, "Total",
$sum{'sent'}->{'count'},
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($sum{'sent'}->{'size'}),
$sum{'rcvd'}->{'count'},
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($sum{'rcvd'}->{'size'}),
$sum{'sent'}->{'count'} + $sum{'rcvd'}->{'count'},
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($sum{'sent'}->{'size'} + $sum{'rcvd'}->{'size'});
print '=' x $main::opts{'print-max-width'} . "\n";
};
sub print_summary_acc_domains_short() {
# Print per domain statistics
my %sum;
my %sum_others;
my $format;
$sum{'sent'}->{'count'} = 0;
$sum{'sent'}->{'size'} = 0;
$sum{'rcvd'}->{'count'} = 0;
$sum{'rcvd'}->{'size'} = 0;
$sum_others{'sent'}->{'count'} = 0;
$sum_others{'sent'}->{'size'} = 0;
$sum_others{'rcvd'}->{'count'} = 0;
$sum_others{'rcvd'}->{'size'} = 0;
# Calculate sum
for my $domain (sort keys %messageDomainStats) {
if ( %domains_customer ) {
if ( defined $domains_customer{$domain} ) {
$sum{'sent'}->{'count'} += $messageDomainStats{$domain}->{'sent'}->{'count'};
$sum{'sent'}->{'size'} += $messageDomainStats{$domain}->{'sent'}->{'size'};
$sum{'rcvd'}->{'count'} += $messageDomainStats{$domain}->{'rcvd'}->{'count'};
$sum{'rcvd'}->{'size'} += $messageDomainStats{$domain}->{'rcvd'}->{'size'};
} else {
$sum_others{'sent'}->{'count'} += $messageDomainStats{$domain}->{'sent'}->{'count'};
$sum_others{'sent'}->{'size'} += $messageDomainStats{$domain}->{'sent'}->{'size'};
$sum_others{'rcvd'}->{'count'} += $messageDomainStats{$domain}->{'rcvd'}->{'count'};
$sum_others{'rcvd'}->{'size'} += $messageDomainStats{$domain}->{'rcvd'}->{'size'};
};
} else {
$sum{'sent'}->{'count'} += $messageDomainStats{$domain}->{'sent'}->{'count'};
$sum{'sent'}->{'size'} += $messageDomainStats{$domain}->{'sent'}->{'size'};
$sum{'rcvd'}->{'count'} += $messageDomainStats{$domain}->{'rcvd'}->{'count'};
$sum{'rcvd'}->{'size'} += $messageDomainStats{$domain}->{'rcvd'}->{'size'};
};
};
# Format: treeview
if (defined $main::format{"treeview"}) {
$format = "treeview";
print "\n\nWARNING(acc): Format '" . $format . "' is currently not supported!\n\n";
};
# Format: computer
if (defined $main::format{"computer"}) {
$format = "computer";
if ( %domains_customer ) {
printf "# %s %s\n", "Domain (filtered by customers domain list)", "[BytesTotal]";
} else {
printf "# %s %s\n", "Domain", "[BytesTotal]";
};
for my $domain (sort keys %messageDomainStats) {
if ( %domains_customer ) {
if ( defined $domains_customer{$domain} ) {
print $domain . "=" . ($messageDomainStats{$domain}->{'sent'}->{'size'} + $messageDomainStats{$domain}->{'rcvd'}->{'size'}) . "\n";
};
} else {
print $domain . "=" . ($messageDomainStats{$domain}->{'sent'}->{'size'} + $messageDomainStats{$domain}->{'rcvd'}->{'size'}) . "\n";
};
};
if ( %domains_customer ) {
print "_customers=" . ($sum{'sent'}->{'size'} + $sum{'rcvd'}->{'size'}) . "\n";
print "_others=" . ($sum_others{'sent'}->{'size'} + $sum_others{'rcvd'}->{'size'}) . "\n";
print "_total=" . ($sum{'sent'}->{'size'} + $sum{'rcvd'}->{'size'} + $sum_others{'sent'}->{'size'} + $sum_others{'rcvd'}->{'size'}) . "\n";
} else {
print "_total=" . ($sum{'sent'}->{'size'} + $sum{'rcvd'}->{'size'}) . "\n";
};
};
# Format: indented
if (defined $main::format{"indented"}) {
$format = "indented";
print "\n\nWARNING(acc): Format '" . $format . "' is currently not supported!\n\n";
};
# Format: txttable
if (defined $main::format{"txttable"}) {
$format = "txttable";
print '=' x $main::opts{'print-max-width'} . "\n";
if ( %domains_customer ) {
printf "%-*s:%9s\n", $main::opts{'print-max-width'} - 11, "Domain (filtered by customers domain list)", "BytesTotal";
} else {
printf "%-*s:%9s\n", $main::opts{'print-max-width'} - 11, "Domain", "BytesTotal";
};
print '-' x $main::opts{'print-max-width'} . "\n";
for my $domain (sort keys %messageDomainStats) {
if ( %domains_customer ) {
if ( defined $domains_customer{$domain} ) {
printf "%-*s: %9s\n",
$main::opts{'print-max-width'} - 11,
$domain,
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($messageDomainStats{$domain}->{'sent'}->{'size'}
+ $messageDomainStats{$domain}->{'rcvd'}->{'size'});
};
} else {
printf "%-*s: %9s\n",
$main::opts{'print-max-width'} - 11,
$domain,
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($messageDomainStats{$domain}->{'sent'}->{'size'}
+ $messageDomainStats{$domain}->{'rcvd'}->{'size'});
};
};
print '-' x $main::opts{'print-max-width'} . "\n";
if ( %domains_customer ) {
printf "%-*s: %9s\n", $main::opts{'print-max-width'} - 11, "Customers",
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($sum{'sent'}->{'size'} + $sum{'rcvd'}->{'size'});
printf "%-*s: %9s\n", $main::opts{'print-max-width'} - 11, "Others",
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($sum_others{'sent'}->{'size'} + $sum_others{'rcvd'}->{'size'});
print '-' x $main::opts{'print-max-width'} . "\n";
printf "%-*s: %9s\n", $main::opts{'print-max-width'} - 11, "Total",
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($sum{'sent'}->{'size'} + $sum{'rcvd'}->{'size'} + $sum_others{'sent'}->{'size'} + $sum_others{'rcvd'}->{'size'} );
} else {
printf "%-*s: %9s\n", $main::opts{'print-max-width'} - 11, "Total",
$main::numberformat{$main::opts{'numberformat'}}->format_bytes($sum{'sent'}->{'size'} + $sum{'rcvd'}->{'size'});
};
print '=' x $main::opts{'print-max-width'} . "\n";
};
};
## End of module
return 1;
syntax highlighted by Code2HTML, v. 0.9.1