]> scripts.mit.edu Git - www/ikiwiki.git/commitdiff
Merge branch 'master' of ssh://git.ikiwiki.info/srv/git/ikiwiki.info
authorJoey Hess <joey@gnu.kitenet.net>
Sat, 17 Jan 2009 03:39:46 +0000 (22:39 -0500)
committerJoey Hess <joey@gnu.kitenet.net>
Sat, 17 Jan 2009 03:39:46 +0000 (22:39 -0500)
IkiWiki/Plugin/blogspam.pm [new file with mode: 0644]
IkiWiki/Plugin/comments.pm
IkiWiki/Plugin/editpage.pm
IkiWiki/Plugin/skeleton.pm.example
debian/changelog
doc/plugins/blogspam.mdwn [new file with mode: 0644]
doc/plugins/write.mdwn
doc/todo/anti-spam_protection.mdwn
t/syntax.t

diff --git a/IkiWiki/Plugin/blogspam.pm b/IkiWiki/Plugin/blogspam.pm
new file mode 100644 (file)
index 0000000..6e68a98
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/perl
+package IkiWiki::Plugin::blogspam;
+
+use warnings;
+use strict;
+use IkiWiki 3.00;
+require RPC::XML;
+require RPC::XML::Client;
+
+my $defaulturl='http://test.blogspam.net:8888/';
+
+sub import {
+       hook(type => "getsetup", id => "blogspam",  call => \&getsetup);
+       hook(type => "checkcontent", id => "blogspam", call => \&checkcontent);
+}
+
+sub getsetup () {
+       return
+               plugin => {
+                       safe => 1,
+                       rebuild => 0,
+               },
+               blogspam_pagespec => {
+                       type => 'pagespec',
+                       example => 'postcomment(*)',
+                       description => 'PageSpec of pages to check for spam',
+                       link => 'ikiwiki/PageSpec',
+                       safe => 1,
+                       rebuild => 0,
+               },
+               blogspam_options => {
+                       type => "string",
+                       example => "blacklist=1.2.3.4,blacklist=8.7.6.5,max-links=10",
+                       description => "options to send to blogspam server",
+                       link => "http://blogspam.net/api/testComment.html#options",
+                       safe => 1,
+                       rebuild => 0,
+               },
+               blogspam_server => {
+                       type => "string",
+                       default => $defaulturl,
+                       description => "blogspam server XML-RPC url",
+                       safe => 1,
+                       rebuild => 0,
+               },
+}
+
+sub checkcontent (@) {
+       my %params=@_;
+       
+       if (exists $config{blogspam_pagespec}) {
+               return undef
+                       if ! pagespec_match($params{page}, $config{blogspam_pagespec},
+                               location => $params{page});
+       }
+
+       my $url=$defaulturl;
+       $url = $params{blogspam_server} if exists $params{blogspam_server};
+       my $client = RPC::XML::Client->new($url);
+
+       my @options = split(",", $params{blogspam_options})
+               if exists $params{blogspam_options};
+
+       # Allow short comments and whitespace-only edits, unless the user
+       # has overridden min-words themselves.
+       push @options, "min-words=0"
+               unless grep /^min-words=/i, @options;
+       # Wiki pages can have a lot of urls, unless the user specifically
+       # wants to limit them.
+       push @options, "exclude=lotsaurls"
+               unless grep /^max-links/i, @options;
+       # Unless the user specified a size check, disable such checking.
+       push @options, "exclude=size"
+               unless grep /^(?:max|min)-size/i, @options;
+       # This test has absurd false positives on words like "alpha"
+       # and "buy".
+       push @options, "exclude=stopwords";
+
+       # blogspam API does not have a field for author url, so put it in
+       # the content to be checked.
+       if (exists $params{url}) {
+               $params{content}.="\n".$params{url};
+       }
+
+       my $res = $client->send_request('testComment', {
+               ip => $ENV{REMOTE_ADDR},
+               comment => $params{content},
+               subject => defined $params{subject} ? $params{subject} : "",
+               name => defined $params{author} ? $params{author} : "",
+               options => join(",", @options),
+               site => $config{url},
+               version => "ikiwiki ".$IkiWiki::version,
+       });
+
+       if (! ref $res || ! defined $res->value) {
+               debug("failed to get response from blogspam server ($url)");
+               return undef;
+       }
+       elsif ($res->value =~ /^SPAM:(.*)/) {
+               return gettext("Sorry, but that looks like spam to <a href=\"http://blogspam.net/\">blogspam</a>: ").$1;
+       }
+       elsif ($res->value ne 'OK') {
+               debug(gettext("blogspam server failure: ").$res->value);
+               return undef;
+       }
+       else {
+               return undef;
+       }
+}
+
+1
index 1c4ab4895f0e16f90d33b4dbb347ecc7fbc9192c..833bedf25dcbad59a6f1dc3faed0f5c764b03db9 100644 (file)
@@ -343,8 +343,6 @@ sub sessioncgi ($$) {
                error(gettext("bad page name"));
        }
 
