summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Sulfrian <alex@spline.inf.fu-berlin.de>2016-06-17 20:28:37 +0200
committerAlexander Sulfrian <alex@spline.inf.fu-berlin.de>2016-06-17 20:28:37 +0200
commitc810aa389a5c13aa1f9539d9e2d4404aeaa7e039 (patch)
tree6a28606746fcee74570c397acfb8514063bdc5a7
parentfe71a855fc8b8d7a809c2087e3b12971ab74b0e2 (diff)
downloadsrs-c810aa389a5c13aa1f9539d9e2d4404aeaa7e039.tar.gz
srs-c810aa389a5c13aa1f9539d9e2d4404aeaa7e039.tar.bz2
srs-c810aa389a5c13aa1f9539d9e2d4404aeaa7e039.zip
More Progress
Add config libsrs_alt fallback More tests
-rw-r--r--Spline/Srs.pm105
-rwxr-xr-xsrs1
-rw-r--r--t/srs.t99
3 files changed, 177 insertions, 28 deletions
diff --git a/Spline/Srs.pm b/Spline/Srs.pm
index d7fc795..8acb188 100644
--- a/Spline/Srs.pm
+++ b/Spline/Srs.pm
@@ -9,34 +9,47 @@ use Spline::Netstring;
use base 'Exporter';
our @EXPORT = qw( );
our @EXPORT_OK = qw(
+ config_set
+ config_get
check_exclude
srs_forward
srs_reverse
);
-our $alias = 'spline.inf.fu-berlin.de';
-our @excludes = (
- 'spline.inf.fu-berlin.de',
- '.spline.inf.fu-berlin.de',
- 'spline.de',
- '.spline.de',
-);
+my $config = {
+ alias => '',
+ excludes => [],
+ secret => '',
+ max_age => 49,
+ hash_length => 5,
+ hash_min => 5,
+ ignore_time => 0,
+ srsalt_fallback => 0,
+};
+
+my $srs = undef;
+
+sub config_set($$) {
+ my ($key, $value) = @_;
+ return unless defined $config->{$key};
+
+ $config->{$key} = $value;
+ $srs = undef;
+}
-my $srs = new Mail::SRS(
- Secret => "",
- MaxAge => 49,
- HashLength => 5,
- HashMin => 4,
-);
+sub config_get($) {
+ my $key = shift;
+ return $config->{$key};
+}
sub check_exclude($@) {
- my $addr = shift;
- my @excludes = @_;
+ my ($addr, $excludes) = @_;
+ return 0 unless ref($excludes) eq 'ARRAY';
my @parts = split(/@/, $addr);
my $domain = $parts[-1];
- for my $exclude (@excludes) {
+ for my $exclude (@$excludes) {
if ($exclude =~ m/^\./) {
return 1 if $domain =~ m/\Q$exclude\E$/;
}
@@ -48,18 +61,74 @@ sub check_exclude($@) {
return 0;
}
+sub replace_srsalt_chars($) {
+ my $addr = shift;
+ if ($addr =~ m/^(SRS[01][=+-])([^=]+)(=.*)$/) {
+ my ($srs, $hash, $rest) = ($1, $2, $3);
+ $hash =~ s#_#+#g;
+ $hash =~ s#\.#/#g;
+ return "$srs$hash$rest";
+ }
+
+ return $addr;
+}
+
sub srs_forward($) {
my $addr = shift;
- return if check_exclude($addr, @excludes);
- return $srs->forward($addr, $alias);
+ return $addr if check_exclude($addr, $config->{excludes});
+
+ check_configured();
+ return $srs->forward($addr, $config->{alias});
}
sub srs_reverse($) {
my $addr = shift;
+
+ check_configured();
+
+ if ($config->{srsalt_fallback}) {
+ my $result;
+ eval {
+ $result = $srs->reverse($addr);
+ };
+
+ my $err = $@;
+ if ($err) {
+ if ($err =~ m/Invalid hash/) {
+ my $fallback = replace_srsalt_chars($addr);
+ return $srs->reverse($fallback);
+ }
+
+ die $err;
+ }
+
+ return $result;
+ }
+
return $srs->reverse($addr);
}
+sub check_configured() {
+ return if defined $srs;
+
+ if (length($config->{secret}) < 20) {
+ die 'You need to configure a suitable secret';
+ }
+
+ if (!$config->{alias}) {
+ die 'You need to configure an alias domain';
+ }
+
+ $srs = new Mail::SRS(
+ Secret => $config->{secret},
+ MaxAge => $config->{max_age},
+ HashLength => $config->{hash_length},
+ HashMin => $config->{hash_min},
+ IgnoreTimestamp => $config->{ignore_time},
+ );
+}
+
sub handle($$) {
my ($map, $key) = @_;
my $result;
diff --git a/srs b/srs
index eea1711..d3b6d66 100755
--- a/srs
+++ b/srs
@@ -8,7 +8,6 @@ use Spline::Socketmap;
use Spline::Srs;
$Spline::Socketmap::timeout = 10;
-
$Spline::Socketmap::handler = sub {
Spline::Srs->handle(@_);
};
diff --git a/t/srs.t b/t/srs.t
index 824d8e9..9b225d4 100644
--- a/t/srs.t
+++ b/t/srs.t
@@ -1,31 +1,112 @@
use strict;
use warnings;
-use Test::More;# tests => 8;
+use Test::More tests => 39;
+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');
-is(check_exclude('test@test.example.com', 'example.com'), 0, 'Do not ignore sub-domain');
-is(check_exclude('test@test.example.com', '.example.com'), 1, 'Ignore sub-domain if requested');
+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');
+is(check_exclude('test@test.example.com', ['example.com']), 0, 'Do not ignore sub-domain');
+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';
+
+# 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_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_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_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');
# 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/\@spline\.inf\.fu-berlin\.de$/, 'Rewrite to spline-Domain');
+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
+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');
+}
+
+# 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';
-# SRS Reverse
-is(Spline::Srs::srs_forward('alex@domain.invalid'), 'SRS0=7tXNg=SJ=domain.invalid=alex@spline.inf.fu-berlin.de', 'Forward');
-is(Spline::Srs::srs_reverse('SRS0=7tXNg=SJ=domain.invalid=alex@spline.inf.fu-berlin.de'), 'alex@domain.invalid', 'Reverse');
+# 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');
done_testing;