Log every SMTP transaction in a MySQL database on SME Server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
11 KiB

12 years ago
#!/usr/bin/perl
#####################################################
# log2sql - qpsmtp plugin for logging in SQL DB #
# By Todor Todorov <tttodorov@yahoo.com> #
# http://www.geocities.com/tttodorov/ #
# VERSION 0.06 #
# Last modified 2012/10/11 #
# Edit by Daniel Berteaud #
# <daniel@firewall-services.com> #
12 years ago
#####################################################
use DBI;
use Encode qw(decode);
12 years ago
use strict;
#plugin level variables are here since version 0.02
my ($dsn,$mail_table,$rcpt_table,$user,$passwd,$mail_id);
my ($log_header);
my ($log_all_body,$log_deny_body,$body_table);
my ($dbh,$sth);
sub register {
my ($self,$qp) = (shift,shift);
$self->log(LOGERROR,"Bad count of parameters in log2sql plugin.") if @_ % 2;
my (%args) = @_;
$self->register_hook("connect", "connect_handler");
$self->register_hook("mail", "mail_handler");
$self->register_hook("rcpt", "rcpt_handler");
$self->register_hook("data_post", "data_post_handler");
$self->register_hook("queue", "queue_handler");
$self->register_hook("deny", "deny_handler") if $args{D};
$self->register_hook("disconnect", "disconnect_handler");
$dsn = 'DBI';
$dsn .= ':'.($args{t} || 'mysql');
$dsn .= ':'.($args{d} || 'qpsmtpd');
$dsn .= ';host='.$args{h} if $args{h};
$dsn .= ';port='.$args{P} if $args{P};
$dsn .= ';'.$args{o} if $args{o};
$mail_table = $args{m} || 'messages';
$rcpt_table = $args{r} || 'rcpts';
$user = $args{u} || 'root';
$passwd = $args{p} || '';
$log_header = $args{H} || undef;
$log_all_body = $args{B} || undef;
$log_deny_body = $args{DB} || undef;
$body_table = $args{b} || 'message_body';
}
sub connect_handler {
my ($self,$transaction) = @_;
$self->log(LOGDEBUG,"DSN:$dsn");
$dbh = DBI->connect($dsn,$user,$passwd) || $self->log(LOGERROR,DBI::errstr);
# generate mail id
$mail_id = $$.'.'.time.'.'.int(rand(10000));
# set note for other plugins
$transaction->notes('log2sql_mail_id', $mail_id);
# quote the mail_id for later use:
$mail_id = $dbh->quote($mail_id);
my ($sec,$min,$hour,$day,$mon,$year) = localtime;
$year += 1900;
$mon += 1;
my ($statement) = "INSERT INTO ".$mail_table." (mail_id,".
$mail_table.".date_day,".
$mail_table.".date_time,".
"remote_ip,remote_host,remote_info) ".
"VALUES(".
$mail_id.",".
$dbh->quote($year.'-'.$mon.'-'.$day).",".
$dbh->quote($hour.':'.$min.':'.$sec).",".
$dbh->quote($self->qp->connection->remote_ip).",".
$dbh->quote($self->qp->connection->remote_host).",".
$dbh->quote($self->qp->connection->remote_info).")";
$self->log(LOGDEBUG,"connect_handler statement:".$statement);
12 years ago
$dbh->do($statement) || $self->log(LOGERROR,$dbh->errstr());
12 years ago
return(DECLINED);
12 years ago
}
sub mail_handler {
my ($self, $transaction, $sender) = @_;
12 years ago
my ($statement) = "UPDATE ".$mail_table." SET sender=".
$dbh->quote($sender->user.'@'.$sender->host).
" WHERE mail_id=".$mail_id;
12 years ago
$self->log(LOGDEBUG,"mail_handler statement:".$statement);
12 years ago
$dbh->do($statement)
12 years ago
|| $self->log(LOGERROR,$dbh->errstr());
return(DECLINED);
12 years ago
}
sub rcpt_handler {
my ($self,$transaction,$recipient) = @_;
12 years ago
my ($statement) = "INSERT INTO ".$rcpt_table." (mail_id,".
"recipient) ".
"VALUES(".
$mail_id.",".
$dbh->quote($recipient->user.'@'.$recipient->host).")";
12 years ago
$self->log(LOGDEBUG,"rcpt_handler statement:".$statement);
12 years ago
$dbh->do($statement) || $self->log(LOGERROR,$dbh->errstr());
12 years ago
return(DECLINED);
12 years ago
}
sub data_post_handler{
my ($self,$transaction) = @_;
my ($header) = $transaction->header();
# In data_post handler the header is not actual,
# but in queue handler somethimes there are problems with
# header, sizes, subject logs (don't know why?!?!)
12 years ago
#'touch' this message :-))
$header->add("X-Sql-Log-ID",$mail_id);
12 years ago
my $subject = decode('MIME-Header', scalar $header->get("Subject"));
chomp($subject);
# Only keep the first 255 chars to be sure it can be inserted in the table
$subject= substr $subject, 0, 254 if (length($subject) > 254);
my ($statement) = "UPDATE ".$mail_table." SET subject=".
$dbh->quote($subject).
",header_size=".$dbh->quote(length($header->as_string())).
",body_size=".$dbh->quote($transaction->data_size()).
" WHERE mail_id=".$mail_id;
12 years ago
$self->log(LOGDEBUG,"data_post_handler statement:".$statement);
12 years ago
$dbh->do($statement)
12 years ago
|| $self->log(LOGERROR,$dbh->errstr());
#if $args{H} then log the message header in the main 'messages' table
if ($log_header){
$statement = "UPDATE ".$mail_table." SET ".
"header=".$dbh->quote($header->as_string()).
"WHERE mail_id=".$mail_id;
$self->log(LOGDEBUG,"data_post_handler log header statement:".$statement);
12 years ago
$dbh->do($statement)
12 years ago
|| $self->log(LOGERROR,$dbh->errstr());
}
12 years ago
#if args{B} then log message body in a separated table
if ($log_all_body) {
my (@body) = ($transaction->header()->as_string());
12 years ago
$transaction->body_resetpos();
12 years ago
while(my $line = $transaction->body_getline()) {
push(@body,$line);
}
12 years ago
$statement = "INSERT INTO ".$body_table." (mail_id,body)".
" VALUES(".$mail_id.",".$dbh->quote(join('',@body)).")";
$self->log(LOGDEBUG,"data_post body log statement:".$statement);
12 years ago
$dbh->do($statement)
12 years ago
|| $self->log(LOGERROR,$dbh->errstr());
}
12 years ago
return(DECLINED);
12 years ago
}
sub queue_handler {
my ($self,$transaction) = @_;
my ($status) = $transaction->header->get('X-Spam-Status') or return (DECLINED);
my ($score) = ($status =~ m/hits=(\d+\.\d+)/)[0] || "0" ;
12 years ago
my ($statement) = "UPDATE ".$mail_table." SET ".
"spam_status=".$dbh->quote($score).
" WHERE mail_id=".$mail_id;
12 years ago
$self->log(LOGDEBUG,"queue_handler statement:".$statement);
12 years ago
$dbh->do($statement)
12 years ago
|| $self->log(LOGERROR,$dbh->errstr());
return(DECLINED);
12 years ago
}
sub deny_handler {
my ($self,$transaction,$plugin,$code,$msg) = @_;
12 years ago
my ($statement) = "UPDATE ".$mail_table." SET ".
"deny='YES'".
",deny_plugin=".$dbh->quote($plugin).
",deny_code=".$dbh->quote($code).
",deny_msg=".$dbh->quote($msg).
" WHERE mail_id=".$mail_id;
12 years ago
$self->log(LOGDEBUG,"deny_handler statement:".$statement);
12 years ago
$dbh->do($statement) || $self->log(LOGERROR,$dbh->errstr());
12 years ago
#if configured to log the body of the denied messages
if ($log_deny_body && ! $log_all_body) {
my (@body) = ($transaction->header()->as_string());
12 years ago
$transaction->body_resetpos();
12 years ago
while (my $line = $transaction->body_getline()) {
push(@body,$line);
}
12 years ago
$statement = "INSERT INTO ".$body_table." (mail_id,body)".
" VALUES(".$mail_id.",".$dbh->quote(join('',@body)).")";
12 years ago
$self->log(LOGDEBUG,"data_post body log statement:".$statement);
12 years ago
$dbh->do($statement) || $self->log(LOGERROR,$dbh->errstr());
12 years ago
}
12 years ago
return(DECLINED);
12 years ago
}
sub disconnect_handler {
my ($self,$transaction) = @_;
12 years ago
$dbh->disconnect();
12 years ago
return(DECLINED);
12 years ago
}
=head1 NAME
log2sql - qmstpd plugin for logging information for mail messages in a SQL database.
=head1 DESCRIPTION
The plugin logs the information for mail messages: remote_ip, remote_host, remote_info,
sender, subject, header_size, body_size, spam_status (this is the 'hits' field from spamassassin 'X-Spam-Status' header line), header ,all recipients of the message, etc. The data is logged into two tables: messages table - one message per row,
recipients table - one recipient per row. One message could have more than one recipient.
=head1 CONFIGURATION
The plugin accepts the following parameters:
t - type of the SQL database (mysql, postgresql, etc.)
d - database name (default qpsmtpd)
h - hostname of the database server (optional)
P - tcp port of the database server (optional)
o - dsn options (in format like DBI dsn options). For example: "o mysql_socket=/path/to/socket"
m - database table for mail messages (default messages)
r - database table for the recipients (default rcpts)
u - database user (default root)
p - database password (default '')
H - log the header of the message in the main table for mail messages (e.g. 'messages'). For example "H Yes".
12 years ago
=head1 CONFIGURATION OF THE ADDITIONAL FEATURES
You can configure the following additional features:
D - log the deny information for denied messages in the 'messages' table. Example "D Yes". The 'deny' hook must be enabled in order to use this feature.
B - log the body of all messages in the separated table (e.g. 'message_body'). Example "B Yes".
DB - log the body of the denied messages. D parameter required. Example "DB Yes".
b - sql table for the message bodies. Default: 'message_body'.
12 years ago
As default the plugin sets a note 'log2sql_mail_id'. This chunk of data is passed to other plugins. As default the plugin sets a field 'X-Sql-Log-ID' in the message header.
=head1 SQL TABLES
Here are the CREATE statements for the tables. Assuming the 'messages' table is the table for logging main information for messages, 'rcpts' table is the table for recipients, 'message_body' is optional table if You want to log messages bodies (of the denied messages OR of all messages). This syntax is MySQL syntax. For other databases the syntax could be slightly different.
CREATE TABLE `messages` (
`mail_id` varchar(255) NOT NULL default '',
`date_day` date,
`date_time` time,
`remote_ip` varchar(255) default NULL,
`remote_host` varchar(255) default NULL,
`remote_info` varchar(255) default NULL,
`sender` varchar(255) default NULL,
`subject` varchar(255) default NULL,
`header_size` int(11) default NULL,
`body_size` int(11) default NULL,
`spam_status` float default NULL,
`header` text,
`deny` set('YES','NO') default NULL,
`deny_plugin` varchar(100) default '',
`deny_code` int(11) default '0',
`deny_msg` text,
PRIMARY KEY (`mail_id`)
) TYPE=MyISAM;
CREATE TABLE `rcpts` (
`rcpt_id` int(11) NOT NULL auto_increment,
`mail_id` varchar(255) NOT NULL default '',
`recipient` varchar(255),
PRIMARY KEY (`rcpt_id`)
) TYPE=MyISAM;
CREATE TABLE `message_body` (
`bdy_id` int(11) NOT NULL auto_increment,
`mail_id` varchar(255) NOT NULL default '',
`body` text,
PRIMARY KEY (`bdy_id`)
) TYPE=MyISAM;
12 years ago
=head1 AUTHOR
Todor Todorov, E<lt>tttodorov@yahoo.comE<gt>
L<http://www.geocities.com/tttodorov/>
=cut