2014-09-11 01:53:28 +02:00
|
|
|
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Data::Dumper;
|
|
|
|
use Test::More;
|
|
|
|
use Test::Output;
|
|
|
|
|
|
|
|
use lib 't';
|
|
|
|
use lib 'lib'; # test lib/Qpsmtpd/SMTP (vs site_perl)
|
2014-11-19 01:40:49 +01:00
|
|
|
use Qpsmtpd::Constants;
|
2014-09-11 01:53:28 +02:00
|
|
|
|
|
|
|
use_ok('Test::Qpsmtpd');
|
|
|
|
use_ok('Qpsmtpd::SMTP');
|
|
|
|
|
2014-11-19 01:40:49 +01:00
|
|
|
|
2014-09-11 01:53:28 +02:00
|
|
|
ok(my $smtp = Qpsmtpd::SMTP->new(), "new smtp");
|
|
|
|
ok(my ($smtpd, $conn) = Test::Qpsmtpd->new_conn(), "get new connection");
|
|
|
|
|
|
|
|
__new();
|
|
|
|
__fault();
|
2014-11-19 01:40:49 +01:00
|
|
|
__helo_no_host();
|
|
|
|
__helo_repeat_host();
|
|
|
|
__helo_respond('helo_respond');
|
|
|
|
__helo_respond('ehlo_respond');
|
2014-12-27 06:51:28 +01:00
|
|
|
__data_respond('data_respond');
|
2015-01-04 23:26:27 +01:00
|
|
|
__clean_authentication_results();
|
|
|
|
__authentication_results();
|
2014-09-11 01:53:28 +02:00
|
|
|
|
|
|
|
done_testing();
|
|
|
|
|
|
|
|
sub __new {
|
|
|
|
isa_ok( $smtp, 'Qpsmtpd::SMTP' );
|
|
|
|
|
|
|
|
ok( $smtp->{_commands}, "valid commands populated");
|
|
|
|
$smtp = Qpsmtpd::SMTP->new( key => 'val' );
|
|
|
|
cmp_ok( $smtp->{args}{key}, 'eq', 'val', "new with args");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
sub __fault {
|
|
|
|
|
|
|
|
my $fault;
|
|
|
|
stderr_like { $fault = $smtpd->fault }
|
2014-09-11 19:53:09 +02:00
|
|
|
qr/program fault - command not performed.*Last system error:/ms,
|
2014-09-11 01:53:28 +02:00
|
|
|
'fault outputs proper warning to STDOUT';
|
|
|
|
is($fault->[0], 451, 'fault returns 451');
|
|
|
|
|
|
|
|
stderr_like { $fault = $smtpd->fault('test message') }
|
2014-09-11 19:53:09 +02:00
|
|
|
qr/test message.*Last system error/ms,
|
2014-09-11 01:53:28 +02:00
|
|
|
'fault outputs proper custom warning to STDOUT';
|
|
|
|
is($fault->[1], 'Internal error - try again later - test message',
|
|
|
|
'returns the input message');
|
|
|
|
}
|
2014-11-19 01:40:49 +01:00
|
|
|
|
|
|
|
sub __helo_no_host {
|
|
|
|
is_deeply(
|
|
|
|
$smtpd->helo_no_host('helo'),
|
|
|
|
[501,'helo requires domain/address - see RFC-2821 4.1.1.1'],
|
|
|
|
'return helo'
|
|
|
|
);
|
|
|
|
is_deeply(
|
|
|
|
$smtpd->helo_no_host('ehlo'),
|
|
|
|
[501,'ehlo requires domain/address - see RFC-2821 4.1.1.1'],
|
|
|
|
'return ehlo'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub __helo_repeat_host {
|
|
|
|
is_deeply(
|
|
|
|
$smtpd->helo_repeat_host(),
|
|
|
|
[503,'but you already said HELO ...'], 'repeated helo verb'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub __helo_respond {
|
|
|
|
my $func = shift or die 'missing function name';
|
|
|
|
$smtpd->{_response} = undef; # reset connection
|
|
|
|
$smtpd->$func(DONE, ["Good hair day"], ['helo.example.com']);
|
|
|
|
is_deeply(
|
|
|
|
$smtpd->{_response},
|
|
|
|
undef,
|
|
|
|
"$func DONE",
|
|
|
|
);
|
2014-12-27 06:51:28 +01:00
|
|
|
|
2014-11-19 01:40:49 +01:00
|
|
|
$smtpd->$func(DENY, ["Very bad hair day"], ['helo.example.com']);
|
|
|
|
is_deeply(
|
|
|
|
$smtpd->{_response},
|
|
|
|
[550, 'Very bad hair day'],
|
|
|
|
"$func DENY",
|
|
|
|
);
|
2014-12-27 06:51:28 +01:00
|
|
|
|
2014-11-19 01:40:49 +01:00
|
|
|
$smtpd->$func(DENYSOFT, ["Bad hair day"], ['helo.example.com']),
|
|
|
|
is_deeply(
|
|
|
|
$smtpd->{_response},
|
|
|
|
[450, 'Bad hair day'],
|
|
|
|
"$func DENYSOFT",
|
|
|
|
);
|
|
|
|
|
|
|
|
$smtpd->$func(DENYSOFT_DISCONNECT, ["Bad hair day"], ['helo.example.com']),
|
|
|
|
is_deeply(
|
|
|
|
$smtpd->{_response},
|
|
|
|
[450, 'Bad hair day'],
|
|
|
|
"$func DENYSOFT_DISCONNECT",
|
|
|
|
);
|
|
|
|
|
|
|
|
$smtpd->$func(DENY_DISCONNECT, ["Very bad hair day"], ['helo.example.com']),
|
|
|
|
is_deeply(
|
|
|
|
$smtpd->{_response},
|
|
|
|
[550, 'Very bad hair day'],
|
|
|
|
"$func DENY_DISCONNECT",
|
|
|
|
);
|
|
|
|
|
|
|
|
$smtpd->$func(OK, ["You have hair?"], ['helo.example.com']);
|
|
|
|
ok($smtpd->{_response}[0] == 250, "$func, OK");
|
|
|
|
ok($smtpd->{_response}[1] =~ / Hi /, "$func, OK");
|
|
|
|
|
|
|
|
#warn Data::Dumper::Dumper($smtpd->{_response});
|
|
|
|
}
|
2014-12-27 06:51:28 +01:00
|
|
|
|
|
|
|
sub __data_respond {
|
2014-12-31 00:48:47 +01:00
|
|
|
( $smtpd ) = Test::Qpsmtpd->new_conn();
|
|
|
|
is( $smtpd->data_respond(DONE), 1, 'data_respond(DONE)' );
|
|
|
|
response_is( undef, 'data_respond(DONE) response' );
|
|
|
|
is( $smtpd->data_respond(DENY), 1, 'data_respond(DENY)' );
|
|
|
|
response_is( '554 - Message denied', 'data_respond(DENY) response' );
|
|
|
|
is( $smtpd->data_respond(DENYSOFT), 1, 'data_respond(DENYSOFT)' );
|
|
|
|
response_is( '451 - Message denied temporarily',
|
|
|
|
'data_respond(DENYSOFT) response' );
|
|
|
|
|
|
|
|
$smtpd->connection->notes( disconnected => 0 );
|
|
|
|
is( $smtpd->data_respond(DENY_DISCONNECT), 1,
|
|
|
|
'data_respond(DENY_DISCONNECT)' );
|
|
|
|
response_is( '554 - Message denied',
|
|
|
|
'data_respond(DENY_DISCONNECT) response' );
|
|
|
|
is( $smtpd->connection->notes('disconnected'), 1,
|
|
|
|
'disconnect after data_respond(DENY_DISCONNECT)' );
|
|
|
|
|
|
|
|
$smtpd->connection->notes( disconnected => 0 );
|
|
|
|
is( $smtpd->data_respond(DENYSOFT_DISCONNECT), 1,
|
|
|
|
'data_respond, DENYSOFT_DISCONNECT' );
|
|
|
|
response_is( '451 - Message denied temporarily',
|
|
|
|
'data_respond(DENYSOFT_DISCONNECT) response' );
|
|
|
|
is( $smtpd->connection->notes('disconnected'), 1,
|
|
|
|
'disconnect after data_respond(DENY_DISCONNECT)' );
|
|
|
|
|
|
|
|
is( $smtpd->data_respond(DECLINED), 1,
|
|
|
|
'data_respond(DECLINED) - no sender' );
|
|
|
|
response_is( '503 - MAIL first',
|
|
|
|
'data_respond(DECLINED) response - no sender' );
|
|
|
|
$smtpd->transaction->sender(Qpsmtpd::Address->new('sender@example.com'));
|
|
|
|
is( $smtpd->data_respond(DECLINED), 1,
|
|
|
|
'data_respond(DECLINED) - no recips' );
|
|
|
|
response_is( '503 - RCPT first',
|
|
|
|
'data_respond(DECLINED) response - no recips' );
|
|
|
|
$smtpd->transaction->add_recipient(Qpsmtpd::Address->new('recip@example.com'));
|
2014-12-27 06:51:28 +01:00
|
|
|
|
|
|
|
# data_respond also runs the data_post hooks, so this will require a bit
|
2014-12-31 00:48:47 +01:00
|
|
|
# more work to get under test. we also don't yet have a way to mock
|
|
|
|
# message data; that will probably require overriding getline()
|
|
|
|
#$smtpd->fake_data( _test_message() );
|
|
|
|
#$smtpd->fake_hook( data_post => sub { return DECLINED } );
|
|
|
|
#is( $smtpd->data_respond(DECLINED), 1, 'data_respond, DECLINED' );
|
|
|
|
}
|
|
|
|
|
2015-01-04 23:26:27 +01:00
|
|
|
sub __clean_authentication_results {
|
|
|
|
|
|
|
|
my $smtpd = _transaction_with_headers();
|
|
|
|
$smtpd->transaction->header->add('Authentication-Results', _test_ar_header());
|
|
|
|
$smtpd->clean_authentication_results();
|
|
|
|
ok(!$smtpd->transaction->header->get('Authentication-Results'), "clean_authentication_results removes A-R header");
|
|
|
|
ok($smtpd->transaction->header->get('Original-Authentication-Results'), "clean_authentication_results adds Original-A-R header");
|
|
|
|
|
|
|
|
# A-R header is _not_ DKIM signed
|
|
|
|
$smtpd = _transaction_with_headers();
|
|
|
|
$smtpd->transaction->header->add('Authentication-Results', _test_ar_header());
|
|
|
|
$smtpd->transaction->header->add('DKIM-Signature', _test_dkim_header());
|
|
|
|
$smtpd->clean_authentication_results();
|
|
|
|
ok(!$smtpd->transaction->header->get('Authentication-Results'), "clean_authentication_results removes non-DKIM-signed A-R header");
|
|
|
|
ok($smtpd->transaction->header->get('Original-Authentication-Results'), "clean_authentication_results adds non-DKIM-signed Original-A-R header");
|
|
|
|
|
|
|
|
# A-R header _is_ DKIM signed
|
|
|
|
$smtpd = _transaction_with_headers();
|
|
|
|
$smtpd->transaction->header->add('Authentication-Results', _test_ar_header());
|
|
|
|
$smtpd->transaction->header->add('DKIM-Signature', _test_dkim_sig_ar_signed());
|
|
|
|
$smtpd->clean_authentication_results();
|
|
|
|
ok($smtpd->transaction->header->get('Authentication-Results'), "clean_authentication_results removes non-DKIM-signed A-R header");
|
|
|
|
ok(!$smtpd->transaction->header->get('Original-Authentication-Results'), "clean_authentication_results adds non-DKIM-signed Original-A-R header");
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _test_ar_header {
|
|
|
|
return 'mail.theartfarm.com; iprev=pass; spf=pass smtp.mailfrom=ietf.org; dkim=fail (body hash did not verify) header.i=@kitterman.com; dkim=pass header.i=@ietf.org';
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _test_dkim_header {
|
|
|
|
return <<DKIM_HEADER
|
|
|
|
v=1; a=rsa-sha256; c=relaxed/simple; d=ietf.org; s=ietf1; t=1420404573; bh=Tq5JynLUBXNqn1f+10W+MuPhq+XAbL4oLNfT+QPVK54=; h=From:To:Date:Message-ID:In-Reply-To:References:MIME-Version:Subject:List-Id:List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe:Content-Type:Content-Transfer-Encoding:Sender; b=hsxkiq/cCNBJTOwv1wj+AA9w2ujXnpNVjpPREMSvidQQkDsnFPhASDi9hihEgEqo4LRMkbw/zHNyHBtF5TcT7WysNyItpmbnWiRksB9SuCBaqZMvqE/rNVca3goTgrb89O5SDZIWjcQ7rGvNqk/L+XL8VWCyNhOVlalnFMxKXyE=
|
|
|
|
DKIM_HEADER
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _test_dkim_sig_ar_signed {
|
|
|
|
return <<DKIM_AR_SIGNED_HEADER
|
|
|
|
v=1; a=rsa-sha256; c=relaxed/simple; d=ietf.org; s=ietf1; t=1420404573; bh=Tq5JynLUBXNqn1f+10W+MuPhq+XAbL4oLNfT+QPVK54=; h=Authentication-Results:From:To:Date:Message-ID:In-Reply-To:References:MIME-Version:Subject:List-Id:List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe:Content-Type:Content-Transfer-Encoding:Sender; b=hsxkiq/cCNBJTOwv1wj+AA9w2ujXnpNVjpPREMSvidQQkDsnFPhASDi9hihEgEqo4LRMkbw/zHNyHBtF5TcT7WysNyItpmbnWiRksB9SuCBaqZMvqE/rNVca3goTgrb89O5SDZIWjcQ7rGvNqk/L+XL8VWCyNhOVlalnFMxKXyE=
|
|
|
|
DKIM_AR_SIGNED_HEADER
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _transaction_with_headers {
|
|
|
|
( $smtpd ) = Test::Qpsmtpd->new_conn();
|
|
|
|
$smtpd->transaction->header(
|
|
|
|
Mail::Header->new(Modify => 0, MailFrom => 'COERCE')
|
|
|
|
);
|
|
|
|
return $smtpd;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub __authentication_results {
|
|
|
|
my $smtpd = _transaction_with_headers();
|
|
|
|
$smtpd->authentication_results();
|
|
|
|
my $ar = $smtpd->transaction->header->get('Authentication-Results'); chomp $ar;
|
|
|
|
ok($ar, "added A-R header: $ar");
|
|
|
|
|
|
|
|
$smtpd->{_auth} = OK;
|
|
|
|
$smtpd->{_auth_mechanism} = 'test_mech';
|
|
|
|
$smtpd->{_auth_user} = 'test@example';
|
|
|
|
$smtpd->authentication_results();
|
|
|
|
$ar = $smtpd->transaction->header->get('Authentication-Results'); chomp $ar;
|
|
|
|
ok($ar =~ /auth=pass/, "added A-R header with auth: $ar");
|
|
|
|
|
|
|
|
delete $smtpd->{_auth};
|
|
|
|
$smtpd->connection->notes('authentication_results', 'spf=pass smtp.mailfrom=ietf.org' );
|
|
|
|
$smtpd->authentication_results();
|
|
|
|
$ar = $smtpd->transaction->header->get('Authentication-Results'); chomp $ar;
|
|
|
|
ok($ar =~ /spf/, "added A-R header with SPF: $ar");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-12-31 00:48:47 +01:00
|
|
|
sub response_is {
|
|
|
|
my ( $expected, $descr ) = @_;
|
|
|
|
my $response;
|
|
|
|
my @r = @{ $smtpd->{_response} || [] };
|
|
|
|
$response .= shift @r if @r;
|
|
|
|
$response .= ' - ' . join( "\n", @r ) if @r;
|
|
|
|
is( $response, $expected, $descr );
|
2014-12-27 06:51:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub _new_transaction () {
|
|
|
|
my ($smtpd, $conn) = Test::Qpsmtpd->new_conn();
|
|
|
|
$smtpd->transaction->sender(Qpsmtpd::Address->new('sender@example.com'));
|
|
|
|
$smtpd->transaction->add_recipient(Qpsmtpd::Address->new('recip@example.com'));
|
|
|
|
return $smtpd;
|
|
|
|
};
|
|
|
|
|
|
|
|
sub _test_message {
|
|
|
|
# with \r\n (aka CRLF) endings, as a proper SMTP formatted email would
|
|
|
|
return <<"EOM"
|
|
|
|
From: Jennifer <jennifer\@example.com>\r
|
|
|
|
Subject: Persian New Year's Soup with Beans, Noodles, and Herbs Recipe at Epicurious.com\r
|
|
|
|
Date: Sun, 02 Oct 2011 14:06:06 -0700\r
|
|
|
|
Message-id: <67CC87B2-095C-45C6-BF9B-5A589AD6C264\@example.com>\r
|
|
|
|
To: Matt <matt\@example.net>\r
|
|
|
|
\r
|
|
|
|
\r
|
|
|
|
--Boundary_(ID_lBFzGVLdxsIk2GYiWhQRRQ)\r
|
|
|
|
Content-type: text/plain; CHARSET=US-ASCII\r
|
|
|
|
Content-transfer-encoding: 7BIT\r
|
|
|
|
\r
|
|
|
|
This sounds good. Can we do have it this week?\r
|
|
|
|
\r
|
|
|
|
http://www.epicurious.com/recipes/food/views/Persian-New-Years-Soup-with-Beans-Noodles-and-Herbs-em-Ash-e-reshteh-em-363446\r
|
|
|
|
\r
|
|
|
|
.\r
|
|
|
|
EOM
|
|
|
|
;
|
|
|
|
}
|