added log messages at each exit point
added tests
added reject_type option (defer -vs- deny)
added named argument parsing
---
plugins/check_basicheaders | 117 +++++++++++++++++++++++++------------
t/plugin_tests/check_basicheaders | 63 ++++++++++++++++++++
2 files changed, 143 insertions(+), 37 deletions(-)
create mode 100644 t/plugin_tests/check_basicheaders
diff --git a/plugins/check_basicheaders b/plugins/check_basicheaders
index 973c768..114867a 100644
--- a/plugins/check_basicheaders
+++ b/plugins/check_basicheaders
@@ -15,57 +15,100 @@ some number of the days in the past or future.
=head1 CONFIGURATION
-Takes one optional parameter, the number of days in the future or past
-beyond which to reject messages. (The default is to not reject messages
-based on the date.)
+The following optional parameters exist:
-=head1 AUTHOR
-
-Written by Jim Winstead Jr.
+=head2 days
-=head1 LICENSE
-
-Released to the public domain, 26 March 2004.
+The number of days in the future or past beyond which to reject messages. When
+unset, messages are not rejected based on the date.
-=cut
+ check_basicheaders [ days 3 ]
-use Date::Parse qw(str2time);
+=head2 reject_type
-sub register {
- my ($self, $qp, @args) = @_;
+Whether to issue a permanent or temporary rejection. The default is permanent.
- if (@args > 0) {
- $self->{_days} = $args[0];
- $self->log(LOGWARN, "WARNING: Ignoring additional arguments.") if (@args >
1);
- }
-}
+ check_basicheaders reject_type [ temp | perm ]
-sub hook_data_post {
- my ($self, $transaction) = @_;
+Switching to a temporary rejection is most useful when testing the plugin. It
+allows an administrator to watch for a test period and make sure no valid mail
+is getting rejected.
- return (DENY, "You have to send some data first")
- if $transaction->data_size == 0;
-
- my $header = $transaction->header;
- return (DENY, "Mail with no From header not accepted here")
- unless $header && $header->get('From');
+=head1 AUTHOR
- my $date = $header->get('Date');
+ 2004 - Written by Jim Winstead Jr.
- return (DENY, "Mail with no Date header not accepted here")
- unless $date;
+ 2012 - added logging, named arguments, reject_type, tests - Matt Simerson
- return (DECLINED) unless defined $self->{_days};
+=head1 LICENSE
- my $ts = str2time($date);
+Released to the public domain, 26 March 2004.
- return (DECLINED) unless $ts;
+=cut
- return (DENY, "The Date in the header was too far in the past")
- if $ts < time - ($self->{_days}*24*3600);
+use Date::Parse qw(str2time);
- return (DENY, "The Date in the header was too far in the future")
- if $ts > time + ($self->{_days}*24*3600);
+sub register {
+ my ($self, $qp, @args) = @_;
+
+ if ( @args == 1 ) {
+ $self->log(LOGWARN, "deprecated arguments. Update your arguments to
this plugin");
+ $self->{_args}{days} = $args[0];
+ }
+ elsif ( @args % 2 ) {
+ $self->log(LOGWARN, "invalid arguments");
+ }
+ else {
+ $self->{_args} = { @args };
+ };
+}
- return (DECLINED);
+sub hook_data_post {
+ my ($self, $transaction) = @_;
+
+ my $deny = $self->{_args}{reject_type} eq 'temp' ? DENYSOFT : DENY;
+
+ if ( $transaction->data_size == 0 ) {
+ $self->log(LOGINFO, "fail: no data");
+ return ($deny, "You have to send some data first");
+ };
+
+ my $header = $transaction->header or do {
+ $self->log(LOGINFO, "fail: no headers");
+ return ($deny, "missing header");
+ };
+
+ if ( ! $header->get('From') ) {
+ $self->log(LOGINFO, "fail: no from");
+ return ($deny, "We require a valid From header")
+ };
+
+ my $date = $header->get('Date') or do {
+ $self->log(LOGINFO, "fail: no date");
+ return ($deny, "We require a valid Date header");
+ };
+
+ my $days = $self->{_args}{days};
+ if ( ! defined $days ) {
+ $self->log(LOGINFO, "pass: no days arg");
+ return (DECLINED);
+ };
+
+ my $ts = str2time($date) or do {
+ $self->log(LOGINFO, "skip: date not parseable ($date)");
+ return (DECLINED);
+ };
+
+ if ( $ts < time - ($days*24*3600) ) {
+ $self->log(LOGINFO, "fail: date too old ($date)");
+ return ($deny, "The Date in the header is too far in the past")
+ };
+
+ if ( $ts > time + ($days*24*3600) ) {
+ $self->log(LOGINFO, "fail: date in future ($date)");
+ return ($deny, "The Date in the header is too far in the future")
+ };
+
+ $self->log(LOGINFO, "pass");
+ return (DECLINED);
}
diff --git a/t/plugin_tests/check_basicheaders
b/t/plugin_tests/check_basicheaders
new file mode 100644
index 0000000..82e2f39
--- /dev/null
+++ b/t/plugin_tests/check_basicheaders
@@ -0,0 +1,63 @@
+#!perl -w
+
+use strict;
+use Data::Dumper;
+
+use Qpsmtpd::Address;
+use Qpsmtpd::Constants;
+
+sub register_tests {
+ my $self = shift;
+
+ $self->register_test("test_hook_data_post", 5);
+}
+
+sub test_hook_data_post {
+ my $self = shift;
+
+ my $transaction = $self->qp->transaction;
+ my $test_email = '[email protected]';
+ my $address = Qpsmtpd::Address->new( "<$test_email>" );
+ my $header = Mail::Header->new(Modify => 0, MailFrom => "COERCE");
+ my $now = `date`;
+ my $future = `date -v +6d`;
+ my $past = `date -v -6d`;
+ $self->{_args}{days} = 5;
+
+ $transaction->sender($address);
+ $transaction->header($header);
+ $transaction->header->add('From', "<$test_email>");
+ $transaction->header->add('Date', $now );
+ $transaction->body_write( "test message body " );
+
+ my ($code, $mess) = $self->hook_data_post( $transaction );
+ cmp_ok( DECLINED, '==', $code, "okay" );
+
+ $transaction->header->delete('Date');
+ ($code, $mess) = $self->hook_data_post( $transaction );
+ cmp_ok( DENY, '==', $code, "missing date ( $mess )" );
+
+ $transaction->header->delete('From');
+ $transaction->header->add('Date', $now );
+ ($code, $mess) = $self->hook_data_post( $transaction );
+ cmp_ok( DENY, '==', $code, "missing from ( $mess )" );
+
+ if ( $future ) {
+ $transaction->header->replace('Date', $future );
+ ($code, $mess) = $self->hook_data_post( $transaction );
+ cmp_ok( DENY, '==', $code, "too new ( $mess )" );
+
+ $transaction->header->replace('Date', $past );
+ ($code, $mess) = $self->hook_data_post( $transaction );
+ cmp_ok( DENY, '==', $code, "too old ( $mess )" );
+
+ }
+ else {
+ ok( 1, "skip: unable to use 'date' output");
+ ok( 1, "skip: unable to use 'date' output");
+ }
+
+ $self->{_args}{reject_type} = 'temp';
+ ($code, $mess) = $self->hook_data_post( $transaction );
+ cmp_ok( DENYSOFT, '==', $code, "defer, not deny ( $mess )" );
+};
--
1.7.9.6