package Yaala::Parser;

use strict;
use warnings;
use vars qw#%DATAFIELDS#;

use Exporter;
use Yaala::Data::Persistent qw#init#;
use Yaala::Parser::WebserverTools qw#%MONTH_NUMBERS#;

@Yaala::Parser::EXPORT_OK = qw(parse extra %DATAFIELDS);
@Yaala::Parser::ISA = ('Exporter');

our $LASTDATE = init ('$LASTDATE', 'scalar');
our $EXTRA = init ('$EXTRA', 'hash');
our $MAILS = init ('$MAILS', 'hash');

if (!$$LASTDATE) { $$LASTDATE = 0; }
if (!defined ($EXTRA->{'relay_denied'})) { $EXTRA->{'relay_denied'} = 0; }
if (!defined ($EXTRA->{'tls_hosts'}   )) { $EXTRA->{'tls_hosts'}    = {}; }

%DATAFIELDS =
(
	date		=> 'key:date',
	hour		=> 'key:hour',

	sender		=> 'key',
	recipient	=> 'key',

	defer_count	=> 'key:numeric',
	delay		=> 'key:time',

	incoming_host	=> 'key:host',
	outgoing_host	=> 'key:host',

	count		=> 'agg',
	bytes		=> 'agg:bytes'
);

# This needs to be done at runtime, since Data uses Setup which relies on
# %DATAFIELDS to be defined  -octo
require Yaala::Data::Core;
import Yaala::Data::Core qw#store#;

my $VERSION = '$Id: Postfix.pm,v 1.6 2003/12/07 15:42:22 octo Exp $';
print STDERR $/, __FILE__, ": $VERSION" if ($::DEBUG);

return (1);

sub parse
{
	my $line = shift;

	if ($line =~ m#^(\w{3})\s+(\d+) (\d\d):(\d\d):(\d\d) (\S+) postfix/([^\[]+)[^:]+: ([A-F0-9]+): (.+)$#)
	{
		my ($month, $day, $hour, $minute, $second,
			$hostname, $service, $id, $line_end) =
		($1, $2, $3, $4, $5, $6, $7, $8, $9);
		my $year = (localtime ())[5] + 1900;
		$month = $MONTH_NUMBERS{$month};

		{
			my $tmp = int (sprintf ("%04u%02u%02u%02u%02u%02u",
					$year, $month, $day, $hour, $minute, $second));

			if ($tmp < $$LASTDATE)
			{
				print STDERR $/, __FILE__, ": Skipping.. ($tmp <= $$LASTDATE)" if ($::DEBUG & 0x0200);
				return (undef);
			}
			else { $$LASTDATE = $tmp; }
		}
	
		my $date = sprintf ("%04u-%02u-%02u", $year, $month, $day);

		if (!defined ($MAILS->{$id}))
		{
			$MAILS->{$id} =
			{
				date		=> $date,
				hour		=> $hour,
				sender		=> '*UNKNOWN*',
				recipient	=> '*UNKNOWN*',
				defer_count	=> 0,
				delay		=> 0,
				incoming_host	=> '*UNKNOWN*',
				outgoing_host	=> '*UNKNOWN*',
				count		=> 1,
				bytes		=> 0
			};
		}

		$MAILS->{$id}{'date'} = $date;
		$MAILS->{$id}{'hour'} = $hour;
		
		if ($line_end =~ m/^to=<([^>]+)>, relay=([^,]+), delay=(\d+), status=(\w+)/)
		{
			my ($to, $relay, $delay, $status) = ($1, $2, $3, $4);

			$MAILS->{$id}{'recipient'} = $to;
			if ($MAILS->{$id}{'delay'} < $delay)
			{
				$MAILS->{$id}{'delay'} = $delay;
			}

			if ($relay =~ m/^([^\[]+)\[([\d\.]+)\]$/)
			{
				my $host = $1;
				my $ip = $2;

				if ($host eq 'unknown')
				{
					$MAILS->{$id}{'outgoing_host'} = $ip;
				}
				else
				{
					$MAILS->{$id}{'outgoing_host'} = $host;
				}
			}
			elsif ($relay eq 'local')
			{
				$MAILS->{$id}{'outgoing_host'} = 'localhost';
			}

			if ($status eq 'sent')
			{
				store_mail ($id);
			}
			elsif ($status eq 'deferred')
			{
				$MAILS->{$id}{'defer_count'}++;
			}
			elsif ($status eq 'bounced')
			{
				$MAILS->{$id}{'recipient_count'}--;
				if ($MAILS->{$id}{'recipient_count'} < 1)
				{
					delete ($MAILS->{$id});
				}
			}
			elsif ($::DEBUG)
			{
				print STDERR $/, __FILE__, ": Unknown status: $status";
			}
		}
		elsif ($line_end =~ m/^from=<([^>]*)>, size=(\d+), nrcpt=(\d+)/)
		{
			my ($from, $size, $nrcpt) = ($1, $2, $3, $4);

			$MAILS->{$id}{'sender'} = $from if ($from);
			$MAILS->{$id}{'bytes'} = $size;
			
			$MAILS->{$id}{'recipient_count'} = $nrcpt;
		}
		elsif ($line_end =~ m/client=([^ ,]+)/)
		{
			my $client = $1;

			if ($client =~ m/^([^\[]+)\[([\d\.]+)\]$/)
			{
				my $host = $1;
				my $ip = $2;

				if ($host eq 'unknown')
				{
					$MAILS->{$id}{'incoming_host'} = $ip;
				}
				else
				{
					$MAILS->{$id}{'incoming_host'} = $host;
				}
			}
			elsif ($::DEBUG)
			{
				print STDERR $/, __FILE__,
				": Unable to parse client string: $client";
			}
		}
	}
	elsif ($line =~ m/Relay access denied/i)
	{
		$EXTRA->{'relay_denied'}++;
	}
	elsif ($line =~ m/TLS connection established (?:to|from) ([^\[]+)\[([^\]]+)\]/i)
	{
		my $host = $1;
		my $ip = $2;

		my $ident = ($host eq 'unknown' ? $ip : $host);

		$EXTRA->{'tls_hosts'}{$ident} = 1;
	}
	elsif ($::DEBUG and 0)
	{
		chomp ($line);
		print STDERR $/, __FILE__, ": Unable to parse line: $line";
	}
}

sub store_mail
{
	my $id = shift;
	my $mail = $MAILS->{$id};

	store ($mail);
	
	$mail->{'recipient_count'}--;
	if ($mail->{'recipient_count'} < 1)
	{
		delete ($MAILS->{$id});
	}
}

sub extra
{
	if ($EXTRA->{'relay_denied'})
	{
		$::EXTRA->{'Relay access denied'} = sprintf ("%u times", $EXTRA->{'relay_denied'});
	}

	my $tls_hosts = scalar (keys (%{$EXTRA->{'tls_hosts'}}));
	if ($tls_hosts)
	{
		$::EXTRA->{'TLS connections'} = sprintf ("%u hosts", $tls_hosts);
	}
}
