From 339312c92a71a0943ae34aab27b1026bb3479d0d Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Tue, 21 Jun 2016 02:42:25 +0200 Subject: Make subclasses of Net::Server Spline::Socketmap is now a subclasses of Net::Server and Spline::Socketmap::Srs is a subclass of Spline::Socketmap. This way the Srs stuff is completly independent of the postfix-socketmap protocol handling. The config is located in the instances and configurable with Net::Server command line options or config file. --- t/netstring.t | 44 ++++++++++++++ t/socketmap.t | 132 +++++++++++++++++++++++++++++++++++++++++ t/srs.t | 184 +++++++++++++++++++++++++++++++++------------------------- 3 files changed, 281 insertions(+), 79 deletions(-) create mode 100644 t/netstring.t create mode 100644 t/socketmap.t (limited to 't') diff --git a/t/netstring.t b/t/netstring.t new file mode 100644 index 0000000..de0b61e --- /dev/null +++ b/t/netstring.t @@ -0,0 +1,44 @@ +use strict; +use warnings; + +use Test::More tests => 11; +use Test::Exception; + +BEGIN { + use_ok 'Spline::Socketmap', qw( + netstring_read + netstring_write + ) or BAIL_OUT; +} + +sub output($) { + my $data = shift; + + my $output; + open(my $fh, '>', \$output); + netstring_write($fh, $data); + return $output; +} + +sub input($) { + my $data = shift; + + open(my $fh, '<', \$data); + return netstring_read($fh); +} + +# Netstring output +is(output('test'), '4:test,', 'Netstring output'); +is(output(''), '0:,', 'Empty netstring output'); +is(output('. '), '2:. ,', 'Preserve spaces'); + +# Netstring input +is(input('4:test,'), 'test', 'Basic netstring input'); +is(input('0:,'), '', 'Empty netstring input'); +throws_ok { input('4:test') } qr/, missing/, 'Missing final ,'; +throws_ok { input('5:test') } qr/4 of 5 bytes/, 'Data too short'; +throws_ok { input('4test') } qr/Invalid length/, 'Format error'; +throws_ok { input('') } qr/Cannot read .* length/, 'Empty input'; +throws_ok { netstring_read('') } qr/Filehandle required/, 'No filehandle'; + +# vim: set et ts=4: diff --git a/t/socketmap.t b/t/socketmap.t new file mode 100644 index 0000000..a788663 --- /dev/null +++ b/t/socketmap.t @@ -0,0 +1,132 @@ +use strict; +use warnings; + +use Test::More tests => 19; +use Test::Exception; + +BEGIN { + use_ok 'Spline::Socketmap', qw( + lookup + handle_request + socketmap_protocol + process_request + ) or BAIL_OUT; +} + +# Default handler +throws_ok { lookup(undef, 'map', 'key') } qr/Not implemented/, 'No handler'; + +my $socketmap = new Spline::Socketmap(); + +# Parsing socketmap input +my @args; +{ + no warnings 'redefine'; + local *Spline::Socketmap::lookup = sub { shift; @args = @_; return }; + is($socketmap->handle_request('map key with whitespace'), undef, + 'Protocol parsed'); + is_deeply(\@args, ['map', 'key with whitespace'], 'Parsing with whitespace'); +} + +{ + no warnings 'redefine'; + local *Spline::Socketmap::lookup = sub { return 'OK test' }; + is(handle_request($socketmap, 'map key'), 'OK test', 'Result passed'); +} + +# Socketmap protocol handling +{ + no warnings 'redefine'; + local *Spline::Socketmap::lookup = sub { }; + is($socketmap->socketmap_protocol('map key'), undef, 'Pass undef return value'); +} + +{ + no warnings 'redefine'; + local *Spline::Socketmap::lookup = sub { shift; shift; return shift }; + is($socketmap->socketmap_protocol('map key'), 'key', 'Pass return value'); +} + +{ + no warnings 'redefine'; + local *Spline::Socketmap::lookup = sub { sleep 2 }; + $socketmap->{server}->{timeout} = 1; + + is($socketmap->socketmap_protocol('map key'), 'TEMP Timeout', + 'Temporary error on timeout'); +} + +{ + no warnings 'redefine'; + local *Spline::Socketmap::lookup = sub { die 'Other error' }; + like($socketmap->socketmap_protocol('map key'), qr/TEMP Other error/, + 'Temporary error on die'); +} + +# process_request (Net::Server) +my ($input, $output, $log); +my $dummy = {}; +{ + package Dummy; + use base 'Spline::Socketmap'; + sub recv { return $input } + sub send { shift; $output = shift } + sub log { shift; shift; $log = shift } + bless $dummy, 'Dummy'; +} + +{ + no warnings 'redefine'; + local *Dummy::socketmap_protocol = sub { shift; return &{shift()} }; + + $input = 'OK test'; + process_request($dummy); + is($output, 'OK test', 'Send output if defined (ok)'); + + $input = 'NOTFOUND '; + process_request($dummy); + is($output, 'NOTFOUND ', 'Send output if defined (notfound)'); +} + +{ + no warnings 'redefine'; + local *Dummy::socketmap_protocol = sub { }; + + process_request($dummy); + like($output, qr/TEMP .* error/, 'Temporary error if result is undef'); +} + +{ + no warnings 'redefine'; + local *Dummy::recv = sub { sleep 10 }; + $dummy->{server}->{timeout} = 1; + + lives_ok { process_request($dummy) } 'Temporary error with hanging input'; + like($output, qr/TEMP Timeout/, 'Timeout on hanging input'); +} + +# recv +{ + my $recv_data; + open(my $fh, '<', \$recv_data); + local *STDIN = $fh; + + $log = undef; + $recv_data = '7:OK test,'; + is(Spline::Socketmap::recv($dummy), 'OK test', 'Receive works'); + is($log, 'OK test', 'Log data after receiving'); +} + +# send +{ + my $send_data; + open(my $fh, '>', \$send_data); + local *STDOUT = $fh; + + $log = undef; + lives_ok { Spline::Socketmap::send($dummy, 'OK test') } 'Sending works'; + is($send_data, '7:OK test,', 'Send data in netstring format'); + is($log, 'OK test', 'Log data before sending'); +} + +# vim: set et ts=4: diff --git a/t/srs.t b/t/srs.t index a1c8104..b6482b4 100644 --- a/t/srs.t +++ b/t/srs.t @@ -1,30 +1,16 @@ use strict; use warnings; -use Test::More tests => 39; +use Test::More tests => 35; use Test::Exception; use Test::MockModule; BEGIN { use_ok 'Spline::Srs', qw( - config_set - config_get check_exclude - srs_forward - srs_reverse ) or BAIL_OUT; } -# Test config -my $old_value = config_get('hash_length'); -isnt($old_value, undef, 'Config value exists'); -config_set('hash_length', $old_value+1); -isnt(config_get('hash_length'), $old_value, 'Config changed'); - -is(config_get('invalid'), undef, 'Invalid config value is undef'); -config_set('invalid', 'test'); -is(config_get('invalid'), undef, 'Cannot set invalid config values'); - # Testing ignores is(check_exclude('test@example.com',['example.com']), 1, 'Ignore matching domain'); is(check_exclude('test@example.de', ['example.com']), 0, 'Do not ignore non-matching domain'); @@ -32,80 +18,120 @@ is(check_exclude('test@test.example.com', ['example.com']), 0, 'Do not ignore su is(check_exclude('test@test.example.com', ['.example.com']), 1, 'Ignore sub-domain if requested'); # Try without config -throws_ok { srs_forward('something@example.com') } qr/configure.*secret/, 'Secret required'; -config_set('secret', 'abc'); -throws_ok { srs_forward('something@example.com') } qr/configure.*secret/, 'Long secret required'; -config_set('secret', '12345678901234567890'); -throws_ok { srs_forward('something@example.com') } qr/configure.*alias/, 'Alias required'; -config_set('alias', 'something'); -lives_ok { srs_forward('something@example.com') } 'Config complete'; +my %config = (); +throws_ok { new Spline::Srs(\%config) } qr/configure.*secret/, 'Secret required'; +$config{secret} = 'abc'; +throws_ok { new Spline::Srs(\%config) } qr/configure.*secret/, 'Long secret required'; +$config{secret} = '12345678901234567890'; +throws_ok { new Spline::Srs(\%config) } qr/configure.*alias/, 'Alias required'; +$config{alias} = 'something'; +lives_ok { new Spline::Srs(\%config) } 'Config complete'; # Testing ignores with config -config_set('alias', 'domain.invalid'); -is(srs_forward('test@domain.invalid'), 'test@domain.invalid', 'Exclude alias domain by default'); -isnt(srs_forward('test@example.com'), 'test@example.com', 'Not excluding something other'); +$config{alias} = 'domain.invalid'; +{ + my $srs = new Spline::Srs(\%config); + is($srs->forward('test@domain.invalid'), 'test@domain.invalid', + 'Exclude alias domain by default'); + isnt($srs->forward('test@example.com'), 'test@example.com', + 'Not excluding something other'); +} -config_set('excludes', ['example.com']); -is(srs_forward('test@domain.invalid'), 'test@domain.invalid', 'Exclude alias domain by default'); -is(srs_forward('test@example.com'), 'test@example.com', 'Exclude is working'); -isnt(srs_forward('test@test.example.com'), 'test@test.example.com', 'Excluded domain should not match subdomain'); +$config{excludes} = ['example.com']; +{ + my $srs = new Spline::Srs(\%config); + is($srs->forward('test@domain.invalid'), 'test@domain.invalid', + 'Exclude alias domain by default'); + is($srs->forward('test@example.com'), 'test@example.com', + 'Exclude is working'); + isnt($srs->forward('test@test.example.com'), 'test@test.example.com', + 'Excluded domain should not match subdomain'); +} -config_set('excludes', ['.example.com']); -is(srs_forward('test@domain.invalid'), 'test@domain.invalid', 'Exclude alias domain by default'); -is(srs_forward('test@test.example.com'), 'test@test.example.com', 'Exclude subdomains'); -isnt(srs_forward('test@example.com'), 'test@example.com', 'Excluded subdomain should not match domain'); +$config{excludes} = ['.example.com']; +{ + my $srs = new Spline::Srs(\%config); + is($srs->forward('test@domain.invalid'), 'test@domain.invalid', + 'Exclude alias domain by default'); + is($srs->forward('test@test.example.com'), 'test@test.example.com', + 'Exclude subdomains'); + isnt($srs->forward('test@example.com'), 'test@example.com', + 'Excluded subdomain should not match domain'); +} -config_set('excludes', ['example.com', '.example.com']); -is(srs_forward('test@domain.invalid'), 'test@domain.invalid', 'Exclude alias domain by default'); -is(srs_forward('test@test.example.com'), 'test@test.example.com', 'Multiple excludes'); -is(srs_forward('test@example.com'), 'test@example.com', 'Multiple excludes'); +$config{excludes} = ['example.com', '.example.com']; +{ + my $srs = new Spline::Srs(\%config); + is($srs->forward('test@domain.invalid'), 'test@domain.invalid', + 'Exclude alias domain by default'); + is($srs->forward('test@test.example.com'), 'test@test.example.com', + 'Multiple excludes'); + is($srs->forward('test@example.com'), 'test@example.com', + 'Multiple excludes'); +} # SRS Forward -config_set('secret', '12345678901234567890'); -config_set('alias', 'domain.invalid'); -config_set('excludes', undef); -my $result = srs_forward('alex@example.com'); -isnt($result, undef, 'Not undef'); -isnt($result, 'alex@example.com', 'Not rewritten'); -like($result, qr/^SRS0[+=-]/, 'SRS0 Prefix'); -like($result, qr/\@domain\.invalid$/, 'Rewrite to alias-Domain'); - -# Check SRS forward an backwards -my $address = 'alex@example.com'; -my $srs_address = srs_forward($address); -isnt($srs_address, undef, 'Forward works'); -isnt($srs_address, $address, 'Forward works'); -is(srs_reverse($srs_address), $address, 'Reverse maps back to original'); - -# Some checks requires a fixed time for deterministic output +$config{secret} = '12345678901234567890'; +$config{alias} = 'domain.invalid'; +$config{excludes} = undef; +$config{hash_length} = 6; my ($old_srs_address, $broken); { - no warnings 'redefine'; - my $timestamp_create = \&Mail::SRS::timestamp_create; - local *Mail::SRS::timestamp_create = sub { - my $self = shift; - $timestamp_create->($self, 1); - }; - - $old_srs_address = srs_forward($address); - isnt($old_srs_address, $srs_address, 'Time should have changed'); - is($old_srs_address, 'SRS0=R0OuN1=AA=example.com=alex@domain.invalid', 'exakt match with fixed time'); - - # Hash with broken chars - $broken = srs_forward('nk@example.com'); - is($broken, 'SRS0=s/6g+P=AA=example.com=nk@domain.invalid', 'srs address is as expected'); + my $srs = new Spline::Srs(\%config); + my $result = $srs->forward('alex@example.com'); + isnt($result, undef, 'Not undef'); + isnt($result, 'alex@example.com', 'Not rewritten'); + like($result, qr/^SRS0[+=-]/, 'SRS0 Prefix'); + like($result, qr/\@domain\.invalid$/, 'Rewrite to alias-Domain'); + + # Check SRS forward an backwards + my $address = 'alex@example.com'; + my $srs_address = $srs->forward($address); + isnt($srs_address, undef, 'Forward works'); + isnt($srs_address, $address, 'Forward works'); + is($srs->reverse($srs_address), $address, 'Reverse maps back to original'); + + # Some checks requires a fixed time for deterministic output + { + no warnings 'redefine'; + my $timestamp_create = \&Mail::SRS::timestamp_create; + local *Mail::SRS::timestamp_create = sub { + my $self = shift; + $timestamp_create->($self, 1); + }; + + $old_srs_address = $srs->forward($address); + isnt($old_srs_address, $srs_address, 'Time should have changed'); + is($old_srs_address, 'SRS0=R0OuN1=AA=example.com=alex@domain.invalid', + 'exakt match with fixed time'); + + # Hash with broken chars + $broken = $srs->forward('nk@example.com'); + is($broken, 'SRS0=s/6g+P=AA=example.com=nk@domain.invalid', + 'srs address is as expected'); + } + + # Timestamp should timeout (but old timestamps can be ignored) + throws_ok { $srs->reverse($old_srs_address) } qr/Invalid timestamp/, + 'Timestamp should be to old'; } -# Timestamp should timeout (but old timestamps can be ignored) -throws_ok { srs_reverse($old_srs_address) } qr/Invalid timestamp/, 'Timestamp should be to old'; -config_set('ignore_time', 1); -lives_ok { srs_reverse($old_srs_address) } 'Ignore old timestamp'; - -# libsrs_alt uses different base64 chars and should also work -lives_ok { srs_reverse($broken) } 'Reverse with special chars'; -$broken = 'SRS0=s.6g_P=AA=example.com=nk@domain.invalid'; -throws_ok { srs_reverse($broken) } qr/Invalid hash/, 'Reverse with legacy chars'; -config_set('srsalt_fallback', 1); -is(srs_reverse($broken), 'nk@example.com', 'Reverse with legacy chars'); +$config{'ignore_time'} = 1; +{ + my $srs = new Spline::Srs(\%config); + lives_ok { $srs->reverse($old_srs_address) } 'Ignore old timestamp'; + + # libsrs_alt uses different base64 chars and should also work + lives_ok { $srs->reverse($broken) } 'Reverse with special chars'; + $broken = 'SRS0=s.6g_P=AA=example.com=nk@domain.invalid'; + throws_ok { $srs->reverse($broken) } qr/Invalid hash/, + 'Reverse with legacy chars'; +} + +$config{'srsalt_fallback'} = 1; +{ + my $srs = new Spline::Srs(\%config); + is($srs->reverse($broken), 'nk@example.com', 'Reverse with legacy chars'); +} # vim: set et ts=4: -- cgit v1.2.3-1-g7c22