Changeset 47
- Timestamp:
- 02/22/05 13:15:24
- Files:
-
- trunk/plugins/spamlookup/mt-spamlookup.cgi (modified) (7 diffs)
- trunk/plugins/spamlookup/spamlookup.pl (modified) (20 diffs)
- trunk/plugins/spamlookup/tmpl/spamlookup.tmpl (modified) (10 diffs)
- trunk/plugins/spamlookup/tmpl/view_cache.tmpl (modified) (1 diff)
- trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/plugins/spamlookup/mt-spamlookup.cgi
r46 r47 34 34 35 35 use MT::App; 36 use MT::Util qw(format_ts offset_time mark_odd_rows); 37 use MT::Author qw(APPROVED BLOCKED); 36 38 use vars qw(@ISA); 37 39 @ISA = qw(MT::App); … … 48 50 unban_ip => \&unban_ip, 49 51 reset_cache => \&reset_cache, 50 save => \&save_form 52 save => \&save_form, 53 despam => \&despam, 54 view => \&view, 51 55 ); 52 56 $app->{default_mode} = 'default'; … … 58 62 $app->{cfg}->set('AltTemplatePath', ''); 59 63 $app->{requires_login} = 1; 60 $app->{breadcrumbs} = [ { bc_name => 'SpamLookup Configuration',64 $app->{breadcrumbs} = [ { bc_name => 'SpamLookup', 61 65 bc_uri => '?__mode=show' } ]; 66 $app->{mode} = $mode; 62 67 $app; 68 } 69 70 sub view { 71 my $app = shift; 72 my (%param) = @_; 73 74 my $query = $app->{query}; 75 my $type = $query->param('_type'); 76 my $Type = $type eq 'comments' ? 'Comments' : 'TrackBack Pings'; 77 78 $app->add_breadcrumb("View $Type" => '?__mode=view&_type='.$type); 79 80 my $tmpl; 81 if ($type eq 'comments') { 82 $tmpl = 'comments.tmpl'; 83 } else { 84 $tmpl = 'pings.tmpl'; 85 } 86 87 $app->select_objects(\%param); 88 89 $app->build_page($tmpl, \%param); 90 } 91 92 sub despam { 93 my $app = shift; 94 my (%param) = @_; 95 96 my $query = $app->{query}; 97 my $type = $query->param('_type') || 'comments'; 98 my $Type = $type eq 'comments' ? 'Comments' : 'TrackBack Pings'; 99 100 $app->add_breadcrumb("Despam $Type" => '?__mode=despam&_type='.$type); 101 102 $param{'type'} = $type; 103 $param{'type_'.$type} = 1; 104 105 my %to_rebuild; 106 my @ids = $query->param('is_spam'); 107 my $action = $query->param('action'); 108 if (@ids && ($action =~ m/Delete/)) { 109 if ($type eq 'comments') { 110 foreach (@ids) { 111 my $obj = MT::Comment->load($_); 112 next unless $obj; 113 $to_rebuild{$obj->entry_id} = 1; 114 $obj->remove; 115 } 116 } else { 117 require MT::TBPing; 118 foreach (@ids) { 119 my $obj = MT::TBPing->load($_); 120 next unless $obj; 121 $to_rebuild{$obj->tb_id} = 1; 122 $obj->remove; 123 } 124 } 125 } 126 127 $app->select_objects(\%param); 128 129 my $loop = $param{object_loop}; 130 131 if (!@$loop) { 132 if ($param{redirect}) { 133 $param{message} = qq{Didn't find any spam in the last }.$param{count}.qq{ records scanned in the date range } . format_ts("%Y-%m-%d %H:%M:%S", $param{start_date}) . " through " . format_ts("%Y-%m-%d %H:%M:%S", $param{end_date}) . ". Continuing search..."; ## next obj id is }.$obj->id; 134 } 135 } else { 136 $param{message} = "The following items were identified as spam, falling in the date range: " . format_ts("%Y-%m-%d %H:%M:%S", $param{start_date}) . " through " . format_ts("%Y-%m-%d %H:%M:%S", $param{end_date}); 137 } 138 139 if (%to_rebuild) { 140 require MT::Blog; 141 require MT::Entry; 142 MT::Util::start_background_task(sub { 143 my %blog_rebuild; 144 145 foreach my $id (keys %to_rebuild) { 146 if ($type eq 'comments') { 147 my $entry = MT::Entry->load($id); 148 next unless $entry; 149 $app->rebuild_entry(Entry => $id, BuildDependencies => 1); 150 $blog_rebuild{$entry->blog_id} = 1; 151 } else { 152 my $tb = MT::Trackback->load($id); 153 next unless $tb; 154 my $blog = MT::Blog->load($tb->blog_id); 155 $blog_rebuild{$tb->blog_id} = 1; 156 if ($tb->entry_id) { 157 $app->rebuild_entry(Entry => $tb->entry_id); 158 } else { 159 $app->_rebuild_entry_archive_type( 160 Entry => undef, Blog => $blog, 161 Category => MT::Category->load($tb->category_id), 162 ArchiveType => 'Category' 163 ); 164 } 165 if ($app->{cfg}->GenerateTrackBackRSS) { 166 ## Now generate RSS feed for this trackback item. 167 my $rss = _generate_rss($tb, 10); 168 my $base = $blog->archive_path; 169 my $feed = File::Spec->catfile($base, $tb->rss_file || $tb->id . '.xml'); 170 my $fmgr = $blog->file_mgr; 171 $fmgr->put_data($rss, $feed); 172 } 173 } 174 } 175 176 $app->rebuild_indexes( BlogID => $_ ) foreach keys %blog_rebuild; 177 }); 178 } 179 180 $app->build_page('despam.tmpl', \%param); 181 } 182 183 sub select_objects { 184 my $app = shift; 185 my ($param) = @_; 186 187 my $query = $app->{query}; 188 my $type = $query->param('_type') || 'comments'; 189 190 my $offset = $query->param('offset') || 0; 191 my $limit = $query->param('limit') || 25; 192 my $filter = $app->{mode} eq 'despam'; 193 194 my $iter; 195 if ($type eq 'comments') { 196 require MT::Comment; 197 my $terms = {}; 198 $terms->{visible} = 1 if $filter; 199 my $args = { offset => $offset, limit => ($limit*10)+1, 'sort' => 'created_on', direction => 'descend' }; 200 $iter = MT::Comment->load_iter($terms, $args); 201 } else { 202 require MT::TBPing; 203 require MT::Trackback; 204 my $terms = {}; 205 my $args = { offset => $offset, limit => ($limit * 10)+1, 'sort' => 'created_on', direction => 'descend' }; 206 $iter = MT::TBPing->load_iter($terms, $args); 207 } 208 209 my @loop; 210 require MT::Callback; 211 my $cb = new MT::Callback(method => 'Despam'); 212 my (%blogs, $obj, $start_date, $end_date); 213 my $count = 0; 214 while ($obj = $iter->()) { 215 $start_date = $obj->created_on unless defined $start_date; 216 $end_date = $obj->created_on; 217 $offset++; $count++; 218 last if $count == ($limit * 10); 219 next if ($type ne 'comments') && (($obj->created_by || 0) == 1) && $filter; 220 next unless $obj->ip || $filter; # objects without an ip address aren't very despammable 221 my $data = $obj->column_values; 222 my $blog_id = $obj->blog_id; 223 my $blog; 224 if ($blogs{$blog_id}) { 225 $blog = $blogs{$blog_id}; 226 } else { 227 $blog = MT::Blog->load($blog_id); 228 $blogs{$blog_id} = $blog; 229 } 230 if ($filter) { 231 $cb->error(''); 232 my $result = MT::Plugin::SpamLookup::filter($cb, $app, $obj); 233 my $action; 234 235 if ($type eq 'comments') { 236 if ($result) { 237 next if $obj->visible; 238 } 239 } else { 240 if ($result) { 241 next unless $obj->created_by; 242 } 243 $result = 1 if $obj->created_by; 244 } 245 $action = $result ? 'Moderate' : 'Block'; 246 $data->{result} = $action; 247 $data->{"action_$action"} = 1; 248 $data->{reason} = $cb->errstr; 249 } 250 251 my $text; 252 if ($type eq 'comments') { 253 $data->{excerpt} = (substr($obj->text(), 0, 125) 254 . (length($obj->text) > 125 ? "..." : "")); 255 $data->{author_display} = $data->{author}; 256 $data->{author_display} = substr($data->{author_display}, 0, 25) . '...' 257 if $data->{author_display} && $data->{author_display} =~ m(\S{25,}); 258 my $entry = MT::Entry->load($obj->entry_id()); 259 $data->{entry_title} = $entry->title() || $entry->text || ""; 260 $data->{entry_title} = substr($data->{entry_title}, 0, 25) . '...' 261 if $data->{entry_title} && $data->{entry_title} =~ m(\S{25,}); 262 $data->{commenter_id} = $obj->commenter_id() if $obj->commenter_id(); 263 my $cmntr = MT::Author->load({ id => $obj->commenter_id(), 264 type => MT::Author::COMMENTER }); 265 if ($cmntr) { 266 $data->{email_hidden} = $cmntr && $cmntr->is_email_hidden(); 267 require MT::Permission; 268 269 my $status = $cmntr->status($blog_id); 270 $data->{commenter_approved} = ($cmntr->status($blog_id) == APPROVED); 271 $data->{commenter_blocked} = ($cmntr->status($blog_id) == BLOCKED); 272 } 273 $text = $obj->url . ' ' . $obj->text; 274 } else { 275 my $tb = MT::Trackback->load($obj->tb_id); 276 my ($entry, $cat); 277 if ($tb->entry_id) { 278 $entry = MT::Entry->load($tb->entry_id); 279 $data->{entry_id} = $entry->id; 280 } elsif ($tb->category_id) { 281 $cat = MT::Category->load($tb->category_id); 282 $data->{category_id} = $cat->id; 283 } 284 $data->{tb_blog_name} = $obj->blog_name; 285 286 $text = $obj->source_url . ' ' . $obj->excerpt; 287 $data->{excerpt} = (substr($obj->excerpt(), 0, 125) 288 . (length($obj->excerpt()) > 125 ? "..." : "")); 289 if ($entry) { 290 $data->{entry_title} = $entry->title() || $entry->text || ""; 291 $data->{entry_title} = substr($data->{entry_title}, 0, 25) . '...' 292 if $data->{entry_title} && $data->{entry_title} =~ m(\S{25,}); 293 } 294 $data->{category_title} = $cat->label if $cat; 295 } 296 $data->{blog_name} = $blog->name; 297 $data->{created_on_relative_date} = relative_date(ts2epoch($blog->id, $obj->created_on()), offset_time(time, $blog), $obj->created_on, "%Y-%m-%d"); 298 if ($filter) { 299 my @urls = MT::Plugin::SpamLookup::extract_urls($text, 2); 300 my @domain_loop; 301 push @domain_loop, { domain => $_ } foreach @urls; 302 $data->{domain_loop} = \@domain_loop; 303 } 304 push @loop, $data; 305 last if scalar(@loop) == $limit; 306 } 307 $param->{count} = $count; 308 $param->{start_date} = $start_date; 309 $param->{end_date} = $end_date; 310 mark_odd_rows(\@loop); 311 if (!@loop) { 312 if ($obj) { 313 $param->{redirect} = 1; 314 } 315 } else { 316 $param->{more} = $obj ? 1 : 0; 317 } 318 $param->{object_loop} = \@loop; 319 $param->{offset} = $offset; 320 $param->{limit} = $limit; 321 } 322 323 use Time::Local qw( timegm ); 324 sub ts2epoch { 325 my ($blog, $ts) = @_; 326 my ($yr, $mo, $dy, $hr, $mn, $sc) = unpack('A4A2A2A2A2A2A2', $ts); 327 $ts = timegm($sc, $mn, $hr, $dy, $mo-1, $yr-1900); 328 } 329 330 sub relative_date { 331 my ($ts1, $ts2, $ts, $fmt) = @_; 332 # make sure ts2 is always > ts1 333 ($ts2, $ts1) = ($ts1, $ts2) if $ts2 < $ts1; 334 my $delta = $ts2 - $ts1; 335 if ($delta <= 60) { 336 "Less than 1 minute"; 337 } elsif ($delta <= 86400) { 338 # less than 1 day 339 my $hours = int($delta / 3600); 340 my $min = int(($delta % 3600) / 60); 341 my $result; 342 if ($hours && $min) { 343 $result = sprintf('%d hour' . ($hours > 1 ? 's' : '') . ', %d minute' . ($min > 1 ? 's' : '') . ' ago', $hours, $min); 344 } elsif ($hours) { 345 $result = sprintf('%d hour' . ($hours > 1 ? 's' : '') . ' ago', $hours); 346 } elsif ($min) { 347 $result = sprintf('%d minute' . ($min > 1 ? 's' : '') . ' ago', $min); 348 } 349 $result; 350 } elsif ($delta <= 604800) { 351 # less than 1 week 352 my $days = int($delta / 86400); 353 my $hours = int(($delta % 86400) / 3600); 354 my $result; 355 if ($days && $hours) { 356 $result = sprintf('%d day' . ($days > 1 ? 's' : '') . ', %d hour' . ($hours > 1 ? 's' : '') . ' ago', $days, $hours); 357 } elsif ($days) { 358 $result = sprintf('%d day' . ($days > 1 ? 's' : '') . ' ago', $days); 359 } elsif ($hours) { 360 $result = sprintf('%d hour' . ($hours > 1 ? 's' : '') . ' ago', $hours); 361 } 362 $result; 363 } else { 364 format_ts($fmt, $ts); 365 } 366 } 367 368 sub _generate_rss { 369 my($tb, $lastn) = @_; 370 my $rss = <<RSS; 371 <rss version="0.91"><channel> 372 <title>@{[ $tb->title ]}</title> 373 <link>@{[ $tb->url || '' ]}</link> 374 <description>@{[ $tb->description || '' ]}</description> 375 <language>en-us</language> 376 RSS 377 my %arg; 378 if ($lastn) { 379 %arg = ('sort' => 'created_on', direction => 'descend', 380 limit => $lastn); 381 } 382 my $iter = MT::TBPing->load_iter({ tb_id => $tb->id }, \%arg); 383 while (my $ping = $iter->()) { 384 $rss .= sprintf qq(<item>\n<title>%s</title>\n<link>%s</link>\n), 385 encode_xml($ping->title), encode_xml($ping->source_url); 386 if ($ping->excerpt) { 387 $rss .= sprintf qq(<description>%s</description>\n), 388 encode_xml($ping->excerpt); 389 } 390 $rss .= qq(</item>\n); 391 } 392 $rss .= qq(</channel>\n</rss>); 393 $rss; 63 394 } 64 395 … … 122 453 my $service=$data->{cache_ip_infoservice}; 123 454 if($service =~ /%ip%/) { 124 foreach (@data) {125 my $dest=$service;126 $dest=~s/%ip%/$_->{ipaddr}/eg;127 $_->{iptext}=qq{<a href="$dest">$_->{ipaddr}</a>};128 }455 foreach (@data) { 456 my $dest = $service; 457 $dest =~ s/%ip%/$_->{ipaddr}/eg; 458 $_->{iptext} = qq{<a href="$dest">$_->{ipaddr}</a>}; 459 } 129 460 } 130 461 $param{cache_loop} = \@data; … … 218 549 @domains = MT::Plugin::SpamLookup::extract_urls('http://' . $domain) 219 550 if !@domains; 220 foreach my$domain (@domains) {551 foreach $domain (@domains) { 221 552 if ($domain =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) { 222 553 $domain = "$4.$3.$2.$1"; … … 238 569 my $app = shift; 239 570 my (%param) = @_; 571 572 $app->add_breadcrumb("Configuration" => '?__mode=show'); 573 240 574 my @config = MT::Plugin::SpamLookup->instance->config_params; 241 575 my $data = MT::Plugin::SpamLookup->instance->get_config_hash(); 242 576 $param{$_} = $data->{$_} foreach @config; 577 if (defined $param{"pingip_moderate"}) { 578 $param{"pingip_moderate_".$param{"pingip_moderate"}} = 1; 579 } 580 if (defined $param{"pingip_block"}) { 581 $param{"pingip_block_".$param{"pingip_block"}} = 1; 582 } 583 if (defined $param{"tbpass_entryfield"}) { 584 $param{"tbpass_entryfield_".$param{"tbpass_entryfield"}} = 1; 585 } 586 if (defined $param{"commentpass_entryfield"}) { 587 $param{"commentpass_entryfield_".$param{"commentpass_entryfield"}} = 1; 588 } 243 589 if (defined $MT::Plugin::Moderate::VERSION) { 244 590 $param{can_moderate_pings} = 1; 245 591 } 592 246 593 $app->build_page('spamlookup.tmpl', \%param); 247 594 } … … 250 597 my $app = shift; 251 598 my $auth = $app->{author}; 252 my $q = $app->{query};599 my $query = $app->{query}; 253 600 my $pd = MT::Plugin::SpamLookup->instance->get_config_obj; 254 601 my $config = $pd->data() || {}; 255 602 my @config = MT::Plugin::SpamLookup->instance->config_params; 256 603 foreach (@config) { 257 $config->{$_} = $q ->param($_) || undef;604 $config->{$_} = $query->param($_) || undef; 258 605 } 259 606 $pd->data($config); trunk/plugins/spamlookup/spamlookup.pl
r45 r47 11 11 name => 'SpamLookup', 12 12 config_link => 'mt-spamlookup.cgi', 13 description => 'Controls feedback from open proxies and spammy URLs ',13 description => 'Controls feedback from open proxies and spammy URLs.', 14 14 doc_link => undef 15 15 }); … … 17 17 MT->add_callback('CommentFilter', 6, $plugin, \&filter); 18 18 MT->add_callback('TBPingFilter', 6, $plugin, \&filter); 19 MT->add_callback('TBPingThrottleFilter', 6, $plugin, \&tbfilter); 19 20 20 21 use vars qw($DEBUG); … … 33 34 qw( comment_filter ping_filter 34 35 allow_auth allow_urlless allow_priorurls 35 pingip_ moderate pingip_check36 pingip_enabled pingip_moderate pingip_block 36 37 ipbl_enabled ipbl_moderate ipbl_service 37 38 domainbl_enabled domainbl_moderate domainbl_service … … 40 41 headers_enabled headers_moderate headers_ignore 41 42 urlcount_enabled urlcount_moderate_limit urlcount_block_limit 43 commentpass_enabled commentpass_formfield commentpass_entryfield commentpass_text commentpass_moderate 44 tbpass_enabled tbpass_formfield tbpass_entryfield tbpass_text tbpass_moderate 45 wordlist_moderate wordlist_block 42 46 ); 43 47 } … … 79 83 $data->{allow_urlless} = 0 unless exists $data->{allow_urlless}; 80 84 $data->{allow_priorurls} = 0 unless exists $data->{allow_priorurls}; 81 $data->{pingip_check} = 1 unless exists $data->{pingip_check}; 82 $data->{pingip_moderate} = 1 unless exists $data->{pingip_moderate}; 85 $data->{pingip_enabled} = 1 unless exists $data->{pingip_enabled}; 86 $data->{pingip_moderate} = 2 unless exists $data->{pingip_moderate}; 87 $data->{pingip_block} = 3 unless exists $data->{pingip_block}; 88 89 $data->{tbpass_enabled} = 0 unless exists $data->{tbpass_enabled}; 90 if (!($data->{tbpass_formfield} && ($data->{tbpass_entryfield} || $data->{tbpass_text}))) { 91 $data->{tbpass_enabled} = 0; 92 } 93 $data->{commentpass_enabled} = 0 unless exists $data->{commentpass_enabled}; 94 if (!($data->{commentpass_entryfield} || $data->{commentpass_text})) { 95 $data->{commentpass_enabled} = 0; 96 } 83 97 84 98 $data->{ipbl_enabled} = 1 unless exists $data->{ipbl_enabled}; … … 113 127 $data->{proxycheck_autoban} = 0 unless exists $data->{proxycheck_autoban}; 114 128 129 $data->{wordlist_moderate} ||= ''; 130 $data->{wordlist_block} ||= ''; 131 115 132 $data; 133 } 134 135 sub tbfilter { 136 my ($eh, $app, $obj) = @_; 137 138 my $plugin = $eh->{plugin}; 139 my $data = $plugin->get_config_hash(); 140 if ($data->{tbpass_enabled} && $obj->entry_id) { 141 my $text; 142 if (my $field = $data->{tbpass_entryfield}) { 143 require MT::Entry; 144 my $entry = MT::Entry->load($obj->entry_id); 145 if ($field eq 'title') { 146 $text = $entry->title; 147 } elsif ($field eq 'excerpt') { 148 $text = $entry->excerpt; 149 } elsif ($field eq 'keywords') { 150 $text = $entry->keywords; 151 } elsif ($field eq 'extended') { 152 $text = $entry->text_more; 153 } 154 } else { 155 $text = $data->{tbpass_text}; 156 } 157 $text =~ s/^\s*//s; 158 $text =~ s/\s*$//s; 159 $obj->passphrase($text); 160 } 161 162 1; 116 163 } 117 164 … … 120 167 121 168 # pull these from user configuration settings... 169 my $plugin = $eh->{plugin}; 122 170 my $data = $plugin->get_config_hash(); 171 172 my $method = $eh->{method}; 123 173 124 174 my (@urls, $blog, $nurls); … … 129 179 130 180 # comment 131 @urls = extract_urls($obj->text, 0, \$nurls);132 push @urls, extract_urls($obj->url) if $obj->url;181 my $text = ($obj->text || '') . ' ' . ($obj->url || ''); 182 @urls = extract_urls($text, 0, \$nurls); 133 183 $data->{source_url} = $obj->url; 134 184 … … 149 199 return 1 unless $data->{ping_filter}; 150 200 151 $data->{__type} = 'Track back ping';201 $data->{__type} = 'TrackBack ping'; 152 202 153 203 if ($data->{domainbl_enabled} || $data->{urlcount_enabled}) { 154 @urls = extract_urls($obj->excerpt, 0, \$nurls);155 push @urls, extract_urls($obj->source_url) if $obj->source_url;204 my $text = ($obj->excerpt || '') . ' ' . ($obj->source_url || ''); 205 @urls = extract_urls($text, 0, \$nurls); 156 206 } 157 207 $data->{source_url} = $obj->source_url; 158 } 208 } else { 209 return 1; # not sure what we got, but we don't know what to do with it 210 } 211 159 212 $data->{__urls} = \@urls; 160 213 $data->{__nurls} = $nurls; … … 176 229 177 230 my @tests; 178 push @tests, \&priorurl_check if $data->{allow_priorurls} && $data->{source_url}; 179 push @tests, \&pingip_check if $data->{pingip_check} && $data->{pingip_check} && $data->{__type} ne 'comment'; 180 push @tests, \&cache_check if $data->{caching_enabled} && $data->{cache_limit}; 181 push @tests, \&headers_check if $data->{headers_enabled}; 182 push @tests, \&ipbl_check if $data->{ipbl_enabled}; 183 push @tests, \&domainbl_check if $data->{domainbl_enabled}; 184 push @tests, \&urlcount_check if $data->{urlcount_enabled}; 185 push @tests, \&proxy_check if $data->{proxycheck_enabled}; 231 push @tests, [\&commentpass_check, 'commentpass_check'] 232 if $data->{commentpass_enabled} && ($data->{__type} eq 'comment') 233 && ($method ne 'Despam'); 234 push @tests, [\&tbpass_check, 'tbpass_check'] 235 if $data->{tbpass_enabled} && ($data->{__type} ne 'comment') 236 && ($method ne 'Despam'); 237 push @tests, [\&wordlist_check, 'wordlist_check'] 238 if ($data->{wordlist_moderate} || $data->{wordlist_block}); 239 push @tests, [\&priorurl_check, 'priorurl_check'] 240 if $data->{allow_priorurls} && $data->{source_url} 241 && ($method ne 'Despam'); 242 push @tests, [\&pingip_check, 'pingip_check'] 243 if $data->{pingip_enabled} && ($data->{__type} ne 'comment'); 244 push @tests, [\&cache_check, 'cache_check'] 245 if $data->{caching_enabled} && $data->{cache_limit}; 246 push @tests, [\&headers_check, 'headers_check'] 247 if $data->{headers_enabled} 248 && ($method ne 'Despam'); 249 push @tests, [\&ipbl_check, 'ipbl_check'] 250 if $data->{ipbl_enabled}; 251 push @tests, [\&domainbl_check, 'domainbl_check'] 252 if $data->{domainbl_enabled}; 253 push @tests, [\&urlcount_check, 'urlcount_check'] 254 if $data->{urlcount_enabled}; 255 push @tests, [\&proxy_check, 'proxy_check'] 256 if $data->{proxycheck_enabled} 257 && ($method ne 'Despam'); # this test should not be done during despamming, should it? 186 258 187 259 my ($result, @errors); 188 260 foreach (@tests) { 189 $result = $_-> ($eh, $app, $obj, $data);190 if ( $eh->errstr =~ m/\w/) {261 $result = $_->[0]->($eh, $app, $obj, $data); 262 if ((defined $eh->errstr) && ($eh->errstr =~ m/\w/)) { 191 263 push @errors, $eh->errstr; 192 264 $eh->error(undef); … … 208 280 # MT-Moderate: 209 281 $obj->created_by(1); 210 $obj->save;211 282 } else { 212 283 # trackbacks can't be moderated right now... … … 219 290 } 220 291 221 if ($data->{is_proxy} && $data->{proxycheck_autoban}) { 222 require MT::IPBanList; 223 my $ban = new MT::IPBanList; 224 $ban->ip($obj->ip); 225 $ban->blog_id($obj->blog_id); 226 $ban->save; 227 $app->log("SpamLookup: permanently banning IP ".$obj->ip." due to open proxy testing"); 228 } 229 230 if (($data->{caching_enabled} && $data->{cache_limit}) && 231 ($result != ACCEPT)) { 232 $cache->{$obj->ip}->{lastseen} = time; 233 $cache->{$obj->ip}->{hits}++; 234 $cache->{$obj->ip}->{blog_id} = $obj->blog_id; 235 $cache->{$obj->ip}->{result} = $result; 236 $cache->{$obj->ip}->{reason} = (join "\n", @errors) || ''; 237 my @keys = keys %$cache; 238 my $todelete = scalar(@keys) - $data->{cache_limit}; 239 if ($todelete > 0) { 240 foreach my $key (sort { $cache->{$a}->{lastseen} 241 <=> 242 $cache->{$b}->{lastseen} } @keys) { 243 delete $cache->{$key}; 244 last unless(--$todelete); 292 if ($method ne 'Despam') { 293 if ($data->{is_proxy} && $data->{proxycheck_autoban}) { 294 require MT::IPBanList; 295 my $ban = new MT::IPBanList; 296 $ban->ip($obj->ip); 297 $ban->blog_id($obj->blog_id); 298 $ban->save; 299 $app->log("SpamLookup: permanently banning IP ".$obj->ip." due to open proxy testing"); 300 } 301 302 if (($data->{caching_enabled} && $data->{cache_limit}) && 303 ($result != ACCEPT)) { 304 $cache->{$obj->ip}->{lastseen} = time; 305 $cache->{$obj->ip}->{hits}++; 306 $cache->{$obj->ip}->{blog_id} = $obj->blog_id; 307 $cache->{$obj->ip}->{result} = $result; 308 $cache->{$obj->ip}->{reason} = (join "\n", @errors) || ''; 309 my @keys = keys %$cache; 310 my $todelete = scalar(@keys) - $data->{cache_limit}; 311 if ($todelete > 0) { 312 foreach my $key (sort { $cache->{$a}->{lastseen} 313 <=> 314 $cache->{$b}->{lastseen} } @keys) { 315 delete $cache->{$key}; 316 last unless(--$todelete); 317 } 245 318 } 246 }247 unless ($pdata) {248 $pdata = new MT::PluginData;249 $pdata->plugin($plugin->name);250 $pdata->key('cache');251 }252 $pdata->data($cache);253 $pdata->save;319 unless ($pdata) { 320 $pdata = new MT::PluginData; 321 $pdata->plugin($plugin->name); 322 $pdata->key('cache'); 323 } 324 $pdata->data($cache); 325 $pdata->save; 326 } 254 327 } 255 328 … … 260 333 ## Each returns one of three states: 0 to reject, 1 to accept, undef 261 334 ## to ignore, allowing the next test to run. 335 336 sub wordlist_check { 337 my ($eh, $app, $obj, $data) = @_; 338 339 my $text = ''; 340 if ($data->{__type} eq 'comment') { 341 $text = join "\n", 342 "name:". ($obj->author || ''), 343 "email:" . ($obj->email || ''), 344 "url:" . ($obj->url || ''), 345 "text:" . ($obj->text || ''); 346 } else { 347 $text = join "\n", 348 "blog:". ($obj->blog_name || ''), 349 "title:" . ($obj->title || ''), 350 "url:" . ($obj->source_url || ''), 351 "text:" . ($obj->excerpt || ''); 352 } 353 354 my $decodedtext = decode_entities($text); 355 356 my $match; 357 if ($data->{wordlist_block}) { 358 $match = wordlist_match($text, $data->{wordlist_block}); 359 $match ||= wordlist_match($decodedtext, $data->{wordlist_block}); 360 if ($match) { 361 $eh->error("Blocking ".$data->{__type}." on blog id ".$obj->blog_id." based on wordlist match: ".$match); 362 return BLOCK; 363 } 364 } 365 366 if ($data->{wordlist_moderate}) { 367 $match = wordlist_match($text, $data->{wordlist_moderate}); 368 $match ||= wordlist_match($decodedtext, $data->{wordlist_moderate}); 369 if ($match) { 370 $eh->error("Moderating ".$data->{__type}." on blog id ".$obj->blog_id." based on wordlist match: ".$match); 371 return MODERATE; 372 } 373 } 374 375 return IGNORE; 376 } 377 378 sub wordlist_match { 379 my ($text, $patterns) = @_; 380 381 $text ||= ''; 382 my @patt = split /[\r\n]+/, $patterns; 383 foreach my $patt (@patt) { 384 $patt =~ s/^\s+//s; 385 $patt =~ s/\s+$//s; 386 next if $patt eq ''; 387 if ($patt =~ m!^/!) { 388 my $re = $patt; 389 my ($opt) = $re =~ m!/([^/]*)$!; 390 $re =~ s!^/!!; 391 $re =~ s!/[^/]*$!!; 392 $re = '(?'.$opt.':'.$re.')' if $opt; 393 $re = eval { qr/$re/ }; 394 $re = '\b' . quotemeta($patt) . '\b' if $@; 395 return 'Match on pattern: '.$patt if $text =~ m/$re/; 396 } else { 397 my $re = '\b' . quotemeta($patt) . '\b'; 398 return 'Match on phrase: '.$patt if $text =~ m/$re/i; 399 } 400 } 401 0; 402 } 403 404 sub tbpass_check { 405 my ($eh, $app, $obj, $data) = @_; 406 407 408 return IGNORE; 409 } 410 411 sub commentpass_check { 412 my ($eh, $app, $obj, $data) = @_; 413 414 my $formfield = $data->{commentpass_formfield}; 415 my $entryfield = $data->{commentpass_entryfield}; 416 my $text = $data->{commentpass_text}; 417 418 return IGNORE unless $formfield; 419 420 my $inpass = $app->{query}->param($formfield); 421 return BLOCK unless defined $inpass; 422 423 if ($entryfield) { 424 my $entry = MT::Entry->load($obj->entry_id); 425 if ($entryfield eq 'title') { 426 $text = $entry->title; 427 } elsif ($entryfield eq 'excerpt') { 428 $text = $entry->excerpt; 429 } elsif ($entryfield eq 'keywords') { 430 $text = $entry->keywords; 431 } elsif ($entryfield eq 'extended') { 432 $text = $entry->text_more; 433 } 434 } 435 436 foreach ($text, $inpass) { 437 s/^\s*//s; 438 s/\s*$//s; 439 } 440 441 return ACCEPT if lc($text) eq lc($inpass); 442 443 return $data->{commentpass_moderate} ? MODERATE : BLOCK; 444 } 262 445 263 446 sub pingip_check { … … 274 457 } 275 458 276 return IGNORE if $pingip eq $domainip; 277 278 # test for class C ip match 279 my $domainipc = $domainip; 280 $domainipc =~ s/\.\d+$//; 281 my $pingipc = $pingip; 282 $pingipc =~ s/\.\d+$//; 283 return IGNORE if $pingipc eq $domainipc; 284 285 my $action = ($data->{pingip_moderate} ? "Moderating" : "Blocking"); 459 my @domainip = split /\./, $domainip; 460 my @pingip = split /\./, $pingip; 461 462 my $distance = 4; 463 foreach (0..3) { 464 if ($domainip[$_] == $pingip[$_]) { 465 $distance--; 466 } else { 467 last; 468 } 469 } 470 471 return IGNORE if $distance == 0; 472 473 my $result = IGNORE; 474 $result = BLOCK if $distance >= $data->{pingip_block}; 475 $result ||= MODERATE if $distance >= $data->{pingip_moderate}; 476 477 my $action = ($result == MODERATE ? "Moderating" : "Blocking"); 286 478 $eh->error("$action TrackBack ping for blog " . $obj->blog_id . 287 479 " since domain IP does not match ping IP for source URL " . 288 480 $obj->source_url . "; domain IP: $domainip; ping IP: $pingip"); 289 return $data->{pingip_moderate} ? MODERATE : BLOCK; 481 482 return $result; 290 483 } 291 484 … … 418 611 419 612 my $type = $data->{__type}; 420 my $nurls = $data->{__nurls} ;613 my $nurls = $data->{__nurls} || 0; 421 614 if ($nurls >= $data->{urlcount_block_limit}) { 422 615 $eh->error("Blocking $type on blog " . $obj->blog_id . … … 463 656 my $remote_ip = $obj->ip; 464 657 foreach my $url (@$urls) { 658 next if $url !~ m/\./; # ignore domain if it is just a single word 465 659 if ($url =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) { 466 660 $url = "$4.$3.$2.$1"; … … 503 697 sub checkdns { 504 698 my ($name) = @_; 699 require MT::Request; 700 my $cache = MT::Request->instance->cache('checkdns_cache') || {}; 701 return $cache->{$name} if exists $cache->{$name}; 505 702 my $iaddr = gethostbyname($name); 506 703 return 0 unless $iaddr; 507 704 require Socket; 508 705 my $ip = Socket::inet_ntoa($iaddr); 706 $cache->{$name} = $ip; 707 MT::Request->instance->cache('checkdns_cache', $cache); 509 708 return $ip ? $ip : undef; 510 709 } 511 710 512 711 sub extract_urls { 513 my ($str, $single, $total) = @_; 514 712 my ($str, $mode, $total) = @_; 713 714 $mode ||= 0; 515 715 # unmunge so we can see encoded urls as well 516 716 $str = lc decode_entities($str); … … 523 723 $domain =~ s/^www\.//s; 524 724 next unless $domain; 725 next unless $domain =~ m/\./; 525 726 my @parts = split /\./, $domain; 526 727 next unless @parts; … … 532 733 next; 533 734 } 534 return $domain if $single; 735 return $domain if $mode == 1; 736 next if $seen{$domain}; 535 737 $$total++ if(defined($total)); 536 my $last = $#parts;537 my $start = length($parts[$last]) < 3 ? 2 : 1;538 if ($start > $last) {539 if ( !$seen{$domain}) {738 if ($mode == 0) { # default mode, replicate for all subdomains 739 my $last = $#parts; 740 my $start = length($parts[$last]) < 3 ? 2 : 1; 741 if ($start > $last) { 540 742 $seen{$domain} = 1; 541 743 push @urls, $domain; 542 744 } 543 } 544 foreach (my $i = $start; $i <= $last; $i++) { 545 my $partial = join '.', @parts[$last - $i .. $last]; 546 next if $seen{$partial}; 547 $seen{$partial} = 1; 548 push @urls, $partial; 745 foreach (my $i = $start; $i <= $last; $i++) { 746 my $partial = join '.', @parts[$last - $i .. $last]; 747 next if $seen{$partial}; 748 $seen{$partial} = 1; 749 push @urls, $partial; 750 } 751 } else { 752 $seen{$domain} = 1; 753 push @urls, $domain; 549 754 } 550 755 } … … 553 758 } 554 759 555 # yanked from HTML::Entities, since some users don't have the module556 760 sub decode_entities { 557 761 my ($str) = @_; 558 my $c; 559 for ($str) { 560 s/(&\#(\d+);?)/$2 < 256 ? chr($2) : $1/eg; 561 s/(&\#[xX]([0-9a-fA-F]+);?)/$c = hex($2); $c < 256 ? chr($c) : $1/eg; 562 } 563 $str; 762 if (eval { require HTML::Entities; 1 }) { 763 return HTML::Entities::decode($str); 764 } else { 765 # yanked from HTML::Entities, since some users don't have the module 766 my $c; 767 for ($str) { 768 s/(&\#(\d+);?)/$2 < 256 ? chr($2) : $1/eg; 769 s/(&\#[xX]([0-9a-fA-F]+);?)/$c = hex($2); $c < 256 ? chr($c) : $1/eg; 770 } 771 $str; 772 } 564 773 } 565 774 trunk/plugins/spamlookup/tmpl/spamlookup.tmpl
r43 r47 22 22 23 23 <p> 24 The SpamLookup plugin allows you block comments and TrackBack pings 25 from open proxies. The plugin checks the IP address of each submitted 26 comment against your choice of IP blacklists of known open proxies. 24 SpamLookup provides a combination of antispam tests that allow you to 25 identify and moderate or block incoming or existing weblog spam. 27 26 </p> 28 27 29 30 <form method="post" action="mt-spamlookup.cgi"> 28 <p> 29 <a href="http://en.wikipedia.org/wiki/Link_spam">Learn more about "Link spam"</a> 30 </p> 31 32 <p> 33 <strong>Note:</strong> Please note that all of the following settings are 34 installation-wide. They will apply to all of your weblogs for this Movable 35 Type installation. 36 </p> 37 38 <p> 39 <a href="<TMPL_VAR NAME=SCRIPT_URL>?__mode=view&_type=comments">View Comments</a><br /> 40 <a href="<TMPL_VAR NAME=SCRIPT_URL>?__mode=view&_type=pings">View TrackBacks</a><br /> 41 <a href="<TMPL_VAR NAME=SCRIPT_URL>?__mode=despam&_type=comments">Despam existing comments</a><br /> 42 <a href="<TMPL_VAR NAME=SCRIPT_URL>?__mode=despam&_type=pings">Despam existing TrackBack pings</a> 43 </p> 44 45 <form method="post" action="<TMPL_VAR NAME=SCRIPT_URL>"> 31 46 <input type="hidden" name="__mode" value="save" /> 32 47 … … 41 56 </p> 42 57 43 <h3>Filter Options</h3>44 45 <p> 46 <input type="checkbox" name="allow_auth" id="allow_auth" value="1" <TMPL_IF NAME=ALLOW_AUTH>checked="checked"</TMPL_IF> /> <label for="allow_auth"> Don't filter authenticated comments</label><br />58 <h3>Filt
