From e7161dda4f395d063b3635d0eacfce57706c14ec Mon Sep 17 00:00:00 2001 From: Xiaochen Wang Date: Thu, 13 Oct 2022 19:07:58 +0800 Subject: [PATCH] fixed CVE-2021-23017 * import patch from: https://nginx.org/download/patch.2021.resolver.txt * updated resolver test cases from official nginx-tests lib --- src/core/ngx_resolver.c | 8 +- .../nginx-tests/http_resolver_aaaa.t | 148 ++++----- .../nginx-tests/http_resolver_cleanup.t | 104 +++++++ .../nginx-tests/http_resolver_cname.t | 282 ++++++++++++++++++ tests/nginx-tests/nginx-tests/mail_resolver.t | 69 ++++- 5 files changed, 533 insertions(+), 78 deletions(-) create mode 100644 tests/nginx-tests/nginx-tests/http_resolver_cleanup.t create mode 100644 tests/nginx-tests/nginx-tests/http_resolver_cname.t diff --git a/src/core/ngx_resolver.c b/src/core/ngx_resolver.c index 17d654257c..65cbe9e8b3 100644 --- a/src/core/ngx_resolver.c +++ b/src/core/ngx_resolver.c @@ -4143,15 +4143,15 @@ ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src, n = *src++; } else { + if (dst != name->data) { + *dst++ = '.'; + } + ngx_strlow(dst, src, n); dst += n; src += n; n = *src++; - - if (n != 0) { - *dst++ = '.'; - } } if (n == 0) { diff --git a/tests/nginx-tests/nginx-tests/http_resolver_aaaa.t b/tests/nginx-tests/nginx-tests/http_resolver_aaaa.t index dcb31240c3..6ae16bca99 100644 --- a/tests/nginx-tests/nginx-tests/http_resolver_aaaa.t +++ b/tests/nginx-tests/nginx-tests/http_resolver_aaaa.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy rewrite ipv6/); +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -38,12 +38,12 @@ http { server { listen 127.0.0.1:8080; - listen [::1]:8080; + listen [::1]:%%PORT_8080%%; server_name localhost; location / { - resolver 127.0.0.1:8081; - proxy_pass http://$host:8080/backend; + resolver 127.0.0.1:%%PORT_8981_UDP%%; + proxy_pass http://$host:%%PORT_8080%%/backend; proxy_next_upstream http_504 timeout error; proxy_intercept_errors on; @@ -52,8 +52,8 @@ http { add_header X-Host $upstream_addr; } location /two { - resolver 127.0.0.1:8081 127.0.0.1:8082; - proxy_pass http://$host:8080/backend; + resolver 127.0.0.1:%%PORT_8981_UDP%% 127.0.0.1:%%PORT_8982_UDP%%; + proxy_pass http://$host:%%PORT_8080%%/backend; } location /backend { @@ -69,16 +69,18 @@ EOF $t->try_run('no inet6 support')->plan(72); -$t->run_daemon(\&dns_daemon, 8081, $t); -$t->run_daemon(\&dns_daemon, 8082, $t); +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->run_daemon(\&dns_daemon, port(8982), $t); -$t->waitforfile($t->testdir . '/8081'); -$t->waitforfile($t->testdir . '/8082'); +$t->waitforfile($t->testdir . '/' . port(8981)); +$t->waitforfile($t->testdir . '/' . port(8982)); ############################################################################### my (@n, $response); +my $p0 = port(8080); + like(http_host_header('aaaa.example.net', '/'), qr/\[fe80::1\]/, 'AAAA'); like(http_host_header('cname.example.net', '/'), qr/\[fe80::1\]/, 'CNAME'); like(http_host_header('cname.example.net', '/'), qr/\[fe80::1\]/, @@ -93,11 +95,11 @@ like(http_host_header('cname_a.example.net', '/'), qr/\[::1\]/, 'CNAME + AAAA'); # nonexisting IPs enumerated with proxy_next_upstream like(http_host_header('many.example.net', '/'), - qr/^\[fe80::(1\]:8080, \[fe80::2\]:8080|2\]:8080, \[fe80::1\]:8080)$/m, + qr/^\[fe80::(1\]:$p0, \[fe80::2\]:$p0|2\]:$p0, \[fe80::1\]:$p0)$/m, 'AAAA many'); like(http_host_header('many.example.net', '/'), - qr/^\[fe80::(1\]:8080, \[fe80::2\]:8080|2\]:8080, \[fe80::1\]:8080)$/m, + qr/^\[fe80::(1\]:$p0, \[fe80::2\]:$p0|2\]:$p0, \[fe80::1\]:$p0)$/m, 'AAAA many cached'); # tests for several resolvers specified in directive @@ -116,118 +118,118 @@ like(http_host_header('2.example.net', '/two'), qr/200 OK/, 'two ns cached'); # various ipv4/ipv6 combinations $response = http_host_header('z_z.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'zero zero responses'); +is(@n = $response =~ /$p0/g, 0, 'zero zero responses'); like($response, qr/502 Bad/, 'zero zero'); -like(http_host_header('z_n.example.net', '/'), qr/^\[fe80::1\]:8080$/ms, +like(http_host_header('z_n.example.net', '/'), qr/^\[fe80::1\]:$p0$/ms, 'zero AAAA'); $response = http_host_header('z_c.example.net', '/'); -is(@n = $response =~ /8080/g, 2, 'zero CNAME responses'); -like($response, qr/127.0.0.201:8080/, 'zero CNAME 1'); -like($response, qr/\[fe80::1\]:8080/, 'zero CNAME 2'); +is(@n = $response =~ /$p0/g, 2, 'zero CNAME responses'); +like($response, qr/127.0.0.201:$p0/, 'zero CNAME 1'); +like($response, qr/\[fe80::1\]:$p0/, 'zero CNAME 2'); $response = http_host_header('z_cn.example.net', '/'); -is(@n = $response =~ /8080/g, 2, 'zero CNAME+AAAA responses'); -like($response, qr/\[fe80::1\]:8080/, 'zero CNAME+AAAA 1'); -like($response, qr/\[fe80::2\]:8080/, 'zero CNAME+AAAA 2'); +is(@n = $response =~ /$p0/g, 2, 'zero CNAME+AAAA responses'); +like($response, qr/\[fe80::1\]:$p0/, 'zero CNAME+AAAA 1'); +like($response, qr/\[fe80::2\]:$p0/, 'zero CNAME+AAAA 2'); $response = http_host_header('z_e.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'zero error responses'); +is(@n = $response =~ /$p0/g, 0, 'zero error responses'); like($response, qr/502 Bad/, 'zero error'); -like(http_host_header('n_z.example.net', '/'), qr/^127.0.0.201:8080$/ms, +like(http_host_header('n_z.example.net', '/'), qr/^127.0.0.201:$p0$/ms, 'A zero'); $response = http_host_header('n_n.example.net', '/'); -is(@n = $response =~ /8080/g, 2, 'A AAAA responses'); -like($response, qr/127.0.0.201:8080/, 'A AAAA 1'); -like($response, qr/\[fe80::1\]:8080/, 'A AAAA 2'); +is(@n = $response =~ /$p0/g, 2, 'A AAAA responses'); +like($response, qr/127.0.0.201:$p0/, 'A AAAA 1'); +like($response, qr/\[fe80::1\]:$p0/, 'A AAAA 2'); -like(http_host_header('n_c.example.net', '/'), qr/^127.0.0.201:8080$/ms, +like(http_host_header('n_c.example.net', '/'), qr/^127.0.0.201:$p0$/ms, 'A CNAME'); $response = http_host_header('n_cn.example.net', '/'); -is(@n = $response =~ /8080/g, 4, 'A CNAME+AAAA responses'); -like($response, qr/127.0.0.201:8080/, 'A CNAME+AAAA 1'); -like($response, qr/127.0.0.202:8080/, 'A CNAME+AAAA 2'); -like($response, qr/\[fe80::1\]:8080/, 'A CNAME+AAAA 3'); -like($response, qr/\[fe80::2\]:8080/, 'A CNAME+AAAA 4'); +is(@n = $response =~ /$p0/g, 4, 'A CNAME+AAAA responses'); +like($response, qr/127.0.0.201:$p0/, 'A CNAME+AAAA 1'); +like($response, qr/127.0.0.202:$p0/, 'A CNAME+AAAA 2'); +like($response, qr/\[fe80::1\]:$p0/, 'A CNAME+AAAA 3'); +like($response, qr/\[fe80::2\]:$p0/, 'A CNAME+AAAA 4'); $response = http_host_header('n_e.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'A error responses'); +is(@n = $response =~ /$p0/g, 0, 'A error responses'); like($response, qr/502 Bad/, 'A error'); $response = http_host_header('c_z.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'CNAME zero responses'); +is(@n = $response =~ /$p0/g, 0, 'CNAME zero responses'); like($response, qr/502 Bad/, 'CNAME zero'); -like(http_host_header('c_n.example.net', '/'), qr/^\[fe80::1\]:8080$/ms, +like(http_host_header('c_n.example.net', '/'), qr/^\[fe80::1\]:$p0$/ms, 'CNAME AAAA'); $response = http_host_header('c_c.example.net', '/'); -is(@n = $response =~ /8080/g, 2, 'CNAME CNAME responses'); -like($response, qr/127.0.0.201:8080/, 'CNAME CNAME 1'); -like($response, qr/\[fe80::1\]:8080/, 'CNAME CNAME 2'); +is(@n = $response =~ /$p0/g, 2, 'CNAME CNAME responses'); +like($response, qr/127.0.0.201:$p0/, 'CNAME CNAME 1'); +like($response, qr/\[fe80::1\]:$p0/, 'CNAME CNAME 2'); -like(http_host_header('c1_c2.example.net', '/'), qr/^\[fe80::1\]:8080$/ms, +like(http_host_header('c1_c2.example.net', '/'), qr/^\[fe80::1\]:$p0$/ms, 'CNAME1 CNAME2'); $response = http_host_header('c_cn.example.net', '/'); -is(@n = $response =~ /8080/g, 2, 'CNAME CNAME+AAAA responses'); -like($response, qr/\[fe80::1\]:8080/, 'CNAME CNAME+AAAA 1'); -like($response, qr/\[fe80::2\]:8080/, 'CNAME CNAME+AAAA 1'); +is(@n = $response =~ /$p0/g, 2, 'CNAME CNAME+AAAA responses'); +like($response, qr/\[fe80::1\]:$p0/, 'CNAME CNAME+AAAA 1'); +like($response, qr/\[fe80::2\]:$p0/, 'CNAME CNAME+AAAA 1'); $response = http_host_header('c_e.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'CNAME error responses'); +is(@n = $response =~ /$p0/g, 0, 'CNAME error responses'); like($response, qr/502 Bad/, 'CNAME error'); $response = http_host_header('cn_z.example.net', '/'); -is(@n = $response =~ /8080/g, 2, 'CNAME+A zero responses'); -like($response, qr/127.0.0.201:8080/, 'CNAME+A zero 1'); -like($response, qr/127.0.0.202:8080/, 'CNAME+A zero 2'); +is(@n = $response =~ /$p0/g, 2, 'CNAME+A zero responses'); +like($response, qr/127.0.0.201:$p0/, 'CNAME+A zero 1'); +like($response, qr/127.0.0.202:$p0/, 'CNAME+A zero 2'); $response = http_host_header('cn_n.example.net', '/'); -is(@n = $response =~ /8080/g, 4, 'CNAME+A AAAA responses'); -like($response, qr/127.0.0.201:8080/, 'CNAME+A AAAA 1'); -like($response, qr/127.0.0.202:8080/, 'CNAME+A AAAA 2'); -like($response, qr/\[fe80::1\]:8080/, 'CNAME+A AAAA 3'); -like($response, qr/\[fe80::2\]:8080/, 'CNAME+A AAAA 4'); +is(@n = $response =~ /$p0/g, 4, 'CNAME+A AAAA responses'); +like($response, qr/127.0.0.201:$p0/, 'CNAME+A AAAA 1'); +like($response, qr/127.0.0.202:$p0/, 'CNAME+A AAAA 2'); +like($response, qr/\[fe80::1\]:$p0/, 'CNAME+A AAAA 3'); +like($response, qr/\[fe80::2\]:$p0/, 'CNAME+A AAAA 4'); $response = http_host_header('cn_c.example.net', '/'); -is(@n = $response =~ /8080/g, 2, 'CNAME+A CNAME responses'); -like($response, qr/127.0.0.201:8080/, 'CNAME+A CNAME 1'); -like($response, qr/127.0.0.202:8080/, 'CNAME+A CNAME 2'); +is(@n = $response =~ /$p0/g, 2, 'CNAME+A CNAME responses'); +like($response, qr/127.0.0.201:$p0/, 'CNAME+A CNAME 1'); +like($response, qr/127.0.0.202:$p0/, 'CNAME+A CNAME 2'); $response = http_host_header('cn_cn.example.net', '/'); -is(@n = $response =~ /8080/g, 4, 'CNAME+A CNAME+AAAA responses'); -like($response, qr/127.0.0.201:8080/, 'CNAME+A CNAME+AAAA 1'); -like($response, qr/127.0.0.202:8080/, 'CNAME+A CNAME+AAAA 2'); -like($response, qr/\[fe80::1\]:8080/, 'CNAME+A CNAME+AAAA 3'); -like($response, qr/\[fe80::2\]:8080/, 'CNAME+A CNAME+AAAA 4'); +is(@n = $response =~ /$p0/g, 4, 'CNAME+A CNAME+AAAA responses'); +like($response, qr/127.0.0.201:$p0/, 'CNAME+A CNAME+AAAA 1'); +like($response, qr/127.0.0.202:$p0/, 'CNAME+A CNAME+AAAA 2'); +like($response, qr/\[fe80::1\]:$p0/, 'CNAME+A CNAME+AAAA 3'); +like($response, qr/\[fe80::2\]:$p0/, 'CNAME+A CNAME+AAAA 4'); $response = http_host_header('cn_e.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'CNAME+A error responses'); +is(@n = $response =~ /$p0/g, 0, 'CNAME+A error responses'); like($response, qr/502 Bad/, 'CNAME+A error'); $response = http_host_header('e_z.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'error zero responses'); +is(@n = $response =~ /$p0/g, 0, 'error zero responses'); like($response, qr/502 Bad/, 'error zero'); $response = http_host_header('e_n.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'error AAAA responses'); +is(@n = $response =~ /$p0/g, 0, 'error AAAA responses'); like($response, qr/502 Bad/, 'error AAAA'); $response = http_host_header('e_c.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'error CNAME responses'); +is(@n = $response =~ /$p0/g, 0, 'error CNAME responses'); like($response, qr/502 Bad/, 'error CNAME'); $response = http_host_header('e_cn.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'error CNAME+AAAA responses'); +is(@n = $response =~ /$p0/g, 0, 'error CNAME+AAAA responses'); like($response, qr/502 Bad/, 'error CNAME+AAAA'); $response = http_host_header('e_e.example.net', '/'); -is(@n = $response =~ /8080/g, 0, 'error error responses'); +is(@n = $response =~ /$p0/g, 0, 'error error responses'); like($response, qr/502 Bad/, 'error error'); ############################################################################### @@ -257,7 +259,7 @@ sub reply_handler { use constant AAAA => 28; use constant DNAME => 39; - use constant IN => 1; + use constant IN => 1; # default values @@ -313,7 +315,7 @@ sub reply_handler { } elsif ($name eq 'cname.example.net') { $state->{cnamecnt}++; if ($state->{cnamecnt} > 2) { - $rcode = SERVFAIL; + $rcode = SERVFAIL; } push @rdata, pack("n3N nCa5n", 0xc00c, CNAME, IN, $ttl, 8, 5, 'alias', 0xc012); @@ -330,7 +332,7 @@ sub reply_handler { } } elsif ($name eq '2.example.net') { - if ($port == 8081) { + if ($port == port(8981)) { $state->{twocnt}++; } if ($state->{twocnt} & 1) { @@ -597,18 +599,18 @@ sub dns_daemon { my ($data, $recv_data); my $socket = IO::Socket::INET->new( - LocalAddr => '127.0.0.1', - LocalPort => $port, - Proto => 'udp', + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', ) or die "Can't create listening socket: $!\n"; # track number of relevant queries my %state = ( - cnamecnt => 0, - twocnt => 0, - manycnt => 0, + cnamecnt => 0, + twocnt => 0, + manycnt => 0, ); # signal we are ready diff --git a/tests/nginx-tests/nginx-tests/http_resolver_cleanup.t b/tests/nginx-tests/nginx-tests/http_resolver_cleanup.t new file mode 100644 index 0000000000..17041dce2f --- /dev/null +++ b/tests/nginx-tests/nginx-tests/http_resolver_cleanup.t @@ -0,0 +1,104 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http resolver, worker process termination. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/); + +plan(skip_all => 'win32') if $^O eq 'MSWin32'; + +$t->write_file_expand('nginx.conf', <<'EOF')->plan(1); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + resolver 127.0.0.1:%%PORT_8981_UDP%%; + proxy_pass http://example.net/$args; + } + + location /pid { + add_header X-Pid $pid always; + } + } +} + +EOF + +$t->run_daemon(\&dns_daemon, $t); +$t->run()->waitforfile($t->testdir . '/' . port(8981)); + +############################################################################### + +# truncated UDP response, no response over TCP + +my $s = http_get('/', start => 1); + +pass('request'); + +sleep 1; + +# retrasmission timer wasn't removed during resolver cleanup, +# while the event memory was freed, resulting in use-after-free +# when later removing timer in TCP connection + +http_get('/pid') =~ qr/X-Pid: (\d+)/; +kill 'TERM', $1; + +############################################################################### + +sub dns_daemon { + my ($t) = @_; + my ($data); + + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => port(8981), + Proto => 'udp', + ) + or die "Can't create UDP socket: $!\n"; + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . port(8981); + close $fh; + + while (1) { + $socket->recv($data, 65536); + # truncation bit set + $data |= pack("n2", 0, 0x8380); + $socket->send($data); + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/http_resolver_cname.t b/tests/nginx-tests/nginx-tests/http_resolver_cname.t new file mode 100644 index 0000000000..255dcd9b7c --- /dev/null +++ b/tests/nginx-tests/nginx-tests/http_resolver_cname.t @@ -0,0 +1,282 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http resolver with CNAME. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx qw/ :DEFAULT http_end /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(11); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /short { + resolver 127.0.0.1:%%PORT_8981_UDP%%; + resolver_timeout 2s; + + proxy_pass http://$host:%%PORT_8080%%/t; + } + + location /long { + resolver 127.0.0.1:%%PORT_8981_UDP%%; + resolver_timeout 5s; + + proxy_pass http://$host:%%PORT_8080%%/t; + } + + location / { } + } +} + +EOF + +$t->run_daemon(\&dns_daemon, port(8981), $t); + +$t->write_file('t', ''); +$t->run(); + +$t->waitforfile($t->testdir . '/' . port(8981)); + +############################################################################### + +# CNAME pointing to name which times out + +like(http_host('cn01.example.net', '/short'), qr/502 Bad/, 'CNAME timeout'); + +# several requests on CNAME pointing to invalid name + +my @s; + +push @s, http_host('cn03.example.net', '/long', start => 1); +push @s, http_host('cn03.example.net', '/long', start => 1); + +like(http_end(pop @s), qr/502 Bad/, 'invalid CNAME - first'); +like(http_end(pop @s), qr/502 Bad/, 'invalid CNAME - last'); + +# several requests on CNAME pointing to cached name + +@s = (); + +http_host('a.example.net', '/long'); + +push @s, http_host('cn04.example.net', '/long', start => 1); +push @s, http_host('cn04.example.net', '/long', start => 1); + +like(http_end(pop @s), qr/200 OK/, 'cached CNAME - first'); +like(http_end(pop @s), qr/200 OK/, 'cached CNAME - last'); + +# several requests on CNAME pointing to name being resolved + +@s = (); + +my $s = http_host('cn06.example.net', '/long', start => 1); + +sleep 1; + +push @s, http_host('cn05.example.net', '/long', start => 1); +push @s, http_host('cn05.example.net', '/long', start => 1); + +like(http_end(pop @s), qr/502 Bad/, 'CNAME in progress - first'); +like(http_end(pop @s), qr/502 Bad/, 'CNAME in progress - last'); + +# several requests on CNAME pointing to name which times out +# 1st request receives CNAME with short ttl +# 2nd request replaces expired CNAME + +@s = (); + +push @s, http_host('cn07.example.net', '/long', start => 1); + +sleep 2; + +push @s, http_host('cn07.example.net', '/long', start => 1); + +like(http_end(pop @s), qr/502 Bad/, 'CNAME ttl - first'); +like(http_end(pop @s), qr/502 Bad/, 'CNAME ttl - last'); + +# several requests on CNAME pointing to name +# 1st request aborts before name is resolved +# 2nd request finishes with name resolved + +@s = (); + +push @s, http_host('cn09.example.net', '/long', start => 1); +push @s, http_host('cn09.example.net', '/long', start => 1); + +select undef, undef, undef, 0.4; # let resolver hang on CNAME + +close(shift @s); + +like(http_end(pop @s), qr/200 OK/, 'abort on CNAME'); + +like(http_host('cn001.example.net', '/short'), qr/502 Bad/, 'recurse uncached'); + +############################################################################### + +sub http_host { + my ($host, $uri, %extra) = @_; + return http(< 0; + + use constant A => 1; + use constant CNAME => 5; + + 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 'b.example.net' && $type == A) { + sleep 2; + push @rdata, rd_addr($ttl, '127.0.0.1'); + + } elsif ($name eq 'cn01.example.net') { + $ttl = 1; + push @rdata, pack("n3N nCa4n", 0xc00c, CNAME, IN, $ttl, + 7, 4, "cn02", 0xc011); + + } elsif ($name =~ /cn0[268].example.net/) { + # resolver timeout + + return; + + } elsif ($name eq 'cn03.example.net') { + select undef, undef, undef, 1.1; + push @rdata, pack("n3N nC", 0xc00c, CNAME, IN, $ttl, 0); + + } elsif ($name eq 'cn04.example.net') { + select undef, undef, undef, 1.1; + push @rdata, pack("n3N nCa1n", 0xc00c, CNAME, IN, $ttl, + 4, 1, "a", 0xc011); + + } elsif ($name eq 'cn05.example.net') { + select undef, undef, undef, 1.1; + push @rdata, pack("n3N nCa4n", 0xc00c, CNAME, IN, $ttl, + 7, 4, "cn06", 0xc011); + + } elsif ($name eq 'cn07.example.net') { + $ttl = 1; + push @rdata, pack("n3N nCa4n", 0xc00c, CNAME, IN, $ttl, + 7, 4, "cn08", 0xc011); + + } elsif ($name eq 'cn09.example.net') { + if ($type == A) { + # await both HTTP requests + select undef, undef, undef, 0.2; + } + push @rdata, pack("n3N nCa1n", 0xc00c, CNAME, IN, $ttl, + 4, 1, "b", 0xc011); + + } elsif ($name eq 'cn052.example.net') { + if ($type == A) { + push @rdata, rd_addr($ttl, '127.0.0.1'); + } + + } elsif ($name =~ /cn0\d+.example.net/) { + my ($id) = $name =~ /cn(\d+)/; + $id++; + push @rdata, pack("n3N nCa5n", 0xc00c, CNAME, IN, $ttl, + 8, 5, "cn$id", 0xc012); + } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); +} + +sub rd_addr { + my ($ttl, $addr) = @_; + + my $code = 'split(/\./, $addr)'; + + 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); + $socket->send($data); + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mail_resolver.t b/tests/nginx-tests/nginx-tests/mail_resolver.t index 8dc06eb610..36d71306e6 100644 --- a/tests/nginx-tests/nginx-tests/mail_resolver.t +++ b/tests/nginx-tests/nginx-tests/mail_resolver.t @@ -23,9 +23,15 @@ use Test::Nginx::SMTP; select STDERR; $| = 1; select STDOUT; $| = 1; +eval { require IO::Socket::SSL; }; +plan(skip_all => 'IO::Socket::SSL not installed') if $@; +eval { IO::Socket::SSL::SSL_VERIFY_NONE(); }; +plan(skip_all => 'IO::Socket::SSL too old') if $@; + local $SIG{PIPE} = 'IGNORE'; -my $t = Test::Nginx->new()->has(qw/mail smtp http rewrite/)->plan(8) +my $t = Test::Nginx->new()->has(qw/mail mail_ssl smtp http rewrite/) + ->has_daemon('openssl')->plan(11) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -40,6 +46,8 @@ mail { smtp_auth none; server_name locahost; + proxy_timeout 15s; + # prevent useless resend resolver_timeout 2s; @@ -89,6 +97,14 @@ mail { 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 { @@ -115,6 +131,24 @@ http { EOF +$t->write_file('openssl.conf', <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); @@ -128,6 +162,7 @@ $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(); @@ -140,6 +175,10 @@ $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(); @@ -173,6 +212,7 @@ $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(); @@ -185,6 +225,12 @@ $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: { @@ -248,6 +294,27 @@ $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: SIZE=100'); +$s->read(); +$s->read(); + +$s->send('RCPT TO:'); +$s->check(qr/TEMPUNAVAIL/, 'PTR SSL empty'); + +$s->send('QUIT'); +$s->read(); + ############################################################################### sub reply_handler {