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.1.1'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = YAML.safe_load(File.read("#{File.expand_path '../../data', __dir__}/dnsbl.yaml")), two_level_tldfile = "#{File.expand_path '../../data', __dir__}/two-level-tlds", three_level_tldfile = "#{File.expand_path '../../data', __dir__}/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



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
# File 'lib/dnsbl/client.rb', line 54

def initialize(config = YAML.safe_load(File.read("#{File.expand_path '../../data', __dir__}/dnsbl.yaml")),
               two_level_tldfile = "#{File.expand_path '../../data', __dir__}/two-level-tlds",
               three_level_tldfile = "#{File.expand_path '../../data', __dir__}/three-level-tlds")
  @dnsbls = config
  @timeout = 1.5
  @first_only = false
  @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

  # let's just the first nameserver in this version of the library
  ip, port = config.nameservers.first

  sock = UDPSocket.new
  sock.connect ip, port
  @sockets << sock
  @socket_index = 0
end

Instance Attribute Details

#first_only=(value) ⇒ Object (writeonly)

Sets the attribute first_only

Parameters:

  • value

    the value to set the attribute first_only to.



48
49
50
# File 'lib/dnsbl/client.rb', line 48

def first_only=(value)
  @first_only = value
end

#timeout=(value) ⇒ Object (writeonly)

Sets the attribute timeout

Parameters:

  • value

    the value to set the attribute timeout to.



48
49
50
# File 'lib/dnsbl/client.rb', line 48

def timeout=(value)
  @timeout = value
end

Instance Method Details

#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



108
109
110
111
112
# File 'lib/dnsbl/client.rb', line 108

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



115
116
117
# File 'lib/dnsbl/client.rb', line 115

def dnsbls
  @dnsbls.keys
end

#lookup(loopup_item) ⇒ Object

lookup performs the sending of DNS queries for the given items returns an array of DNSBLResult



121
122
123
124
125
126
127
128
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
182
183
184
# File 'lib/dnsbl/client.rb', line 121

def lookup(loopup_item)
  # if item is an array, use it, otherwise make it one
  items = Array loopup_item

  # 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.match?(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) ? 'ip' : 'domain'
    if item.match?(/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/) # rubocop: disable Layout/LineLength
      itemtype = 'ip'
    end

    # 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 StandardError => e
        puts "error for #{name}: e"
        puts e.backtrace.join("\n")
      end
    end

    # while we still expect answers
    while sent.positive?
      # wait on the socket for maximally @timeout seconds
      r, = IO.select @sockets, nil, nil, @timeout
      # if we time out, break out of the loop
      break unless r

      # for each reply, decode it and receive results, decrement the pending answers
      first_only = false
      r.each do |s|
        begin
          response = decode_response(s.recv(4096))
          results += response
        rescue StandardError => e
          puts e
          puts e.backtrace.join("\n")
        end
        sent -= 1
        if @first_only
          first_only = true
          break
        end
      end
      break if first_only
    end
  end
  results
end

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

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



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

def nameservers=(nss = Resolv::DNS::Config.new.nameservers)
  @sockets.each(&:close)
  @sockets = []

  # let's just the first nameserver in this version of the library
  ip, port = nss.first

  sock = UDPSocket.new
  sock.connect ip, port
  @sockets << sock
  @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



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/dnsbl/client.rb', line 95

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(%r{^\w{1,20}://}, '').gsub(%r{[/?:].*}, '').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
  dom = parts[-3, 3].join '.' if @two_level_tld.index dom
  dom = parts[-4, 4].join '.' if @three_level_tld.index dom
  dom
end