-       # FIXME: is this right? Or should we be using the candidate subpage
-       # (whatever that might mean) as the base URL?
        my $baseurl = urlto($page, undef, 1);
 
        $form->title(sprintf(gettext("commenting on %s"),
@@ -469,9 +467,21 @@ sub sessioncgi ($$) {
        }
 
        if ($form->submitted eq POST_COMMENT && $form->validate) {
-               my $file = "$location._comment";
-
                IkiWiki::checksessionexpiry($cgi, $session);
+               
+               $postcomment=1;
+               IkiWiki::check_content(content => $form->field('editcontent'),
+                       subject => $form->field('subject'),
+                       $config{comments_allowauthor} ? (
+                               author => $form->field('author'),
+                               url => $form->field('url'),
+                       ) : (),
+                       page => $location,
+                       cgi => $cgi, session => $session
+               );
+               $postcomment=0;
+               
+               my $file = "$location._comment";
 
                # FIXME: could probably do some sort of graceful retry
                # on error? Would require significant unwinding though
index ed994306f55251b561acf61459fcdc06fef8b994..bba52e4fd322e8bbad5f63a13198ff5f491cdef5 100644 (file)
@@ -78,7 +78,43 @@ sub check_canedit ($$$;$) {
                        }
                }
        });
-       return $canedit;
+       return defined $canedit ? $canedit : 1;
+}
+
+sub check_content (@) {
+       my %params=@_;
+       
+       return 1 if ! exists $hooks{checkcontent}; # optimisation
+
+       if (exists $pagesources{$params{page}}) {
+               my @diff;
+               my %old=map { $_ => 1 }
+                       split("\n", readfile(srcfile($pagesources{$params{page}})));
+               foreach my $line (split("\n", $params{content})) {
+                       push @diff, $line if ! exists $old{$_};
+               }
+               $params{content}=join("\n", @diff);
+       }
+
+       my $ok;
+       run_hooks(checkcontent => sub {
+               return if defined $ok;
+               my $ret=shift->(%params);
+               if (defined $ret) {
+                       if ($ret eq "") {
+                               $ok=1;
+                       }
+                       elsif (ref $ret eq 'CODE') {
+                               $ret->();
+                               $ok=0;
+                       }
+                       elsif (defined $ret) {
+                               error($ret);
+                       }
+               }
+
+       });
+       return defined $ok ? $ok : 1;
 }
 
 sub cgi_editpage ($$) {
@@ -368,8 +404,17 @@ sub cgi_editpage ($$) {
                        showform($form, \@buttons, $session, $q, forcebaseurl => $baseurl);
                        exit;
                }
+                       
+               my $message="";
+               if (defined $form->field('comments') &&
+                   length $form->field('comments')) {
+                       $message=$form->field('comments');
+               }
                
                my $content=$form->field('editcontent');
+               check_content(content => $content, page => $page,
+                       cgi => $q, session => $session,
+                       subject => $message);
                run_hooks(editcontent => sub {
                        $content=shift->(
                                content => $content,
@@ -403,12 +448,6 @@ sub cgi_editpage ($$) {
                
                my $conflict;
                if ($config{rcs}) {
-                       my $message="";
-                       if (defined $form->field('comments') &&
-                           length $form->field('comments')) {
-                               $message=$form->field('comments');
-                       }
-                       
                        if (! $exists) {
                                rcs_add($file);
                        }
index 4d7b4e7172d745312c7433daeb797f53b29dc05b..ea7d6e47f576b3cb04ffeb79d9471fdab1cfa9b1 100644 (file)
@@ -30,6 +30,7 @@ sub import {
        hook(type => "auth", id => "skeleton", call => \&auth);
        hook(type => "sessioncgi", id => "skeleton", call => \&sessioncgi);
        hook(type => "canedit", id => "skeleton", call => \&canedit);
+       hook(type => "checkcontent", id => "skeleton", call => \&checkcontent);
        hook(type => "editcontent", id => "skeleton", call => \&editcontent);
        hook(type => "formbuilder_setup", id => "skeleton", call => \&formbuilder_setup);
        hook(type => "formbuilder", id => "skeleton", call => \&formbuilder);
@@ -180,6 +181,12 @@ sub canedit ($$$) {
        debug("skeleton plugin running in canedit");
 }
 
+sub checkcontent (@) {
+       my %params=@_;
+
+       debug("skeleton plugin running in checkcontent");
+}
+
 sub editcontent ($$$) {
        my %params=@_;
 
index f35606148353cd863b65070e3487cca13033a6d5..cb362d6e24f58cb9df8a11f27b6225c23d0a46bd 100644 (file)
@@ -18,6 +18,10 @@ ikiwiki (3.02) UNRELEASED; urgency=low
     behave better.
   * Add auto-blog.setup, which will set up an ikiwiki instance tuned for use
     in blogging.
+  * checkcontent: New hook, can be used to implement arbitrary content
+    filters, including spam filters.
+  * blogspam: New plugin, adding spam filtering for page editing / comment
+    posting using the BlogSpam.net API.
 
  -- Joey Hess <joeyh@debian.org>  Tue, 06 Jan 2009 15:02:52 -0500
 
diff --git a/doc/plugins/blogspam.mdwn b/doc/plugins/blogspam.mdwn
new file mode 100644 (file)
index 0000000..307d464
--- /dev/null
@@ -0,0 +1,23 @@
+[[!template id=plugin name=blogspam author="[[Joey]]"]]
+[[!tag type/auth]]
+
+This plugin adds antispam support to ikiwiki, using the
+[blogspam.net](http://blogspam.net/) API. Both page edits and
+[[comment|comments]] postings can be checked for spam. Currently,
+detected spam is not saved for human review, it is just rejected.
+
+You can control how content is tested via the `blogspam_options`
+setting. By default, the options are configured in a way that is
+appropriate for wiki content. This includes turning off some of the
+more problimatic tests.
+
+The `blogspam_pagespec` setting is a [[ikiwiki/PageSpec]] that can be
+used to configure which pages are checked for spam. The default is to check
+all edits. If you only want to check [[comments]] (not wiki page edits),
+set it to "postcomment(*)".
+
+By default, the blogspam.net server is used to do the spam checking. To
+change this, the `blogspam_server` option can be set to the url for a
+different server implementing the same API. Note that content is sent
+unencrypted over the internet to the server, and the server sees
+the full text of the content.
index 405876d58a9a7200e01d2d77ed91cb9892523ed4..99eea3d1696b3240647b439edae4d8768ddf1033 100644 (file)
@@ -303,7 +303,7 @@ can check if the session object has a "name" parameter set.
 
 ### canedit
 
-       hook(type => "canedit", id => "foo", call => \&pagelocked);
+       hook(type => "canedit", id => "foo", call => \&canedit);
 
 This hook can be used to implement arbitrary access methods to control when
 a page can be edited using the web interface (commits from revision control
@@ -321,6 +321,26 @@ This hook should avoid directly redirecting the user to a signin page,
 since it's sometimes used to test to see which pages in a set of pages a
 user can edit.
 
+### checkcontent
+       
+       hook(type => "checkcontent", id => "foo", call => \&checkcontent);
+
+This hook is called to check the content a user has entered on a page,
+before it is saved, and decide if it should be allowed.
+
+It is passed named parameters: `content`, `page`, `cgi`, and `session`. If
+the content the user has entered is a comment, it may also be passed some
+additional parameters: `author`, `url`, and `subject`. The `subject`
+parameter may also be filled with the user's comment about the change.
+
+Note: When the user edits an existing wiki page, the passed `content` will
+include only the lines that they added to the page, or modified.
+
+The hook should return `undef` on success. If the content is disallowed, it
+should return a message stating what the problem is, or a function
+that can be run to perform whatever action is necessary to allow the user
+to post the content.
+
 ### editcontent
 
        hook(type => "editcontent", id => "foo", call => \&editcontent);
index cb45faee5d4b245712c10a9aeb9914af8a77eb80..b0524be5fcabfba5b66ef671ddebab6eb0645eb4 100644 (file)
@@ -17,3 +17,14 @@ Cheers,
 You might look at the Wikipedia page on "Spam\_in\_blogs" for more ideas.  In particular, would it be possible to force a subset of the pages (by regex, but you'd choose the regex to match those pages which are publicly writable) to use rel="nofollow" in all links.
 
 > I just wanted to leave a link here to the [[todo/require_CAPTCHA_to_edit]] plugin patch.  Unfortunately that plugin currently interacts badly with the openid plugin. -- [[Will]]
+
+
+---
+
+Ikiwiki now has a checkcontent hook that plugins can use to see content
+that is being entered and check it for spam/whatever.
+
+There is a blogspam plugin that uses the blogspam.org service
+to check for common spam signatures. --[[Joey]] 
+
+[[done]]
index d09d17f7f932deecb9c6f16e6911367ced9d4d35..9d5cbc3739d168b9362cbfb91620a9f624e3a918 100755 (executable)
@@ -5,8 +5,8 @@ use Test::More;
 
 my @progs="ikiwiki.in";
 my @libs="IkiWiki.pm";
-# monotone, external, amazon_s3 skipped since they need perl modules
-push @libs, map { chomp; $_ } `find IkiWiki -type f -name \\*.pm | grep -v monotone.pm | grep -v external.pm | grep -v amazon_s3.pm`;
+# monotone, external, blogspam, amazon_s3 skipped since they need perl modules
+push @libs, map { chomp; $_ } `find IkiWiki -type f -name \\*.pm | grep -v monotone.pm | grep -v external.pm | grep -v blogspam.pm | grep -v amazon_s3.pm`;
 push @libs, 'IkiWiki/Plugin/skeleton.pm.example';
 
 plan(tests => (@progs + @libs));