Class: DNSBL::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/dnsbl/client.rb,
lib/dnsbl/client/version.rb

Overview

Client actually handles the sending of queries to a recursive DNS server and places any replies into DNSBLResults

Constant Summary collapse

VERSION =

Current version of the dnsbl-client gem

"1.0.5"

Instance Method Summary collapse

Constructor Details

#initialize(config = YAML.load(File.open(File.expand_path('../../../data', __FILE__)+"/dnsbl.yaml").read), two_level_tldfile = File.expand_path('../../../data', __FILE__)+"/two-level-tlds", three_level_tldfile = File.expand_path('../../../data', __FILE__)+"/three-level-tlds") ⇒ Client

initialize a new DNSBL::Client object the config file automatically points to a YAML file containing the list of DNSBLs and their return codes the two-level-tlds file lists most of the two level tlds, needed for hostname to domain normalization



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/dnsbl/client.rb', line 40

def initialize(config = YAML.load(File.open(File.expand_path('../../../data', __FILE__)+"/dnsbl.yaml").read),
							 two_level_tldfile = File.expand_path('../../../data', __FILE__)+"/two-level-tlds",
							 three_level_tldfile = File.expand_path('../../../data', __FILE__)+"/three-level-tlds")
	@dnsbls = config
	@two_level_tld = []
	@three_level_tld = []
	File.open(two_level_tldfile).readlines.each do |l|
		@two_level_tld << l.strip
	end
	File.open(three_level_tldfile).readlines.each do |l|
		@three_level_tld << l.strip
	end
	@sockets = []
	config = Resolv::DNS::Config.new
	config.nameservers.each do |ip,port|
		sock = UDPSocket.new
		sock.connect(ip,port)
		@sockets << sock
		break # let's just the first nameserver in this version of the library
	end
	@socket_index = 0
end

Instance Method Details

#_encode_query(item, itemtype, domain, apikey = nil) ⇒ Object

converts an ip or a hostname into the DNS query packet requires to lookup the result



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/dnsbl/client.rb', line 108

def _encode_query(item,itemtype,domain,apikey=nil)
	label = nil
	if itemtype == 'ip'
		label = item.split(/\./).reverse.join(".")
	elsif itemtype == 'domain'
		label = normalize(item)
	end
	lookup = "#{label}.#{domain}"
	if apikey
		lookup = "#{apikey}.#{lookup}"
	end
	txid = lookup.sum
	message = Resolv::DNS::Message.new(txid)
	message.rd = 1
	message.add_question(lookup,Resolv::DNS::Resource::IN::A)
	message.encode
end

#add_dnsbl(name, domain, type = 'ip', codes = {"0"=>"OK","127.0.0.2"=>"Blacklisted"}) ⇒ Object

allows the adding of a new DNSBL to the set of configured DNSBLs



96
97
98
99
100
# File 'lib/dnsbl/client.rb', line 96

def add_dnsbl(name,domain,type='ip',codes={"0"=>"OK","127.0.0.2"=>"Blacklisted"})
	@dnsbls[name] = codes
	@dnsbls[name]['domain'] = domain
	@dnsbls[name]['type'] = type
end

#dnsblsObject

returns a list of DNSBL names currently configured



103
104
105
# File 'lib/dnsbl/client.rb', line 103

def dnsbls
	@dnsbls.keys
end

#lookup(item) ⇒ Object

returns an array of DNSBLResult



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/dnsbl/client.rb', line 129

def lookup(item)
	# if item is an array, use it, otherwise make it one
	items = item
	if item.is_a? String
		items = [item]
	end
	# place the results in the results array
	results = []
	# for each ip or hostname
	items.each do |item|
		# sent is used to determine when we have all the answers
		sent = 0
		# record the start time
		@starttime = Time.now.to_f
		# determine the type of query
		itemtype = (item =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) ? 'ip' : 'domain'
		# for each dnsbl that supports our type, create the DNS query packet and send it
		# rotate across our configured name servers and increment sent
		@dnsbls.each do |name,config|
			next if config['disabled']
			next unless config['type'] == itemtype
			begin
				msg = _encode_query(item,itemtype,config['domain'],config['apikey'])
				@sockets[@socket_index].send(msg,0)
				@socket_index += 1
				@socket_index %= @sockets.length
				sent += 1
			rescue Exception => e
				puts e
				puts e.backtrace.join("\n")
			end
		end
		# while we still expect answers
		while sent > 0
			# wait on the socket for maximally 1.5 seconds
			r,_,_ = IO.select(@sockets,nil,nil,1.5)
			# if we time out, break out of the loop
			break unless r
			# for each reply, decode it and receive results, decrement the pending answers
			r.each do |s|
				begin
					response = _decode_response(s.recv(4096))
					results += response
				rescue Exception => e
					puts e
					puts e.backtrace.join("\n")
				end
				sent -= 1
			end
		end
	end
	results
end

#nameservers=(ns = Resolv::DNS::Config.new.nameservers) ⇒ Object

sets the nameservers used for performing DNS lookups in round-robin fashion



64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/dnsbl/client.rb', line 64

def nameservers=(ns=Resolv::DNS::Config.new.nameservers)
	@sockets.each do |s|
		s.close
	end
	@sockets = []
	ns.each do |ip,port|
		sock = UDPSocket.new
		sock.connect(ip,port)
		@sockets << sock
		break # let's just the first nameserver in this version of the library
	end
	@socket_index = 0
end

#normalize(domain) ⇒ Object

Converts a hostname to the domain: e.g., www.google.com => google.com, science.somewhere.co.uk => somewhere.co.uk



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/dnsbl/client.rb', line 79

def normalize(domain)
	# strip off the protocol (\w{1,20}://), the URI (/), parameters (?), port number (:), and username (.*@)
	# then split into parts via the .
	parts = domain.gsub(/^\w{1,20}:\/\//,'').gsub(/[\/\?\:].*/,'').gsub(/.*?\@/,'').split(/\./)
	# grab the last two parts of the domain
	dom = parts[-2,2].join(".")
	# if the dom is in the two_level_tld list, then use three parts
	if @two_level_tld.index(dom)
		dom = parts[-3,3].join(".")
	end
	if @three_level_tld.index(dom)
		dom = parts[-4,4].join(".")
	end
	dom
end