view mail_resolver.t @ 1971:ab45ee8011df

Tests: guarded session ticket tests for old OpenSSL versions. Much like SNI support, TLS session tickets are available starting with OpenSSL 0.9.8f if TLS extensions support is explicitly configured, and enabled by default since 0.9.8j. As such, SNI availability is checked to ensure TLS extensions support is compiled in. Additionally, the ssl_session_ticket_key.t tests for automatic ticket key rotation, which uses session ticket key callback, as introduced in OpenSSL 0.9.8h.
author Maxim Dounin <mdounin@mdounin.ru>
date Mon, 06 May 2024 00:03:16 +0300
parents cdcd75657e52
children
line wrap: on
line source

#!/usr/bin/perl

# (C) Sergey Kandaurov
# (C) Nginx, Inc.

# Tests for mail resolver.

###############################################################################

use warnings;
use strict;

use Test::More;

BEGIN { use FindBin; chdir($FindBin::Bin); }

use lib 'lib';
use Test::Nginx;
use Test::Nginx::SMTP;

###############################################################################

select STDERR; $| = 1;
select STDOUT; $| = 1;

local $SIG{PIPE} = 'IGNORE';

my $t = Test::Nginx->new()->has(qw/mail mail_ssl smtp http rewrite socket_ssl/)
	->has_daemon('openssl')->plan(11)
	->write_file_expand('nginx.conf', <<'EOF');

%%TEST_GLOBALS%%

daemon off;

events {
}

mail {
    auth_http    http://127.0.0.1:8080/mail/auth;
    smtp_auth    none;
    server_name  locahost;

    proxy_timeout 15s;

    # prevent useless resend
    resolver_timeout 2s;

    server {
        listen    127.0.0.1:8025;
        protocol  smtp;
        resolver  127.0.0.1:%%PORT_8981_UDP%%
                  127.0.0.1:%%PORT_8982_UDP%%
                  127.0.0.1:%%PORT_8983_UDP%%;
    }

    server {
        listen    127.0.0.1:8027;
        protocol  smtp;
        resolver  127.0.0.1:%%PORT_8982_UDP%%;
    }

    server {
        listen    127.0.0.1:8028;
        protocol  smtp;
        resolver  127.0.0.1:%%PORT_8983_UDP%%;
        resolver_timeout 1s;
    }

    server {
        listen    127.0.0.1:8029;
        protocol  smtp;
        resolver  127.0.0.1:%%PORT_8984_UDP%%;
    }

    server {
        listen    127.0.0.1:8030;
        protocol  smtp;
        resolver  127.0.0.1:%%PORT_8985_UDP%%;
    }

    server {
        listen    127.0.0.1:8031;
        protocol  smtp;
        resolver  127.0.0.1:%%PORT_8986_UDP%%;
        resolver_timeout 1s;
    }

    server {
        listen    127.0.0.1:8032;
        protocol  smtp;
        resolver  127.0.0.1:%%PORT_8987_UDP%%;
    }

    server {
        ssl_certificate_key localhost.key;
        ssl_certificate localhost.crt;

        listen    127.0.0.1:8033 ssl;
        protocol  smtp;
        resolver  127.0.0.1:%%PORT_8983_UDP%%;
    }
}

http {
    %%TEST_GLOBALS_HTTP%%

    server {
        listen       127.0.0.1:8080;
        server_name  localhost;

        location = /mail/auth {
            set $reply $http_client_host;

            if ($http_client_host !~ UNAVAIL) {
                set $reply OK;
            }

            add_header Auth-Status $reply;
            add_header Auth-Server 127.0.0.1;
            add_header Auth-Port %%PORT_8026%%;
            return 204;
        }
    }
}

EOF

$t->write_file('openssl.conf', <<EOF);
[ req ]
default_bits = 2048
encrypt_key = no
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
EOF

my $d = $t->testdir();

foreach my $name ('localhost') {
	system('openssl req -x509 -new '
		. "-config $d/openssl.conf -subj /CN=$name/ "
		. "-out $d/$name.crt -keyout $d/$name.key "
		. ">>$d/openssl.out 2>&1") == 0
		or die "Can't create certificate for $name: $!\n";
}

$t->run_daemon(\&Test::Nginx::SMTP::smtp_test_daemon);
$t->run_daemon(\&dns_daemon, port($_), $t) foreach (8981 .. 8987);

$t->run();

$t->waitforsocket('127.0.0.1:' . port(8026));
$t->waitforfile($t->testdir . '/' . port($_)) foreach (8981 .. 8987);

###############################################################################

# PTR

my $s = Test::Nginx::SMTP->new();
my $s2 = Test::Nginx::SMTP->new();
$s->read();
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->ok('PTR');

$s->send('QUIT');
$s->read();

$s2->read();
$s2->send('EHLO example.com');
$s2->ok('PTR waiting');

# Cached PTR prevents from querying bad ns on port 8983

$s = Test::Nginx::SMTP->new();
$s->read();
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->ok('PTR cached');

$s->send('QUIT');
$s->read();

# SERVFAIL

$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8027));
$s->read();
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->check(qr/TEMPUNAVAIL/, 'PTR SERVFAIL');

