diff options
author | Yorhel <git@yorhel.nl> | 2014-08-29 11:50:41 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2014-08-29 11:50:41 +0200 |
commit | 1cf4252f2d313bc1e3f460a1d379a49b751d1170 (patch) | |
tree | 4385788e5553fe7376e4063961e8c7a9cbce7663 /lib | |
parent | 9a1bd46a568094ff62cfc85bc488b116042718b8 (diff) |
API: Make sure to honor the new login throttle
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Multi/API.pm | 36 | ||||
-rw-r--r-- | lib/VNDB/Handler/Users.pm | 4 | ||||
-rw-r--r-- | lib/VNDB/Util/Auth.pm | 2 | ||||
-rw-r--r-- | lib/VNDB/Util/Misc.pm | 23 | ||||
-rw-r--r-- | lib/VNDBUtil.pm | 21 |
5 files changed, 53 insertions, 33 deletions
diff --git a/lib/Multi/API.pm b/lib/Multi/API.pm index 1e4700d9..3dfaa2c9 100644 --- a/lib/Multi/API.pm +++ b/lib/Multi/API.pm @@ -14,7 +14,7 @@ use POE::Filter::VNDBAPI 'encode_filters'; use Digest::SHA 'sha256_hex'; use Encode 'encode_utf8'; use Time::HiRes 'time'; # important for throttling -use VNDBUtil 'normalize_query'; +use VNDBUtil 'normalize_query', 'norm_ip'; use JSON::XS; @@ -40,7 +40,7 @@ sub spawn { package_states => [ $p => [qw| _start shutdown log server_error client_connect client_error client_input - login login_res dbstats dbstats_res get_results get_vn get_vn_res + login login_throttle login_res dbstats dbstats_res get_results get_vn get_vn_res get_release get_release_res get_producer get_producer_res get_character get_character_res get_votelist get_votelist_res get_vnlist get_vnlist_res get_wishlist get_wishlist_res set_votelist set_vnlist @@ -420,9 +420,9 @@ sub login { return cerr $c, badarg => 'Invalid client version', field => 'clientver' if $arg->{clientver} !~ /^[a-zA-Z0-9_.\/-]{1,25}$/; if(exists $arg->{username}) { - # fetch user info - $_[KERNEL]->post(pg => query => "SELECT id, salt, encode(passwd, 'hex') as passwd FROM users WHERE username = ?", - [ $arg->{username} ], 'login_res', [ $c, $arg ]); + # check login throttle + $_[KERNEL]->post(pg => query => "SELECT timeout FROM login_throttle WHERE ip = ?", + [ norm_ip($c->{ip}) ], 'login_throttle', [ $c, $arg ]); } else { $c->{client} = $arg->{client}; $c->{clientver} = $arg->{clientver}; @@ -432,14 +432,36 @@ sub login { } -sub login_res { # num, res, [ c, arg ] +sub login_throttle { my($num, $res, $c, $arg) = (@_[ARG0, ARG1], $_[ARG2][0], $_[ARG2][1]); + my $tm = @$res && $res->[0]{timeout} > time ? $res->[0]{timeout} : time; + $tm = int $tm; + return cerr $c, auth => "Too many failed login attempts" + if $tm-time() > $VNDB::S{login_throttle}[1]; + + # fetch user info + $_[KERNEL]->post(pg => query => "SELECT id, salt, encode(passwd, 'hex') as passwd FROM users WHERE username = ?", + [ $arg->{username} ], 'login_res', [ $c, $arg, $tm ]); +} + + +sub login_res { # num, res, [ c, arg, tm ] + my($num, $res, $c, $arg, $tm) = (@_[ARG0, ARG1], $_[ARG2][0], $_[ARG2][1], $_[ARG2][2]); + return cerr $c, auth => "No user with the name '$arg->{username}'" if $num == 0; return cerr $c, auth => "Account disabled" if $res->[0]{salt} =~ /^ +$/; my $encrypted = sha256_hex($VNDB::S{global_salt}.encode_utf8($arg->{password}).encode_utf8($res->[0]{salt})); - return cerr $c, auth => "Wrong password for user '$arg->{username}'" if lc($encrypted) ne lc($res->[0]{passwd}); + + if(lc($encrypted) ne lc($res->[0]{passwd})) { + $tm += $VNDB::S{login_throttle}[0]; + $_[KERNEL]->post(pg => do => 'UPDATE login_throttle SET timeout = ? WHERE ip = ?', [ $tm, $c->{ip} ]); + $_[KERNEL]->post(pg => do => + 'INSERT INTO login_throttle (ip, timeout) SELECT ?, ? WHERE NOT EXISTS(SELECT 1 FROM login_throttle WHERE ip = ?)', + [ $c->{ip}, $tm, $c->{ip} ]); + return cerr $c, auth => "Wrong password for user '$arg->{username}'"; + } $c->{uid} = $res->[0]{id}; $c->{username} = $arg->{username}; diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index 78c4103d..680e50a0 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -140,7 +140,7 @@ sub login { return $self->resRedirect('/') if $self->authInfo->{id}; - my $tm = $self->dbThrottleGet($self->normIP); + my $tm = $self->dbThrottleGet(norm_ip($self->reqIP)); if($tm-time() > $self->{login_throttle}[1]) { $self->htmlHeader(title => mt '_login_title'); div class => 'mainbox'; @@ -167,7 +167,7 @@ sub login { if(!$frm->{_err}) { return if $self->authLogin($frm->{usrname}, $frm->{usrpass}, $ref); $frm->{_err} = [ 'login_failed' ]; - $self->dbThrottleSet($self->normIP, $tm+$self->{login_throttle}[0]); + $self->dbThrottleSet(norm_ip($self->reqIP), $tm+$self->{login_throttle}[0]); } } diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm index a1fa9b4d..89cbd215 100644 --- a/lib/VNDB/Util/Auth.pm +++ b/lib/VNDB/Util/Auth.pm @@ -165,7 +165,7 @@ sub authGetCode { my $self = shift; my $id = shift; my $time = (shift || time)/3600; # accuracy of an hour - my $uid = encode_utf8($self->{_auth} ? $self->{_auth}{id} : $self->normIP()); + my $uid = encode_utf8($self->{_auth} ? $self->{_auth}{id} : norm_ip($self->reqIP())); return lc substr sha1_hex($self->{form_salt} . $uid . encode_utf8($id||'') . pack('N', int $time)), 0, 16; } diff --git a/lib/VNDB/Util/Misc.pm b/lib/VNDB/Util/Misc.pm index 7ee0701b..b7503c24 100644 --- a/lib/VNDB/Util/Misc.pm +++ b/lib/VNDB/Util/Misc.pm @@ -6,9 +6,8 @@ use warnings; use Exporter 'import'; use TUWF ':html'; use VNDB::Func; -use Socket 'inet_pton', 'inet_ntop', 'AF_INET6'; -our @EXPORT = qw|filFetchDB ieCheck normIP|; +our @EXPORT = qw|filFetchDB ieCheck|; my %filfields = ( @@ -143,25 +142,5 @@ sub ieCheck { return 0; } - -# Normalized IP address to use for duplicate detection/throttling. For IPv4 -# this is just the normal address, but for IPv6 this is the /48 subnet, with -# the rest of the address zero'd. -sub normIP { - my $s = shift; - my $ip = $s->reqIP(); - return $ip if $ip !~ /:/; - - # There's a whole bunch of IPv6 manipulation modules on CPAN, but many seem - # quite bloated and still don't offer the functionality to return an IP - # with its mask applied (admittedly not a common operation). The libc - # socket functions will do fine in parsing and formatting IPv6 addresses, - # and the actual masking is quite trivial in binary form. - $ip = inet_pton AF_INET6, $ip; - return '::' if !$ip; - $ip =~ s/^(.{6}).+$/$1 . "\0"x10/e; - return inet_ntop AF_INET6, $ip; -} - 1; diff --git a/lib/VNDBUtil.pm b/lib/VNDBUtil.pm index e9df0a2c..febce0c0 100644 --- a/lib/VNDBUtil.pm +++ b/lib/VNDBUtil.pm @@ -7,8 +7,9 @@ use warnings; use Exporter 'import'; use Encode 'encode_utf8'; use Unicode::Normalize 'NFKD'; +use Socket 'inet_pton', 'inet_ntop', 'AF_INET6'; -our @EXPORT = qw|shorten bb2html gtintype normalize normalize_titles normalize_query imgsize|; +our @EXPORT = qw|shorten bb2html gtintype normalize normalize_titles normalize_query imgsize norm_ip|; sub shorten { @@ -244,5 +245,23 @@ sub imgsize { } +# Normalized IP address to use for duplicate detection/throttling. For IPv4 +# this is just the normal address, but for IPv6 this is the /48 subnet, with +# the rest of the address zero'd. +sub norm_ip { + my $ip = shift; + return $ip if $ip !~ /:/; + + # There's a whole bunch of IPv6 manipulation modules on CPAN, but many seem + # quite bloated and still don't offer the functionality to return an IP + # with its mask applied (admittedly not a common operation). The libc + # socket functions will do fine in parsing and formatting IPv6 addresses, + # and the actual masking is quite trivial in binary form. + $ip = inet_pton AF_INET6, $ip; + return '::' if !$ip; + $ip =~ s/^(.{6}).+$/$1 . "\0"x10/e; + return inet_ntop AF_INET6, $ip; +} + 1; |