diff options
Diffstat (limited to 'extensions/AntiSpam/Extension.pm')
-rw-r--r-- | extensions/AntiSpam/Extension.pm | 184 |
1 files changed, 183 insertions, 1 deletions
diff --git a/extensions/AntiSpam/Extension.pm b/extensions/AntiSpam/Extension.pm index 5cb9928db..ef31a0b2d 100644 --- a/extensions/AntiSpam/Extension.pm +++ b/extensions/AntiSpam/Extension.pm @@ -12,7 +12,178 @@ use warnings; use base qw(Bugzilla::Extension); -our $VERSION = '0'; +use Bugzilla::Error; +use Bugzilla::Group; +use Bugzilla::Util qw(remote_ip trick_taint); +use Email::Address; +use Encode; +use Socket; +use Sys::Syslog qw(:DEFAULT setlogsock); + +our $VERSION = '1'; + +# +# project honeypot integration +# + +sub _project_honeypot_blocking { + my ($self, $api_key, $login) = @_; + my $ip = remote_ip(); + return unless $ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; + my $lookup = "$api_key.$4.$3.$2.$1.dnsbl.httpbl.org"; + return unless my $packed = gethostbyname($lookup); + my $honeypot = inet_ntoa($packed); + return unless $honeypot =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; + my ($status, $days, $threat, $type) = ($1, $2, $3, $4); + + return if $status != 127 + || $threat < Bugzilla->params->{honeypot_threat_threshold}; + + _syslog(sprintf("[audit] blocked <%s> from creating %s, honeypot %s", $ip, $login, $honeypot)); + ThrowUserError('account_creation_restricted'); +} + +sub config_modify_panels { + my ($self, $args) = @_; + push @{ $args->{panels}->{auth}->{params} }, { + name => 'honeypot_api_key', + type => 't', + default => '', + }; + push @{ $args->{panels}->{auth}->{params} }, { + name => 'honeypot_threat_threshold', + type => 't', + default => '32', + }; +} + +# +# comment blocking +# + +sub _comment_blocking { + my ($self, $params) = @_; + + # as we want to use this sparingly, we only block comments on bugs which + # the user didn't report, and skip it completely if the user is in the + # editbugs group. + my $user = Bugzilla->user; + return if $user->in_group('editbugs'); + # new bug + return unless $params->{bug_id}; + # existing bug + my $bug = ref($params->{bug_id}) + ? $params->{bug_id} + : Bugzilla::Bug->new($params->{bug_id}); + return if $bug->reporter->id == $user->id; + + my $blocklist = Bugzilla->dbh->selectcol_arrayref( + 'SELECT word FROM antispam_comment_blocklist' + ); + return unless @$blocklist; + + my $regex = '\b(?:' . join('|', map { quotemeta } @$blocklist) . ')\b'; + if ($params->{thetext} =~ /$regex/i) { + ThrowUserError('antispam_comment_blocked'); + } +} + +# +# domain blocking +# + +sub _domain_blocking { + my ($self, $login) = @_; + my $address = Email::Address->new(undef, $login); + my $blocked = Bugzilla->dbh->selectrow_array( + "SELECT 1 FROM antispam_domain_blocklist WHERE domain=?", + undef, + $address->host + ); + if ($blocked) { + _syslog(sprintf("[audit] blocked <%s> from creating %s, blacklisted domain", remote_ip(), $login)); + ThrowUserError('account_creation_restricted'); + } +} + +# +# ip blocking +# + +sub _ip_blocking { + my ($self, $login) = @_; + my $ip = remote_ip(); + trick_taint($ip); + my $blocked = Bugzilla->dbh->selectrow_array( + "SELECT 1 FROM antispam_ip_blocklist WHERE ip_address=?", + undef, + $ip + ); + if ($blocked) { + _syslog(sprintf("[audit] blocked <%s> from creating %s, blacklisted IP", $ip, $login)); + ThrowUserError('account_creation_restricted'); + } +} + +# +# hooks +# + +sub object_end_of_create_validators { + my ($self, $args) = @_; + if ($args->{class} eq 'Bugzilla::Comment') { + $self->_comment_blocking($args->{params}); + } +} + +sub user_verify_login { + my ($self, $args) = @_; + if (my $api_key = Bugzilla->params->{honeypot_api_key}) { + $self->_project_honeypot_blocking($api_key, $args->{login}); + } + $self->_ip_blocking($args->{login}); + $self->_domain_blocking($args->{login}); +} + +sub editable_tables { + my ($self, $args) = @_; + my $tables = $args->{tables}; + # allow these tables to be edited with the EditTables extension + $tables->{antispam_domain_blocklist} = { + id_field => 'id', + order_by => 'domain', + blurb => 'List of fully qualified domain names to block at account creation time.', + group => 'can_configure_antispam', + }; + $tables->{antispam_comment_blocklist} = { + id_field => 'id', + order_by => 'word', + blurb => "List of whole words that will cause comments containing \\b\$word\\b to be blocked.\n" . + "This only applies to comments on bugs which the user didn't report.\n" . + "Users in the editbugs group are exempt from comment blocking.", + group => 'can_configure_antispam', + }; + $tables->{antispam_ip_blocklist} = { + id_field => 'id', + order_by => 'ip_address', + blurb => 'List of IPv4 addresses which are prevented from creating accounts.', + group => 'can_configure_antispam', + }; +} + +# +# installation +# + +sub install_before_final_checks { + if (!Bugzilla::Group->new({ name => 'can_configure_antispam' })) { + Bugzilla::Group->create({ + name => 'can_configure_antispam', + description => 'Can configure Anti-Spam measures', + isbuggroup => 0, + }); + } +} sub db_schema_abstract_schema { my ($self, $args) = @_; @@ -83,4 +254,15 @@ sub db_schema_abstract_schema { }; } +# +# utilities +# + +sub _syslog { + my $message = shift; + openlog('apache', 'cons,pid', 'local4'); + syslog('notice', encode_utf8($message)); + closelog(); +} + __PACKAGE__->NAME; |