summaryrefslogtreecommitdiffstats
path: root/extensions/AntiSpam
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/AntiSpam')
-rw-r--r--extensions/AntiSpam/Extension.pm184
-rw-r--r--extensions/AntiSpam/template/en/default/hook/admin/admin-end_links_right.html.tmpl16
-rw-r--r--extensions/AntiSpam/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl16
-rw-r--r--extensions/AntiSpam/template/en/default/hook/global/user-error-errors.html.tmpl13
4 files changed, 228 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;
diff --git a/extensions/AntiSpam/template/en/default/hook/admin/admin-end_links_right.html.tmpl b/extensions/AntiSpam/template/en/default/hook/admin/admin-end_links_right.html.tmpl
new file mode 100644
index 000000000..e55475d98
--- /dev/null
+++ b/extensions/AntiSpam/template/en/default/hook/admin/admin-end_links_right.html.tmpl
@@ -0,0 +1,16 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% IF user.in_group('can_configure_antispam') %]
+ <dt id="antispam" >AntiSpam</dt>
+ <dd>
+ <a href="page.cgi?id=edit_table.html&amp;table=antispam_domain_blocklist">Domain Blocklist</a><br>
+ <a href="page.cgi?id=edit_table.html&amp;table=antispam_comment_blocklist">Comment Blocklist</a><br>
+ <a href="page.cgi?id=edit_table.html&amp;table=antispam_ip_blocklist">IP Address Blocklist</a><br>
+ </dd>
+[% END %]
diff --git a/extensions/AntiSpam/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl b/extensions/AntiSpam/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl
new file mode 100644
index 000000000..e8e67eccb
--- /dev/null
+++ b/extensions/AntiSpam/template/en/default/hook/admin/params/editparams-current_panel.html.tmpl
@@ -0,0 +1,16 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% IF panel.name == "auth" %]
+ [% panel.param_descs.honeypot_api_key =
+ 'API Key for http://www.projecthoneypot.org'
+ %]
+ [% panel.param_descs.honeypot_threat_threshold =
+ 'Users will be unable to create accounts if their honeypot threat score is this value or higher.'
+ %]
+[% END -%]
diff --git a/extensions/AntiSpam/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/AntiSpam/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644
index 000000000..44410ca2f
--- /dev/null
+++ b/extensions/AntiSpam/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -0,0 +1,13 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ #
+ # This Source Code Form is "Incompatible With Secondary Licenses", as
+ # defined by the Mozilla Public License, v. 2.0.
+ #%]
+
+[% IF error == "antispam_comment_blocked" %]
+ [% title = "Comment Blocked" %]
+ Your comment contains one or more words deemed inappropriate for use by the
+ administrators of this site.
+[% END %]