]> Sergey Matveev's repositories - public-inbox.git/blobdiff - t/nntpd-tls.t
nntp: support COMPRESS DEFLATE per RFC 8054
[public-inbox.git] / t / nntpd-tls.t
index e8fb63b46fc80a204f774d1718b6158249687eba..84d6e3c08f8bbc499e7698b33e81a7fb9f39092d 100644 (file)
@@ -4,13 +4,18 @@ use strict;
 use warnings;
 use Test::More;
 use File::Temp qw(tempdir);
-use Socket qw(SOCK_STREAM);
+use Socket qw(SOCK_STREAM IPPROTO_TCP SOL_SOCKET);
 # IO::Poll and Net::NNTP are part of the standard library, but
 # distros may split them off...
 foreach my $mod (qw(DBD::SQLite IO::Socket::SSL Net::NNTP IO::Poll)) {
        eval "require $mod";
        plan skip_all => "$mod missing for $0" if $@;
 }
+Net::NNTP->can('starttls') or
+       plan skip_all => 'Net::NNTP does not support TLS';
+IO::Socket::SSL->VERSION(2.007) or
+       plan skip_all => 'IO::Socket::SSL <2.007 not supported by Net::NNTP';
+
 my $cert = 'certs/server-cert.pem';
 my $key = 'certs/server-key.pem';
 unless (-r $key && -r $cert) {
@@ -24,6 +29,9 @@ require './t/common.perl';
 require PublicInbox::InboxWritable;
 require PublicInbox::MIME;
 require PublicInbox::SearchIdx;
+my $need_zlib;
+eval { require Compress::Raw::Zlib } or
+       $need_zlib = 'Compress::Raw::Zlib missing';
 my $version = 2; # v2 needs newer git
 require_git('2.6') if $version >= 2;
 my $tmpdir = tempdir('pi-nntpd-tls-XXXXXX', TMPDIR => 1, CLEANUP => 1);
@@ -34,15 +42,8 @@ my $pi_config = "$tmpdir/pi_config";
 my $group = 'test-nntpd-tls';
 my $addr = $group . '@example.com';
 my $nntpd = 'blib/script/public-inbox-nntpd';
-my %opts = (
-       LocalAddr => '127.0.0.1',
-       ReuseAddr => 1,
-       Proto => 'tcp',
-       Type => SOCK_STREAM,
-       Listen => 1024,
-);
-my $starttls = IO::Socket::INET->new(%opts);
-my $nntps = IO::Socket::INET->new(%opts);
+my $starttls = tcp_server();
+my $nntps = tcp_server();
 my ($pid, $tail_pid);
 END {
        foreach ($pid, $tail_pid) {
@@ -132,6 +133,8 @@ for my $args (
        my $c = Net::NNTP->new($nntps_addr, %o, SSL => 1);
        my $list = $c->list;
        is_deeply($list, $expect, 'NNTPS LIST works');
+       unlike(get_capa($c), qr/\bSTARTTLS\r\n/,
+               'STARTTLS not advertised for NNTPS');
        is($c->command('QUIT')->response(), Net::Cmd::CMD_OK(), 'QUIT works');
        is(0, sysread($c, my $buf, 1), 'got EOF after QUIT');
 
@@ -143,6 +146,8 @@ for my $args (
        is($c->code, 382, 'got 382 for STARTTLS');
        $list = $c->list;
        is_deeply($list, $expect, 'LIST works after STARTTLS');
+       unlike(get_capa($c), qr/\bSTARTTLS\r\n/,
+               'STARTTLS not advertised after STARTTLS');
 
        # Net::NNTP won't let us do dumb things, but we need to test
        # dumb things, so use Net::Cmd directly:
@@ -153,6 +158,7 @@ for my $args (
        # STARTTLS with bad hostname
        $o{SSL_hostname} = $o{SSL_verifycn_name} = 'server.invalid';
        $c = Net::NNTP->new($starttls_addr, %o);
+       like(get_capa($c), qr/\bSTARTTLS\r\n/, 'STARTTLS advertised');
        $list = $c->list;
        is_deeply($list, $expect, 'plain LIST works again');
        ok(!$c->starttls, 'STARTTLS fails with bad hostname');
@@ -182,6 +188,27 @@ for my $args (
        is(sysread($slow, my $eof, 4096), 0, 'got EOF');
        $slow = undef;
 
+       SKIP: {
+               skip 'TCP_DEFER_ACCEPT is Linux-only', 2 if $^O ne 'linux';
+               my $var = Socket::TCP_DEFER_ACCEPT();
+               defined(my $x = getsockopt($nntps, IPPROTO_TCP, $var)) or die;
+               ok(unpack('i', $x) > 0, 'TCP_DEFER_ACCEPT set on NNTPS');
+               defined($x = getsockopt($starttls, IPPROTO_TCP, $var)) or die;
+               is(unpack('i', $x), 0, 'TCP_DEFER_ACCEPT is 0 on plain NNTP');
+       };
+       SKIP: {
+               skip 'SO_ACCEPTFILTER is FreeBSD-only', 2 if $^O ne 'freebsd';
+               if (system('kldstat -m accf_data >/dev/null')) {
+                       skip 'accf_data not loaded? kldload accf_data', 2;
+               }
+               require PublicInbox::Daemon;
+               my $var = PublicInbox::Daemon::SO_ACCEPTFILTER();
+               my $x = getsockopt($nntps, SOL_SOCKET, $var);
+               like($x, qr/\Adataready\0+\z/, 'got dataready accf for NNTPS');
+               $x = getsockopt($starttls, IPPROTO_TCP, $var);
+               is($x, undef, 'no BSD accept filter for plain NNTP');
+       };
+
        $c = undef;
        kill('TERM', $pid);
        is($pid, waitpid($pid, 0), 'nntpd exited successfully');
@@ -200,4 +227,25 @@ for my $args (
        }
 }
 done_testing();
+
+sub get_capa {
+       my ($sock) = @_;
+       syswrite($sock, "CAPABILITIES\r\n");
+       my $capa = '';
+       do {
+               my $r = sysread($sock, $capa, 8192, length($capa));
+               die "unexpected: $!" unless defined($r);
+               die 'unexpected EOF' if $r == 0;
+       } until $capa =~ /\.\r\n\z/;
+
+       my $deflate_capa = qr/\r\nCOMPRESS DEFLATE\r\n/;
+       if ($need_zlib) {
+               unlike($capa, $deflate_capa,
+                       'COMPRESS DEFLATE NOT advertised '.$need_zlib);
+       } else {
+               like($capa, $deflate_capa, 'COMPRESS DEFLATE advertised');
+       }
+       $capa;
+}
+
 1;