domain-probe 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +3 -0
- data/Rakefile +16 -0
- data/domain-probe.gemspec +26 -0
- data/lib/domain-probe/crawling_policy.rb +97 -0
- data/lib/domain-probe/guessing_policy.rb +120 -0
- data/lib/domain-probe/policy.rb +17 -0
- data/lib/domain-probe/resolver_patch.rb +34 -0
- data/lib/domain-probe/util.rb +157 -0
- data/lib/domain-probe/version.rb +4 -0
- data/lib/domain-probe.rb +222 -0
- metadata +102 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rake'
|
4
|
+
require File.expand_path('../lib/domain-probe/version', __FILE__)
|
5
|
+
|
6
|
+
task :default
|
7
|
+
|
8
|
+
desc 'Builds the gem'
|
9
|
+
task :build do
|
10
|
+
sh "gem build domain-probe.gemspec"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Builds and installs the gem'
|
14
|
+
task :install => :build do
|
15
|
+
sh "gem install domain-probe-#{Probe::VERSION}"
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "domain-probe/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "domain-probe"
|
7
|
+
s.version = Probe::VERSION
|
8
|
+
s.authors = ["ijammy"]
|
9
|
+
s.email = ["mzhang@yottaa.com"]
|
10
|
+
s.homepage = "http://www.yottaa.com"
|
11
|
+
s.summary = "A simple library to probe the dns records under specific domain"
|
12
|
+
s.description = "A simple library to probe the dns records under specific domain, as many as possilbe"
|
13
|
+
|
14
|
+
s.rubyforge_project = "domain-probe"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_runtime_dependency "net-dns"
|
25
|
+
s.add_runtime_dependency "nokogiri"
|
26
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'domain-probe/util'
|
5
|
+
require 'domain-probe/policy'
|
6
|
+
|
7
|
+
module Probe
|
8
|
+
|
9
|
+
#consts
|
10
|
+
DOUBLE_SLASH_LENGTH = 2
|
11
|
+
|
12
|
+
TAG_NAME_A = "a".freeze
|
13
|
+
TAG_NAME_SCRIPT = "script".freeze
|
14
|
+
TAG_NAME_IMG = "img".freeze
|
15
|
+
TAG_NAME_FORM = "form".freeze
|
16
|
+
|
17
|
+
ATTR_NAME_SRC = "src".freeze
|
18
|
+
ATTR_NAME_ACTION = "action".freeze
|
19
|
+
ATTR_NAME_HREF = "href".freeze
|
20
|
+
|
21
|
+
#
|
22
|
+
class CrawlingPolicy < Policy
|
23
|
+
|
24
|
+
#
|
25
|
+
# return a set of possilbe host under specific domain by crawling the URL within HTML tags
|
26
|
+
#
|
27
|
+
def possible_host_under_domain(domain)
|
28
|
+
#sanity check
|
29
|
+
return [].to_set if !domain
|
30
|
+
|
31
|
+
set = Set.new()
|
32
|
+
begin
|
33
|
+
|
34
|
+
#choose the default website to try
|
35
|
+
page = Nokogiri::HTML(open(Util.polish_url(domain)))
|
36
|
+
|
37
|
+
#a lambda to check if the attribute is of an absolute url or not
|
38
|
+
absolute_url_checker = lambda { |element,attribute|
|
39
|
+
attrib = element[attribute]
|
40
|
+
attrib && (attrib.downcase =~ /^http[s]?:\/\// || attrib.downcase.start_with?("//") )
|
41
|
+
}
|
42
|
+
|
43
|
+
#try to find some subdomains by a tags
|
44
|
+
links = page.css(TAG_NAME_A).select{ |element| absolute_url_checker.call(element,ATTR_NAME_HREF) }
|
45
|
+
set |= iterate_elements_with_attr(links,ATTR_NAME_HREF,domain)
|
46
|
+
|
47
|
+
#try to find some subdomains by script tags
|
48
|
+
scripts = page.css(TAG_NAME_SCRIPT).select{ |element| absolute_url_checker.call(element,ATTR_NAME_SRC) }
|
49
|
+
set |= iterate_elements_with_attr(scripts,ATTR_NAME_SRC,domain)
|
50
|
+
|
51
|
+
#try to find some subdomains by img tags
|
52
|
+
images = page.css(TAG_NAME_IMG).select{ |element| absolute_url_checker.call(element,ATTR_NAME_SRC) }
|
53
|
+
set |= iterate_elements_with_attr(images,ATTR_NAME_SRC,domain)
|
54
|
+
|
55
|
+
#try to find some subdomains by img tags
|
56
|
+
forms = page.css(TAG_NAME_FORM).select{ |element| absolute_url_checker.call(element,ATTR_NAME_ACTION) }
|
57
|
+
set |= iterate_elements_with_attr(forms,ATTR_NAME_ACTION,domain)
|
58
|
+
|
59
|
+
rescue => ex
|
60
|
+
puts "#{ex.class}: #{ex.message}"
|
61
|
+
end
|
62
|
+
set
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
#
|
71
|
+
#iterate the elements to find a set of host names
|
72
|
+
#
|
73
|
+
def iterate_elements_with_attr(links,attribute,domain)
|
74
|
+
raise ArgumentError, "Both attribute name and domain name are expected" if !attribute || !domain
|
75
|
+
return [].to_set if !links
|
76
|
+
set = Set.new()
|
77
|
+
|
78
|
+
links.each { |link|
|
79
|
+
next unless link[attribute]
|
80
|
+
head = link[attribute].index("//")
|
81
|
+
next unless head
|
82
|
+
tail = link[attribute].index(domain)
|
83
|
+
next unless tail
|
84
|
+
|
85
|
+
#slice the host in the url
|
86
|
+
host = link[attribute].slice(head+DOUBLE_SLASH_LENGTH,tail-head-DOUBLE_SLASH_LENGTH)
|
87
|
+
|
88
|
+
#remove the trailing dot if any
|
89
|
+
host.slice!(host.length-1) if host.end_with? "."
|
90
|
+
set.add host
|
91
|
+
}
|
92
|
+
|
93
|
+
set
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'domain-probe/policy'
|
2
|
+
module Probe
|
3
|
+
|
4
|
+
#
|
5
|
+
# Popular host names, in alphabetic order
|
6
|
+
#
|
7
|
+
COMMON_NAMES = %w{
|
8
|
+
ad
|
9
|
+
ads
|
10
|
+
api
|
11
|
+
app
|
12
|
+
application
|
13
|
+
apps
|
14
|
+
ask
|
15
|
+
asset
|
16
|
+
assets
|
17
|
+
au
|
18
|
+
auto
|
19
|
+
|
20
|
+
baby
|
21
|
+
bbs
|
22
|
+
biz
|
23
|
+
blog
|
24
|
+
build
|
25
|
+
cal
|
26
|
+
calendar
|
27
|
+
cards
|
28
|
+
client
|
29
|
+
club
|
30
|
+
college
|
31
|
+
date
|
32
|
+
dev
|
33
|
+
develop
|
34
|
+
developer
|
35
|
+
dictionary
|
36
|
+
dir
|
37
|
+
direct
|
38
|
+
discovery
|
39
|
+
docs
|
40
|
+
download
|
41
|
+
downloads
|
42
|
+
email
|
43
|
+
english
|
44
|
+
events
|
45
|
+
feed
|
46
|
+
finance
|
47
|
+
food
|
48
|
+
fresh
|
49
|
+
ftp
|
50
|
+
help
|
51
|
+
home
|
52
|
+
hr
|
53
|
+
image
|
54
|
+
images
|
55
|
+
imap
|
56
|
+
|
57
|
+
info
|
58
|
+
ip
|
59
|
+
iphone
|
60
|
+
local
|
61
|
+
log
|
62
|
+
login
|
63
|
+
love
|
64
|
+
m
|
65
|
+
mail
|
66
|
+
maps
|
67
|
+
media
|
68
|
+
message
|
69
|
+
messages
|
70
|
+
mon
|
71
|
+
money
|
72
|
+
monitor
|
73
|
+
mp3
|
74
|
+
news
|
75
|
+
operation
|
76
|
+
operations
|
77
|
+
page
|
78
|
+
pages
|
79
|
+
php
|
80
|
+
play
|
81
|
+
plus
|
82
|
+
pops
|
83
|
+
production
|
84
|
+
qa
|
85
|
+
read
|
86
|
+
repo
|
87
|
+
resource
|
88
|
+
resources
|
89
|
+
sg
|
90
|
+
shopping
|
91
|
+
sms
|
92
|
+
smtp
|
93
|
+
sports
|
94
|
+
staging
|
95
|
+
static
|
96
|
+
stats
|
97
|
+
status
|
98
|
+
studios
|
99
|
+
support
|
100
|
+
trading
|
101
|
+
video
|
102
|
+
view
|
103
|
+
vip
|
104
|
+
web
|
105
|
+
webmail
|
106
|
+
wiki
|
107
|
+
wireless
|
108
|
+
www
|
109
|
+
www1
|
110
|
+
}
|
111
|
+
|
112
|
+
class GuessingPolicy < Policy
|
113
|
+
|
114
|
+
#
|
115
|
+
def possible_host_under_domain(domain)
|
116
|
+
COMMON_NAMES.to_set
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Probe
|
2
|
+
|
3
|
+
#const declaration
|
4
|
+
WWW_PREFIX = "www."
|
5
|
+
|
6
|
+
#
|
7
|
+
# The policy interface specification, each child class must implement the possible_host_under_domain method
|
8
|
+
#
|
9
|
+
class Policy
|
10
|
+
|
11
|
+
def possible_host_under_domain(domain)
|
12
|
+
[].to_set
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'net/dns/resolver'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Hack: the RR in net-dns didn't implement the ==,eql?,hash.methods
|
5
|
+
#
|
6
|
+
module Net
|
7
|
+
module DNS
|
8
|
+
class RR
|
9
|
+
class TXT
|
10
|
+
def value
|
11
|
+
@txt.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_inspect
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(o)
|
20
|
+
if o.is_a? RR
|
21
|
+
self.data == o.data
|
22
|
+
else
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
alias eql? ==
|
28
|
+
|
29
|
+
def hash
|
30
|
+
self.data.hash
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'domain-probe/resolver_patch'
|
2
|
+
#
|
3
|
+
#
|
4
|
+
#
|
5
|
+
module Probe
|
6
|
+
|
7
|
+
#
|
8
|
+
# Utility functions to assist domain-probing
|
9
|
+
#
|
10
|
+
class Util
|
11
|
+
|
12
|
+
#
|
13
|
+
# Detect the zone name the specific domain belongs to
|
14
|
+
#
|
15
|
+
def self.detect_zone domain
|
16
|
+
|
17
|
+
return nil if !domain
|
18
|
+
domain = append_trailing_dot_if_necessary domain
|
19
|
+
names = domain.split "."
|
20
|
+
|
21
|
+
zone = nil
|
22
|
+
begin
|
23
|
+
names.size.times { |index|
|
24
|
+
resovler = Net::DNS::Resolver.new
|
25
|
+
packet = resovler.query(domain, Net::DNS::NS)
|
26
|
+
packet.answer.each { |record|
|
27
|
+
next unless record.name == domain && record.type == "NS"
|
28
|
+
zone = domain
|
29
|
+
break
|
30
|
+
} if packet && packet.answer
|
31
|
+
|
32
|
+
return remove_trailing_dot_if_any(zone) unless !zone
|
33
|
+
domain = domain.slice(names[index].length + 1,domain.length - names[index].length - 1)
|
34
|
+
}
|
35
|
+
rescue => ex
|
36
|
+
puts "#{ex.class}: #{ex.message}"
|
37
|
+
end
|
38
|
+
|
39
|
+
zone
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Detect the nameservers against specific zone
|
44
|
+
#
|
45
|
+
def self.detect_nameservers zone
|
46
|
+
return [] if !zone
|
47
|
+
zone = append_trailing_dot_if_necessary zone
|
48
|
+
|
49
|
+
nameserver_ips = []
|
50
|
+
nameserver_names = []
|
51
|
+
begin
|
52
|
+
resovler = Net::DNS::Resolver.new
|
53
|
+
packet = resovler.query(zone, Net::DNS::NS)
|
54
|
+
packet.answer.each { |record|
|
55
|
+
next unless record.name == append_trailing_dot_if_necessary(zone) && record.type == "NS"
|
56
|
+
nameserver_names << record.nsdname
|
57
|
+
packet.additional.select{ |x| x.name == record.nsdname && x.type == "A" }.each{ |rr|
|
58
|
+
nameserver_ips << rr.address.to_s
|
59
|
+
}
|
60
|
+
} if packet && packet.answer
|
61
|
+
|
62
|
+
if nameserver_ips.empty?
|
63
|
+
nameserver_ips += resolve_addresses(nameserver_names)
|
64
|
+
end
|
65
|
+
rescue => ex
|
66
|
+
puts "#{ex.class}: #{ex.message}"
|
67
|
+
end
|
68
|
+
|
69
|
+
nameserver_ips
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Organize the authorities to assist recusive queries
|
74
|
+
#
|
75
|
+
def self.organize_authorities(authorities,additional)
|
76
|
+
|
77
|
+
return [] if !authorities
|
78
|
+
nameserver_ips = []
|
79
|
+
nameserver_names = []
|
80
|
+
authorities.each{ |authority|
|
81
|
+
next unless authority.type == "NS"
|
82
|
+
nameserver_names << authority.nsdname
|
83
|
+
additional.select{ |x| x.name == authority.nsdname && x.type == "A" }.each{ |rr|
|
84
|
+
nameserver_ips << rr.address.to_s
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
if nameserver_ips.empty?
|
89
|
+
nameserver_ips += resolve_addresses(nameserver_names)
|
90
|
+
end
|
91
|
+
|
92
|
+
nameserver_ips
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
#
|
97
|
+
#
|
98
|
+
def self.polish_url(domain)
|
99
|
+
return nil if !domain
|
100
|
+
zone = detect_zone domain
|
101
|
+
"http://" + (!zone || domain.start_with?(WWW_PREFIX) ? domain : WWW_PREFIX + zone)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
#
|
106
|
+
# if the domain is not ended with dot, then append one
|
107
|
+
#
|
108
|
+
def self.append_trailing_dot_if_necessary domain
|
109
|
+
return domain if !domain || domain.end_with?(".")
|
110
|
+
domain + "."
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# removing trailing dot, if there is one
|
115
|
+
#
|
116
|
+
def self.remove_trailing_dot_if_any domain
|
117
|
+
return domain unless !domain || domain.end_with?(".")
|
118
|
+
domain.chop
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# to judge whether the domain is a subdomain of zone.
|
123
|
+
#
|
124
|
+
def self.is_subdomain?(domain,zone)
|
125
|
+
return false if !domain || !zone
|
126
|
+
zone_with_trailing_dot = Util.append_trailing_dot_if_necessary(zone)
|
127
|
+
return false if domain == zone_with_trailing_dot
|
128
|
+
domain.end_with?(zone_with_trailing_dot) && domain[domain.index(zone_with_trailing_dot) - 1,1] == "."
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
#
|
134
|
+
# resolve the names to ip addresses
|
135
|
+
#
|
136
|
+
def self.resolve_addresses names
|
137
|
+
|
138
|
+
return [] unless names && !names.empty?
|
139
|
+
addresses = []
|
140
|
+
begin
|
141
|
+
resovler = Net::DNS::Resolver.new
|
142
|
+
names.each{ |name|
|
143
|
+
packet = resovler.query(name, Net::DNS::A)
|
144
|
+
packet.answer.each { |record|
|
145
|
+
next unless record.type == "A"
|
146
|
+
addresses << record.address.to_s
|
147
|
+
}
|
148
|
+
}
|
149
|
+
rescue => ex
|
150
|
+
puts "#{ex.class}: #{ex.message}"
|
151
|
+
end
|
152
|
+
addresses
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
data/lib/domain-probe.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'domain-probe/resolver_patch'
|
2
|
+
require 'domain-probe/util'
|
3
|
+
require 'domain-probe/policy'
|
4
|
+
require 'domain-probe/guessing_policy'
|
5
|
+
require 'domain-probe/crawling_policy'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
module Probe
|
9
|
+
|
10
|
+
class DomainProbe
|
11
|
+
|
12
|
+
MAX_RECURSIVE_QUERY_DEPTH = 6
|
13
|
+
|
14
|
+
@policies = []
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_accessor :policies
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
#
|
22
|
+
def self.register_policy policy
|
23
|
+
raise ArgumentError, "policy need respond_to? possible_host_under_domain" unless policy.kind_of? Policy
|
24
|
+
self.policies << policy
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.unregister_policy policy
|
28
|
+
self.policies.delete policy
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# collect the records under domain domain
|
33
|
+
#
|
34
|
+
def self.collect_records domain
|
35
|
+
self.new.collect_records domain
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# ----------------------------------------
|
40
|
+
#
|
41
|
+
#
|
42
|
+
#
|
43
|
+
def initialize()
|
44
|
+
@a_records = []
|
45
|
+
@aaaa_records = []
|
46
|
+
@cname_records = []
|
47
|
+
@mx_records = []
|
48
|
+
@mr_records = []
|
49
|
+
@txt_records = []
|
50
|
+
@hinfo_records = []
|
51
|
+
@srv_records = []
|
52
|
+
@ns_records = []
|
53
|
+
@semaphore = Mutex.new
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_accessor :a_records
|
57
|
+
attr_accessor :aaaa_records
|
58
|
+
attr_accessor :cname_records
|
59
|
+
attr_accessor :mx_records
|
60
|
+
attr_accessor :mr_records
|
61
|
+
attr_accessor :txt_records
|
62
|
+
attr_accessor :hinfo_records
|
63
|
+
attr_accessor :srv_records
|
64
|
+
attr_accessor :ns_records
|
65
|
+
attr_accessor :semaphore
|
66
|
+
|
67
|
+
#
|
68
|
+
# collect the records under domain domain
|
69
|
+
#
|
70
|
+
def collect_records domain
|
71
|
+
raise ArgumentError, "Domain name is expected" unless domain
|
72
|
+
domain = zone = Util.detect_zone(domain)
|
73
|
+
|
74
|
+
threads = []
|
75
|
+
nameservers = Util.detect_nameservers zone
|
76
|
+
possible_domains = Set.new
|
77
|
+
|
78
|
+
DomainProbe.policies.each{ |policy|
|
79
|
+
possible_domains |= policy.possible_host_under_domain(zone)
|
80
|
+
}
|
81
|
+
|
82
|
+
#A/CNAME records
|
83
|
+
possible_domains.each{ |possible_host|
|
84
|
+
possible_domain = possible_host + "." + zone
|
85
|
+
|
86
|
+
threads << threaded_resolve(possible_domain,Net::DNS::A,zone,nameservers)
|
87
|
+
#AAAA
|
88
|
+
threads << threaded_resolve(possible_domain,Net::DNS::AAAA,zone,nameservers)
|
89
|
+
}
|
90
|
+
|
91
|
+
#A/CNAME records
|
92
|
+
threads << threaded_resolve(domain,Net::DNS::A,zone,nameservers)
|
93
|
+
#AAAA
|
94
|
+
threads << threaded_resolve(domain,Net::DNS::AAAA,zone,nameservers)
|
95
|
+
|
96
|
+
#MX records
|
97
|
+
threads << threaded_resolve(domain,Net::DNS::MX,zone,nameservers)
|
98
|
+
|
99
|
+
#MR records
|
100
|
+
threads << threaded_resolve(domain,Net::DNS::MR,zone,nameservers)
|
101
|
+
|
102
|
+
#TXT records
|
103
|
+
threads << threaded_resolve(domain,Net::DNS::TXT,zone,nameservers)
|
104
|
+
|
105
|
+
#HINFO records
|
106
|
+
threads << threaded_resolve(domain,Net::DNS::HINFO,zone,nameservers)
|
107
|
+
|
108
|
+
#SRV records
|
109
|
+
threads << threaded_resolve(domain,Net::DNS::SRV,zone,nameservers)
|
110
|
+
|
111
|
+
#NS records
|
112
|
+
threads << threaded_resolve(domain,Net::DNS::NS,zone,nameservers)
|
113
|
+
|
114
|
+
threads.each do |t|
|
115
|
+
t.join
|
116
|
+
end
|
117
|
+
|
118
|
+
self.a_records + self.aaaa_records + self.cname_records + self.mx_records + self.mr_records + self.txt_records + self.hinfo_records + self.srv_records + self.ns_records
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
#
|
124
|
+
# the method will be called under the threaded execution context
|
125
|
+
# Attention:
|
126
|
+
# 1. tail recursive call happened when we get an un-authorized reply
|
127
|
+
# 2. depth limit to avoid infinite recursion
|
128
|
+
#
|
129
|
+
def resolve_recursive(domain,type,zone,nameservers,depth)
|
130
|
+
|
131
|
+
depth += 1
|
132
|
+
return if depth > MAX_RECURSIVE_QUERY_DEPTH
|
133
|
+
single_record_handler = lambda{ |record|
|
134
|
+
|
135
|
+
if record.name && (record.name == Util.append_trailing_dot_if_necessary(zone) || Util.is_subdomain?(record.name,zone))
|
136
|
+
#A little overhead here
|
137
|
+
self.semaphore.synchronize {
|
138
|
+
case record.type
|
139
|
+
when "A"
|
140
|
+
self.a_records << record unless self.a_records.include? record
|
141
|
+
when "AAAA"
|
142
|
+
self.aaaa_records << record unless self.aaaa_records.include? record
|
143
|
+
when "CNAME"
|
144
|
+
self.cname_records << record unless self.cname_records.include? record
|
145
|
+
when "NS"
|
146
|
+
self.ns_records << record unless self.ns_records.include? record
|
147
|
+
when "MX"
|
148
|
+
self.mx_records << record unless self.mx_records.include? record
|
149
|
+
when "MR"
|
150
|
+
self.mr_records << record unless self.mr_records.include? record
|
151
|
+
when "TXT"
|
152
|
+
self.txt_records << record unless self.txt_records.include? record
|
153
|
+
when "SRV"
|
154
|
+
self.srv_records << record unless self.srv_records.include? record
|
155
|
+
when "HINFO"
|
156
|
+
self.hinfo_records << record unless self.hinfo_records.include? record
|
157
|
+
else
|
158
|
+
#nothing to do?
|
159
|
+
end
|
160
|
+
}
|
161
|
+
end
|
162
|
+
}
|
163
|
+
|
164
|
+
#
|
165
|
+
begin
|
166
|
+
|
167
|
+
resolver = nameservers.empty? ? Net::DNS::Resolver.new : Net::DNS::Resolver.new(:nameserver => nameservers)
|
168
|
+
resolver.log_level = Net::DNS::ERROR
|
169
|
+
packet = resolver.query(domain, type)
|
170
|
+
return unless packet
|
171
|
+
|
172
|
+
if packet.header.auth?
|
173
|
+
packet.answer.each &single_record_handler if packet.answer
|
174
|
+
packet.authority.each &single_record_handler if packet.authority
|
175
|
+
packet.additional.each &single_record_handler if packet.additional
|
176
|
+
elsif packet.answer.empty? && !packet.authority.empty?
|
177
|
+
#following down
|
178
|
+
nameservers = Util.organize_authorities(packet.authority,packet.additional)
|
179
|
+
#tail recursive
|
180
|
+
resolve_recursive(domain,type,zone,nameservers,depth)
|
181
|
+
end
|
182
|
+
|
183
|
+
rescue =>ex
|
184
|
+
puts "#{ex.class}: #{ex.message}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# The thread should benifit us, IF NOT, disable it
|
190
|
+
#
|
191
|
+
def threaded_resolve(domain,type,zone,nameservers)
|
192
|
+
Thread.new(domain,type,zone,nameservers) { |domain,type,zone,nameservers|
|
193
|
+
depth = 0
|
194
|
+
resolve_recursive(domain,type,zone,nameservers,depth)
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
register_policy GuessingPolicy.new
|
199
|
+
register_policy CrawlingPolicy.new
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
#
|
204
|
+
#
|
205
|
+
def collect_records domain
|
206
|
+
DomainProbe.collect_records domain
|
207
|
+
end
|
208
|
+
|
209
|
+
def collect_records_print_beautifully domain
|
210
|
+
start = Time.now.to_i
|
211
|
+
records = DomainProbe.collect_records(domain)
|
212
|
+
finish = Time.now.to_i
|
213
|
+
print "-----------------------","\n"
|
214
|
+
records.each{ |record|
|
215
|
+
print record, "\n"
|
216
|
+
}
|
217
|
+
print "-----------------------","\n"
|
218
|
+
print "Time elapsed:\t",finish - start,"seconds. \n"
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: domain-probe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- ijammy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70294597444060 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70294597444060
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70294597442500 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70294597442500
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: net-dns
|
38
|
+
requirement: &70294597440720 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70294597440720
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: nokogiri
|
49
|
+
requirement: &70294597439640 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70294597439640
|
58
|
+
description: A simple library to probe the dns records under specific domain, as many
|
59
|
+
as possilbe
|
60
|
+
email:
|
61
|
+
- mzhang@yottaa.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- Gemfile
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- domain-probe.gemspec
|
71
|
+
- lib/domain-probe.rb
|
72
|
+
- lib/domain-probe/crawling_policy.rb
|
73
|
+
- lib/domain-probe/guessing_policy.rb
|
74
|
+
- lib/domain-probe/policy.rb
|
75
|
+
- lib/domain-probe/resolver_patch.rb
|
76
|
+
- lib/domain-probe/util.rb
|
77
|
+
- lib/domain-probe/version.rb
|
78
|
+
homepage: http://www.yottaa.com
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project: domain-probe
|
98
|
+
rubygems_version: 1.8.10
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: A simple library to probe the dns records under specific domain
|
102
|
+
test_files: []
|