-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
spam_protection.rb
101 lines (81 loc) · 2.36 KB
/
spam_protection.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# frozen_string_literal: true
require "format"
class SpamProtection
IP_RBLS = ["opm.blitzed.us", "bsb.empty.us"].freeze
HOST_RBLS = ["multi.surbl.org", "bsb.empty.us"].freeze
SECOND_LEVEL = %w(co com net org gov).freeze
attr_accessor :this_blog
def initialize(a_blog)
self.this_blog = a_blog
end
def is_spam?(string)
return false unless this_blog.sp_global
return false if string.blank?
reason = catch(:hit) do
case string
when Format::IP_ADDRESS then scan_ip(string)
when Format::HTTP_URI then scan_uris([string])
else scan_text(string)
end
end
if reason
logger.info("[SP] Hit: #{reason}")
true
end
end
protected
def scan_ip(ip_address)
logger.info("[SP] Scanning IP #{ip_address}")
query_rbls(IP_RBLS, ip_address.split(".").reverse.join("."))
end
def scan_text(string)
# FIXME: Unify HTTP URI matchers
uri_list = string.scan(%r{(http://[^\s"]+)}m).flatten
check_uri_count(uri_list)
scan_uris(uri_list)
false
end
def check_uri_count(uris)
limit = this_blog.sp_url_limit
return if limit.to_i.zero?
throw :hit, "Hard URL Limit hit: #{uris.size} > #{limit}" if uris.size > limit
end
def scan_uris(uris = [])
uris.each do |uri|
host = begin
URI.parse(uri).host
rescue URI::InvalidURIError
next
end
return scan_ip(host) if Format::IP_ADDRESS.match?(host)
host_parts = host.split(".").reverse
domain = []
# Check for two level TLD
(SECOND_LEVEL.include?(host_parts[1]) ? 3 : 2).times do
domain.unshift(host_parts.shift)
end
logger.info("[SP] Scanning domain #{domain.join(".")}")
query_rbls(HOST_RBLS, host, domain.join("."))
logger.info("[SP] Finished domain scan #{domain.join(".")}")
return false
end
end
def query_rbls(rbls, *subdomains)
rbls.each do |rbl|
subdomains.uniq.each do |d|
response = IPSocket.getaddress([d, rbl].join("."))
if response.start_with?("127.0.0.")
throw :hit,
"#{rbl} positively resolved subdomain #{d} => #{response}"
end
rescue SocketError
# NXDOMAIN response => negative: d is not in RBL
next
end
end
false
end
def logger
@logger ||= ::Rails.logger || Logger.new($stdout)
end
end