rubyblog/lib/spam_protection.rb

110 lines
2.7 KiB
Ruby

class SpamProtection
IP_RBLS = ['opm.blitzed.us', 'bsb.empty.us']
HOST_RBLS = ['multi.surbl.org', 'bsb.empty.us']
SECOND_LEVEL = %w(co com net org gov)
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}")
return 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)
uri_list = string.scan(/(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?
if uris.size > limit
throw :hit, "Hard URL Limit hit: #{uris.size} > #{limit}"
end
end
def scan_uris(uris = [])
uris.each do |uri|
host = URI.parse(uri).host rescue next
return scan_ip(host) if host =~ Format::IP_ADDRESS
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|
begin
response = IPSocket.getaddress([d, rbl].join('.'))
if response =~ /^127\.0\.0\./
throw :hit,
"#{rbl} positively resolved subdomain #{d} => #{response}"
end
rescue SocketError # rubocop:disable Lint/HandleExceptions
# NXDOMAIN response => negative: d is not in RBL
end
end
end
false
end
def logger
@logger ||= ::Rails.logger || Logger.new(STDOUT)
end
end
module ActiveRecord
module Validations
module ClassMethods
def validates_against_spamdb(*attr_names)
configuration = { message: 'blocked by SpamProtection' }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) if SpamProtection.new(record.blog).is_spam?(value)
end
end
end
end
end