$s->send('QUIT');
$s->read();

# PTR with zero length RDATA

$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8028));
$s2 = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8028));
$s->read();
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->check(qr/TEMPUNAVAIL/, 'PTR empty');

$s->send('QUIT');
$s->read();

# resolver timeout is set

$s2->read();
$s2->send('EHLO example.com');
$s2->ok('PTR empty waiting');

# CNAME

TODO: {
local $TODO = 'support for CNAME RR';

$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8029));
$s->read();
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->ok('CNAME');

$s->send('QUIT');
$s->read();

}

# uncompressed answer

$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8030));
$s->read();
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->ok('uncompressed PTR');

$s->send('QUIT');
$s->read();

$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8031));
$s->read();
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->check(qr/TEMPUNAVAIL/, 'PTR type');

$s->send('QUIT');
$s->read();

# CNAME and PTR in one answer section

$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8032));
$s->read();
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->ok('CNAME with PTR');

$s->send('QUIT');
$s->read();

# before 1.17.3, read event while in resolving resulted in duplicate resolving

my %ssl = (
	SSL => 1,
	SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
	SSL_error_trap => sub { die $_[1] },
);

$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8033), %ssl);
$s->send('EHLO example.com');
$s->read();
$s->send('MAIL FROM:<test@example.com> SIZE=100');
$s->read();
$s->read();

$s->send('RCPT TO:<test@example.com>');
$s->check(qr/TEMPUNAVAIL/, 'PTR SSL empty');

$s->send('QUIT');
$s->read();

###############################################################################

sub reply_handler {
	my ($recv_data, $port) = @_;

	my (@name, @rdata);

	use constant NOERROR	=> 0;
	use constant SERVFAIL	=> 2;
	use constant NXDOMAIN	=> 3;

	use constant A		=> 1;
	use constant CNAME	=> 5;
	use constant PTR	=> 12;
	use constant DNAME	=> 39;

	use constant IN		=> 1;

	# default values

	my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600);

	# decode name

	my ($len, $offset) = (undef, 12);
	while (1) {
		$len = unpack("\@$offset C", $recv_data);
		last if $len == 0;
		$offset++;
		push @name, unpack("\@$offset A$len", $recv_data);
		$offset += $len;
	}

	$offset -= 1;
	my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);

	my $name = join('.', @name);
	if ($name eq 'a.example.net' && $type == A) {
		push @rdata, rd_addr($ttl, '127.0.0.1');

	} elsif ($name eq '1.0.0.127.in-addr.arpa' && $type == PTR) {
		if ($port == port(8981)) {
			push @rdata, rd_name(PTR, $ttl, 'a.example.net');

		} elsif ($port == port(8982)) {
			$rcode = SERVFAIL;

		} elsif ($port == port(8983)) {
			# zero length RDATA

			push @rdata, pack("n3N n", 0xc00c, PTR, IN, $ttl, 0);

		} elsif ($port == port(8984)) {
			# PTR answered with CNAME

			push @rdata, rd_name(CNAME, $ttl,
				'1.1.0.0.127.in-addr.arpa');

		} elsif ($port == port(8985)) {
			# uncompressed answer

			push @rdata, pack("(C/a*)6x n2N n(C/a*)3x",
				('1', '0', '0', '127', 'in-addr', 'arpa'),
				PTR, IN, $ttl, 15, ('a', 'example', 'net'));

		} elsif ($port == port(8986)) {
			push @rdata, rd_name(DNAME, $ttl, 'a.example.net');

		} elsif ($port == port(8987)) {
			# PTR answered with CNAME+PTR

			push @rdata, rd_name(CNAME, $ttl,
				'1.1.0.0.127.in-addr.arpa');
			push @rdata, pack("n3N n(C/a*)3 x", 0xc034,
				PTR, IN, $ttl, 15, ('a', 'example', 'net'));
		}

	} elsif ($name eq '1.1.0.0.127.in-addr.arpa' && $type == PTR) {
		push @rdata, rd_name(PTR, $ttl, 'a.example.net');
	}

	$len = @name;
	pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
		0, 0, @name, $type, $class) . join('', @rdata);
}

sub rd_name {
	my ($type, $ttl, $name) = @_;
	my ($rdlen, @rdname);

	@rdname = split /\./, $name;
	$rdlen = length(join '', @rdname) + @rdname + 1;
	pack("n3N n(C/a*)* x", 0xc00c, $type, IN, $ttl, $rdlen, @rdname);
}

sub rd_addr {
	my ($ttl, $addr) = @_;

	my $code = 'split(/\./, $addr)';

	# use a special pack string to not zero pad

	return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq '';

	pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
}

sub dns_daemon {
	my ($port, $t) = @_;

	my ($data, $recv_data);
	my $socket = IO::Socket::INET->new(
		LocalAddr => '127.0.0.1',
		LocalPort => $port,
		Proto => 'udp',
	)
		or die "Can't create listening socket: $!\n";

	# signal we are ready

	open my $fh, '>', $t->testdir() . '/' . $port;
	close $fh;

	while (1) {
		$socket->recv($recv_data, 65536);
		$data = reply_handler($recv_data, $port);
		$socket->send($data);
	}
}

###############################################################################