Attachment 'track_email_distribution.pl'
#!/usr/bin/env perl
#
# track_email_distribution.pl
#
# Given a message id (easily found in /var/log/mailman/post)
# Search the /var/log/mail.log* files for matching postfix records and report emails that were sent
#
# This is not strictly a mailman tool, rather a useful tool for capturing information about emails
# that have been sent to a list of people via a mail server (eg. email redirections would also
# be traceable with this script).
#
# $Id: track_email_distribution.pl 3101 2015-03-15 23:35:37Z bjdean $
#
#
# Suggested usage:
#
# ls -tr /var/log/mail.log* | xargs zcat -f | ./track_email_distribution.pl EMAIL-ID-1@exmaple.org EMAIL-ID-2@example.net --addr check-email-recip@example.com
#
#
# Notes:
# - At the momemt this sucks the full mail log into memory for multiple passes
# This could consume a lot of memory for a busy server
# A bettrer solution would be to only cache the log lines relevant to the
# query (thought this may need a multi-pass read of the input)
# -
#
# Copyright (C) 2015 Bradley Dean <bjdean@bjdean.id.au>
#
# 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, either version 3 of the License, or (at your option) any later
# version.
#
# 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, see <http://www.gnu.org/licenses/>.
use strict;
use warnings;
use Getopt::Long;
# autoflush stdout
$| = 1;
# do it
eval { main(); };
if ( $@ ) {
die "Unespected exception: $@";
}
############################################################
# Main program
#
sub main {
# command line options
my @check_email_addresses;
GetOptions(
'address=s' => \@check_email_addresses,
) or die;
my @msg_ids = @ARGV;
# Slurp in the log into memory (bad form, but hey)
my @log_lines = <STDIN>;
# Get message id(s) from command line
MSG_ID:
for my $msg_id ( @msg_ids ) {
print "Tracking email with message id = ${msg_id}\n";
my $datestamp = get_first_date_for_msgid($msg_id, \@log_lines);
if ( $datestamp ) {
print " .. email sent: ${datestamp}\n"
}
else {
print " .. NO email found for that id.\n";
next MSG_ID;
}
my @queue_ids = get_queue_ids_for_msgid($msg_id, \@log_lines);
if ( @queue_ids ) {
print " .. " . scalar(@queue_ids) . " queue IDs: " . join(", ", @queue_ids) . "\n";
}
else {
print " .. NO log details found for that id\n";
next MSG_ID;
}
my @email_recips = get_email_recips_for_queue_ids(\@queue_ids, \@log_lines);
if ( @email_recips ) {
print " .. " . scalar(@email_recips) . " email recips: " . join(", ", @email_recips) . "\n";
check_email_recips(\@email_recips, \@check_email_addresses);
}
else {
print " .. NO email recips details found for that id\n";
}
}
}
############################################################
# Functions
#
sub get_first_date_for_msgid {
my ($msg_id, $log_lines) = @_;
for my $line ( @{$log_lines} ) {
my $postfix_record = parse_line($line);
if ( $postfix_record ) {
if ( $postfix_record->{message} =~ /message-id=<\Q${msg_id}\E>/ ) {
return $postfix_record->{datestamp};
}
}
}
}
sub check_email_recips {
my ($actual_email_recips, $check_email_addresses) = @_;
# Do nothing if no email addresses to check
return unless @{$check_email_addresses};
my @found;
for my $actual_email_recip ( @{$actual_email_recips} ) {
if ( grep { $actual_email_recip eq $_ } @{$check_email_addresses} ) {
push @found, $actual_email_recip;
}
}
if ( @found ) {
print " .. " . scalar(@found) . " check email addresses found: " . join(", ", @found) . "\n";
}
else {
print " .. NO check email addresses found in email recipients\n";
}
}
sub get_email_recips_for_queue_ids {
my ($queue_ids, $log_lines ) = @_;
my %email_recips;
for my $line ( @{$log_lines} ) {
my $postfix_record = parse_line($line);
if ( $postfix_record ) {
if ( grep { $postfix_record->{queue_id} eq $_ } @{$queue_ids} ) {
if ( $postfix_record->{message} =~ /to=<([^>]+)/ ) {
$email_recips{$1} = 1;
}
}
}
}
return sort keys %email_recips;
}
sub get_queue_ids_for_msgid {
my ($msg_id, $log_lines) = @_;
my @queue_ids;
for my $line ( @{$log_lines} ) {
my $postfix_record = parse_line($line);
if ( $postfix_record ) {
if ( $postfix_record->{message} =~ /message-id=<\Q${msg_id}\E>/ ) {
push @queue_ids, $postfix_record->{queue_id};
}
}
}
return @queue_ids;
}
sub parse_line {
my ($line) = @_;
chomp $line;
if ( $line =~ /
^(\w+\s+\d+\s+\d+:\d+:\d+) # datestamp
\s+
\w+ # servername
\s+
(postfix\S+?): # postfix process name and pid
\s+
(\S+?): # queue id
\s+
(.*) # details
/x
) {
return {
datestamp => $1,
postfix_process => $2,
queue_id => $3,
message => $4,
};
}
}
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.
