From c810aa389a5c13aa1f9539d9e2d4404aeaa7e039 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Fri, 17 Jun 2016 20:28:37 +0200 Subject: More Progress Add config libsrs_alt fallback More tests --- Spline/Srs.pm | 105 ++++++++++++++++++++++++++++++++++++++++++++++++---------- srs | 1 - t/srs.t | 99 +++++++++++++++++++++++++++++++++++++++++++++++++----- 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; -- cgit v1.2.3-1-g7c22