passivedns-client 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 87b8057605ef62a693b4460fac1e72fd8c8a0afb
4
- data.tar.gz: 9adef70cf9929f665e75a781f5e3216a9a3207c9
3
+ metadata.gz: 9e141cbc5d84ea9c25ee8c207db9c7758869117e
4
+ data.tar.gz: 81c70c9a9a4c272133c4e33a2e043fab650fd00a
5
5
  SHA512:
6
- metadata.gz: 43d6004ae73822197e0db5e4cfced825462820d3b626c75574cbddaea2ab3c49ba301067e3c58113972cc25008f4b7967076571c6fd1ab72fef09b5faa781c96
7
- data.tar.gz: 8ba404ddc64987dae536adf248a43ac6a30c9b658c71256d5cc27aa02c2e26243b22706676f80d5e733ddd06cc92cc4990b8b8ed215da3b88d7ed001114cb3be
6
+ metadata.gz: fffadc8b47040b55d25255cf62ab2b8f2498a8a39d89caa1812da37944da266f411cf9b384cb1f6ab70718ac106811313a0d4987c792487919f239ad388bc4ed
7
+ data.tar.gz: d0592a94d6f4cec71afdf55d3cccedf1cfc1fe8ecf17cc3f3d746d92b184a694e3c1e1655c17d03f3972a63bc56de433d3cb7ec9eb067172d02183f7c7ff4a5b
data/bin/pdnstool CHANGED
@@ -1,214 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'passivedns/client'
3
- require 'structformatter'
4
- require 'getoptlong'
5
- require 'yaml'
3
+ require 'passivedns/client/cli'
6
4
 
7
- def pdnslookup(state,pdnsclient,recursedepth=1,wait=0,debug=false,limit=nil)
8
- puts "pdnslookup: #{state.level} #{recursedepth}" if debug
9
- level = 0
10
- while level < recursedepth
11
- puts "pdnslookup: #{level} < #{recursedepth}" if debug
12
- state.each_query(recursedepth) do |q|
13
- rv = pdnsclient.query(q,limit)
14
- if rv
15
- rv.each do |r|
16
- if ["A","AAAA","NS","CNAME","PTR"].index(r.rrtype)
17
- puts "pdnslookup: #{r.to_s}" if debug
18
- state.add_result(r)
19
- end
20
- end
21
- else
22
- state.update_query(rv,'failed')
23
- end
24
- sleep wait if level < recursedepth
25
- end
26
- level += 1
27
- end
28
- state
29
- end
30
-
31
- def printresults(state,format,sep="\t")
32
- case format
33
- when 'text'
34
- puts PassiveDNS::PDNSResult.members.join(sep)
35
- puts state.to_s(sep)
36
- when 'yaml'
37
- puts state.to_yaml
38
- when 'xml'
39
- puts state.to_xml
40
- when 'json'
41
- puts state.to_json
42
- when 'gdf'
43
- puts state.to_gdf
44
- when 'graphviz'
45
- puts state.to_graphviz
46
- when 'graphml'
47
- puts state.to_graphml
48
- end
49
- end
50
-
51
- def usage(letter_map)
52
- databases = letter_map.keys.sort.join("")
53
-
54
- puts "Usage: #{$0} [-d [#{databases}]] [-g|-v|-m|-c|-x|-y|-j|-t] [-os <sep>] [-f <file>] [-r#|-w#|-v] [-l <count>] <ip|domain|cidr>"
55
- puts " -d#{databases} uses all of the available passive dns databases"
56
- letter_map.keys.sort.each do |l|
57
- puts " -d#{l} use #{letter_map[l][0]}"
58
- end
59
- puts " -dvt uses VirusTotal and TCPIPUtils (for example)"
60
- puts ""
61
- puts " -g outputs a link-nodal GDF visualization definition"
62
- puts " -v outputs a link-nodal graphviz visualization definition"
63
- puts " -m output a link-nodal graphml visualization definition"
64
- puts " -c outputs CSV"
65
- puts " -x outputs XML"
66
- puts " -y outputs YAML"
67
- puts " -j outputs JSON"
68
- puts " -t outputs ASCII text (default)"
69
- puts " -s <sep> specifies a field separator for text output, default is tab"
70
- puts ""
71
- puts " -f[file] specifies a sqlite3 database used to read the current state - useful for large result sets and generating graphs of previous runs."
72
- puts " -r# specifies the levels of recursion to pull. **WARNING** This is quite taxing on the pDNS servers, so use judiciously (never more than 3 or so) or find yourself blocked!"
73
- puts " -w# specifies the amount of time to wait, in seconds, between queries (Default: 0)"
74
- puts " -v outputs debugging information"
75
- puts " -l <count> limits the number of records returned per passive dns database queried."
76
- exit
77
- end
78
-
79
- opts = GetoptLong.new(
80
- [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
81
- [ '--debug', '-z', GetoptLong::NO_ARGUMENT ],
82
- [ '--database', '-d', GetoptLong::REQUIRED_ARGUMENT ],
83
-
84
- [ '--gdf', '-g', GetoptLong::NO_ARGUMENT ],
85
- [ '--graphviz', '-v', GetoptLong::NO_ARGUMENT ],
86
- [ '--graphml', '-m', GetoptLong::NO_ARGUMENT ],
87
- [ '--csv', '-c', GetoptLong::NO_ARGUMENT ],
88
- [ '--xml', '-x', GetoptLong::NO_ARGUMENT ],
89
- [ '--yaml', '-y', GetoptLong::NO_ARGUMENT ],
90
- [ '--json', '-j', GetoptLong::NO_ARGUMENT ],
91
- [ '--text', '-t', GetoptLong::NO_ARGUMENT ],
92
- [ '--sep', '-s', GetoptLong::REQUIRED_ARGUMENT ],
93
-
94
- [ '--sqlite3', '-f', GetoptLong::REQUIRED_ARGUMENT ],
95
- [ '--recurse', '-r', GetoptLong::REQUIRED_ARGUMENT ],
96
- [ '--wait', '-w', GetoptLong::REQUIRED_ARGUMENT ],
97
- [ '--limit', '-l', GetoptLong::REQUIRED_ARGUMENT ]
98
- )
99
-
100
- letter_map = {}
101
- PassiveDNS.constants.each do |const|
102
- if PassiveDNS.const_get(const).is_a?(Class) and PassiveDNS.const_get(const).superclass == PassiveDNS::PassiveDB
103
- letter_map[PassiveDNS.const_get(const).option_letter] = [PassiveDNS.const_get(const).name, PassiveDNS.const_get(const).config_section_name]
104
- end
105
- end
106
-
107
- # sets the default search methods
108
- pdnsdbs = []
109
- format = "text"
110
- sep = "\t"
111
- recursedepth = 1
112
- wait = 0
113
- res = nil
114
- debug = false
115
- sqlitedb = nil
116
- limit = nil
117
-
118
- opts.each do |opt, arg|
119
- case opt
120
- when '--help'
121
- usage(letter_map)
122
- when '--debug'
123
- debug = true
124
- when '--database'
125
- arg.split(//).each do |c|
126
- if c == ','
127
- next
128
- elsif letter_map[c]
129
- pdnsdbs << letter_map[c][1]
130
- else
131
- $stderr.puts "ERROR: Unknown passive DNS database identifier: #{c}."
132
- usage(letter_map)
133
- end
134
- end
135
- when '--gdf'
136
- format = 'gdf'
137
- when '--graphviz'
138
- format = 'graphviz'
139
- when '--graphml'
140
- format = 'graphml'
141
- when '--csv'
142
- format = 'text'
143
- sep = ','
144
- when '--yaml'
145
- format = 'yaml'
146
- when '--xml'
147
- format = 'xml'
148
- when '--json'
149
- format = 'json'
150
- when '--text'
151
- format = 'text'
152
- when '--sep'
153
- sep = arg
154
- when '--recurse'
155
- recursedepth = arg.to_i
156
- if recursedepth > 3
157
- $stderr.puts "WARNING: a recursedepth of > 3 can be abusive, please reconsider: sleeping 60 seconds for sense to come to you (hint: hit CTRL-C)"
158
- sleep 60
159
- end
160
- when '--wait'
161
- wait = arg.to_i
162
- when '--sqlite3'
163
- sqlitedb = arg
164
- when '--limit'
165
- limit = arg.to_i
166
- else
167
- usage(letter_map)
168
- end
169
- end
170
-
171
- if pdnsdbs.length == 0
172
- pdnsdbs << "certee"
173
- end
174
-
175
- if pdnsdbs.index("bfk") and recursedepth > 1 and wait < 60
176
- wait = 60
177
- $stderr.puts "Enforcing a minimal 60 second wait when using BFK for recursive crawling"
178
- end
179
-
180
- if debug
181
- $stderr.puts "Using the following databases: #{pdnsdbs.join(", ")}"
182
- $stderr.puts "Recursions: #{recursedepth}, Wait time: #{wait}, Limit: #{limit or 'none'}"
183
- if format == "text" or format == "csv"
184
- $stderr.puts "Output format: #{format} (sep=\"#{sep}\")"
185
- else
186
- $stderr.puts "Output format: #{format}"
187
- end
188
- if ENV['http_proxy']
189
- $stderr.puts "Using proxy settings: http_proxy=#{ENV['http_proxy']}, https_proxy=#{ENV['https_proxy']}"
190
- end
191
- end
192
-
193
- state = nil
194
- if sqlitedb
195
- state = PassiveDNS::PDNSToolStateDB.new(sqlitedb)
196
- else
197
- state = PassiveDNS::PDNSToolState.new
198
- end
199
- state.debug = true if debug
200
-
201
- pdnsclient = PassiveDNS::Client.new(pdnsdbs)
202
- pdnsclient.debug = debug
203
-
204
- if ARGV.length > 0
205
- ARGV.each do |arg|
206
- state.add_query(arg,'pending',0)
207
- end
208
- else
209
- $stdin.each_line do |l|
210
- state.add_query(l.chomp,'pending',0)
211
- end
212
- end
213
- pdnslookup(state,pdnsclient,recursedepth,wait,debug,limit)
214
- printresults(state,format,sep)
5
+ puts PassiveDNS::CLI.run(ARGV)
@@ -3,15 +3,18 @@ require "passivedns/client/version"
3
3
  # This code is released under the LGPL: http://www.gnu.org/licenses/lgpl-3.0.txt
4
4
  # Please note that use of any passive dns database is subject to the terms of use of that passive dns database. Use of this script in violation of their terms is not encouraged in any way. Also, please do not add any obfuscation to try to work around their terms of service. If you need special services, ask the providers for help/permission.
5
5
  # Remember, these passive DNS operators are my friends. I don't want to have a row with them because some asshat used this library to abuse them.
6
- require 'passivedns/client/passivedb.rb'
7
- require 'passivedns/client/bfk.rb'
8
- require 'passivedns/client/certee.rb'
9
- require 'passivedns/client/dnsdb.rb'
10
- require 'passivedns/client/virustotal.rb'
11
- require 'passivedns/client/tcpiputils.rb'
12
- require 'passivedns/client/cn360.rb'
13
- require 'passivedns/client/state.rb'
14
- require 'passivedns/client/mnemonic.rb'
6
+ require 'passivedns/client/state'
7
+ require 'passivedns/client/passivedb'
8
+
9
+ require 'passivedns/client/bfk'
10
+ require 'passivedns/client/circl'
11
+ require 'passivedns/client/cn360'
12
+ require 'passivedns/client/dnsdb'
13
+ require 'passivedns/client/mnemonic'
14
+ require 'passivedns/client/passivetotal'
15
+ require 'passivedns/client/tcpiputils'
16
+ require 'passivedns/client/virustotal'
17
+
15
18
  require 'configparser'
16
19
  require 'pp'
17
20
 
@@ -20,7 +23,7 @@ module PassiveDNS
20
23
  class PDNSResult < Struct.new(:source, :response_time, :query, :answer, :rrtype, :ttl, :firstseen, :lastseen, :count); end
21
24
 
22
25
  class Client
23
- def initialize(pdns=['bfk','certee','dnsdb','virustotal','tcpiputils','cn360','mnemonic'], configfile="#{ENV['HOME']}/.passivedns-client")
26
+ def initialize(pdns=['bfk','dnsdb','virustotal','tcpiputils','cn360','mnemonic','passivetotal','CIRCL'], configfile="#{ENV['HOME']}/.passivedns-client")
24
27
  cp = ConfigParser.new(configfile)
25
28
  # this creates a map of all the PassiveDNS provider names and their classes
26
29
  class_map = {}
@@ -0,0 +1,80 @@
1
+ # DESCRIPTION: Module to query PassiveTotal's passive DNS repository
2
+
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'openssl'
6
+ require 'pp'
7
+
8
+ module PassiveDNS
9
+ class Circl < PassiveDB
10
+ # override
11
+ def self.name
12
+ "CIRCL"
13
+ end
14
+ #override
15
+ def self.config_section_name
16
+ "CIRCL"
17
+ end
18
+ #override
19
+ def self.option_letter
20
+ "c"
21
+ end
22
+
23
+ attr_accessor :debug
24
+ def initialize(options={})
25
+ @debug = options[:debug] || true
26
+ @username = options["USERNAME"]
27
+ @password = options["PASSWORD"]
28
+ @auth_token = options["AUTH_TOKEN"]
29
+ @url = options["URL"] || "https://www.circl.lu/pdns/query"
30
+ end
31
+
32
+ def parse_json(page,query,response_time=0)
33
+ res = []
34
+ # need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
35
+ page.split(/\n/).each do |line|
36
+ row = JSON.parse(line)
37
+ res << PDNSResult.new(self.class.name,response_time,
38
+ row['rrname'], row['rdata'], row['rrtype'], 0,
39
+ row['time_first'], row['time_last'], row['count'])
40
+ end
41
+ res
42
+ rescue Exception => e
43
+ $stderr.puts "#{self.class.name} Exception: #{e}"
44
+ raise e
45
+ end
46
+
47
+ def lookup(label, limit=nil)
48
+ $stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
49
+ Timeout::timeout(240) {
50
+ url = @url+"/"+label
51
+ $stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
52
+ url = URI.parse url
53
+ http = Net::HTTP.new(url.host, url.port)
54
+ http.use_ssl = (url.scheme == 'https')
55
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
56
+ http.verify_depth = 5
57
+ request = Net::HTTP::Get.new(url.request_uri)
58
+ request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
59
+ if @username
60
+ request.basic_auth(@username, @password)
61
+ end
62
+ if @auth_token
63
+ request.add_field("Authorization", @auth_token)
64
+ end
65
+ t1 = Time.now
66
+ response = http.request(request)
67
+ t2 = Time.now
68
+ recs = parse_json(response.body, label, t2-t1)
69
+ if limit
70
+ recs[0,limit]
71
+ else
72
+ recs
73
+ end
74
+ }
75
+ rescue Timeout::Error => e
76
+ $stderr.puts "#{self.class.name} lookup timed out: #{label}"
77
+ end
78
+ end
79
+ CIRCL = PassiveDNS::Circl
80
+ end
@@ -0,0 +1,245 @@
1
+ require 'getoptlong'
2
+ require 'structformatter'
3
+ require 'getoptlong'
4
+ require 'yaml'
5
+
6
+ module PassiveDNS
7
+ class CLInterface
8
+ def self.parse_command_line(args)
9
+ opts = GetoptLong.new(
10
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
11
+ [ '--debug', '-z', GetoptLong::NO_ARGUMENT ],
12
+ [ '--database', '-d', GetoptLong::REQUIRED_ARGUMENT ],
13
+
14
+ [ '--gdf', '-g', GetoptLong::NO_ARGUMENT ],
15
+ [ '--graphviz', '-v', GetoptLong::NO_ARGUMENT ],
16
+ [ '--graphml', '-m', GetoptLong::NO_ARGUMENT ],
17
+ [ '--csv', '-c', GetoptLong::NO_ARGUMENT ],
18
+ [ '--xml', '-x', GetoptLong::NO_ARGUMENT ],
19
+ [ '--yaml', '-y', GetoptLong::NO_ARGUMENT ],
20
+ [ '--json', '-j', GetoptLong::NO_ARGUMENT ],
21
+ [ '--text', '-t', GetoptLong::NO_ARGUMENT ],
22
+ [ '--sep', '-s', GetoptLong::REQUIRED_ARGUMENT ],
23
+
24
+ [ '--sqlite3', '-f', GetoptLong::REQUIRED_ARGUMENT ],
25
+ [ '--recurse', '-r', GetoptLong::REQUIRED_ARGUMENT ],
26
+ [ '--wait', '-w', GetoptLong::REQUIRED_ARGUMENT ],
27
+ [ '--limit', '-l', GetoptLong::REQUIRED_ARGUMENT ]
28
+ )
29
+
30
+ letter_map = {}
31
+ PassiveDNS.constants.each do |const|
32
+ if PassiveDNS.const_get(const).is_a?(Class) and PassiveDNS.const_get(const).superclass == PassiveDNS::PassiveDB
33
+ letter_map[PassiveDNS.const_get(const).option_letter] = [PassiveDNS.const_get(const).name, PassiveDNS.const_get(const).config_section_name]
34
+ end
35
+ end
36
+
37
+ # sets the default search methods
38
+ options = {
39
+ :pdnsdbs => [],
40
+ :format => "text",
41
+ :sep => "\t",
42
+ :recursedepth => 1,
43
+ :wait => 0,
44
+ :res => nil,
45
+ :debug => false,
46
+ :sqlitedb => nil,
47
+ :limit => nil
48
+ }
49
+
50
+ opts.each do |opt, arg|
51
+ case opt
52
+ when '--help'
53
+ puts usage(letter_map)
54
+ exit
55
+ when '--debug'
56
+ options[:debug] = true
57
+ when '--database'
58
+ arg.split(//).each do |c|
59
+ if c == ','
60
+ next
61
+ elsif letter_map[c]
62
+ options[:pdnsdbs] << letter_map[c][1]
63
+ else
64
+ $stderr.puts "ERROR: Unknown passive DNS database identifier: #{c}."
65
+ usage(letter_map)
66
+ end
67
+ end
68
+ when '--gdf'
69
+ options[:format] = 'gdf'
70
+ when '--graphviz'
71
+ options[:format] = 'graphviz'
72
+ when '--graphml'
73
+ options[:format] = 'graphml'
74
+ when '--csv'
75
+ options[:format] = 'text'
76
+ options[:sep] = ','
77
+ when '--yaml'
78
+ options[:format] = 'yaml'
79
+ when '--xml'
80
+ options[:format] = 'xml'
81
+ when '--json'
82
+ options[:format] = 'json'
83
+ when '--text'
84
+ options[:format] = 'text'
85
+ when '--sep'
86
+ options[:sep] = arg
87
+ when '--recurse'
88
+ options[:recursedepth] = arg.to_i
89
+ if options[:recursedepth] > 3
90
+ $stderr.puts "WARNING: a recursedepth of > 3 can be abusive, please reconsider: sleeping 60 seconds for sense to come to you (hint: hit CTRL-C)"
91
+ sleep 60
92
+ end
93
+ when '--wait'
94
+ options[:wait] = arg.to_i
95
+ when '--sqlite3'
96
+ options[:sqlitedb] = arg
97
+ when '--limit'
98
+ otions[:limit] = arg.to_i
99
+ else
100
+ puts usage(letter_map)
101
+ exit
102
+ end
103
+ end
104
+
105
+ if options[:pdnsdbs].length == 0
106
+ options[:pdnsdbs] << "bfk"
107
+ end
108
+
109
+ if options[:pdnsdbs].index("bfk") and recursedepth > 1 and wait < 60
110
+ options[:wait] = 60
111
+ $stderr.puts "Enforcing a minimal 60 second wait when using BFK for recursive crawling"
112
+ end
113
+
114
+ if options[:debug]
115
+ $stderr.puts "Using the following databases: #{options[:pdnsdbs].join(", ")}"
116
+ $stderr.puts "Recursions: #{options[:recursedepth]}, Wait time: #{options[:wait]}, Limit: #{options[:limit] or 'none'}"
117
+ if options[:format] == "text" or options[:format] == "csv"
118
+ $stderr.puts "Output format: #{options[:format]} (sep=\"#{options[:sep]}\")"
119
+ else
120
+ $stderr.puts "Output format: #{options[:format]}"
121
+ end
122
+ if ENV['http_proxy']
123
+ $stderr.puts "Using proxy settings: http_proxy=#{ENV['http_proxy']}, https_proxy=#{ENV['https_proxy']}"
124
+ end
125
+ end
126
+
127
+ [options, args]
128
+ end
129
+
130
+ def self.usage(letter_map)
131
+ databases = letter_map.keys.sort.join("")
132
+ help_text = "\n"
133
+ help_text << "Usage: #{$0} [-d [#{databases}]] [-g|-v|-m|-c|-x|-y|-j|-t] [-os <sep>] [-f <file>] [-r#|-w#|-v] [-l <count>] <ip|domain|cidr>\n"
134
+ help_text << "Passive DNS Providers"
135
+ help_text << " -d#{databases} uses all of the available passive dns database\n"
136
+ letter_map.keys.sort.each do |l|
137
+ help_text << " -d#{l} use #{letter_map[l][0]}\n"
138
+ end
139
+ help_text << " -dvt uses VirusTotal and TCPIPUtils (for example)\n"
140
+ help_text << "\n"
141
+ help_text << "Output Formatting\n"
142
+ help_text << " -g link-nodal GDF visualization definition\n"
143
+ help_text << " -v link-nodal graphviz visualization definition\n"
144
+ help_text << " -m link-nodal graphml visualization definition\n"
145
+ help_text << " -c CSV\n"
146
+ help_text << " -x XML\n"
147
+ help_text << " -y YAML\n"
148
+ help_text << " -j JSON\n"
149
+ help_text << " -t ASCII text (default)\n"
150
+ help_text << " -s <sep> specifies a field separator for text output, default is tab\n"
151
+ help_text << "\n"
152
+ help_text << "State and Recusion\n"
153
+ help_text << " -f[file] specifies a sqlite3 database used to read the current state - useful for large result sets and generating graphs of previous runs.\n"
154
+ help_text << " -r# specifies the levels of recursion to pull. **WARNING** This is quite taxing on the pDNS servers, so use judiciously (never more than 3 or so) or find yourself blocked!\n"
155
+ help_text << " -w# specifies the amount of time to wait, in seconds, between queries (Default: 0)\n"
156
+ help_text << " -l <count> limits the number of records returned per passive dns database queried.\n"
157
+ help_text << "\n"
158
+ help_text << "Getting Help\n"
159
+ help_text << " -v debugging information\n"
160
+
161
+ help_text
162
+ end
163
+
164
+ def self.pdnslookup(state, pdnsclient, options)
165
+ recursedepth = options[:recursedepth]
166
+ wait = options[:wait]
167
+ debug = options[:debug]
168
+ limit = options[:limit]
169
+ puts "pdnslookup: #{state.level} #{recursedepth}" if debug
170
+ level = 0
171
+ while level < recursedepth
172
+ puts "pdnslookup: #{level} < #{recursedepth}" if debug
173
+ state.each_query(recursedepth) do |q|
174
+ rv = pdnsclient.query(q,limit)
175
+ if rv
176
+ rv.each do |r|
177
+ if ["A","AAAA","NS","CNAME","PTR"].index(r.rrtype)
178
+ puts "pdnslookup: #{r.to_s}" if debug
179
+ state.add_result(r)
180
+ end
181
+ end
182
+ else
183
+ state.update_query(rv,'failed')
184
+ end
185
+ sleep wait if level < recursedepth
186
+ end
187
+ level += 1
188
+ end
189
+ state
190
+ end
191
+
192
+ def self.results_to_s(state,options)
193
+ format = options[:format]
194
+ sep = options[:sep]
195
+ case format
196
+ when 'text'
197
+ PassiveDNS::PDNSResult.members.join(sep)+"\n"+state.to_s(sep)
198
+ when 'yaml'
199
+ state.to_yaml
200
+ when 'xml'
201
+ state.to_xml
202
+ when 'json'
203
+ state.to_json
204
+ when 'gdf'
205
+ state.to_gdf
206
+ when 'graphviz'
207
+ state.to_graphviz
208
+ when 'graphml'
209
+ state.to_graphml
210
+ end
211
+ end
212
+
213
+ def self.create_state(sqlitedb=nil)
214
+ state = nil
215
+ if sqlitedb
216
+ state = PassiveDNS::PDNSToolStateDB.new(sqlitedb)
217
+ else
218
+ state = PassiveDNS::PDNSToolState.new
219
+ end
220
+ end
221
+
222
+ def self.run(args)
223
+ options, items = parse_command_line(args)
224
+ state = create_state(options[:sqlitedb])
225
+ state.debug = options[:debug]
226
+
227
+ pdnsclient = PassiveDNS::Client.new(options[:pdnsdbs])
228
+ pdnsclient.debug = options[:debug]
229
+
230
+ if ARGV.length > 0
231
+ ARGV.each do |arg|
232
+ state.add_query(arg,'pending',0)
233
+ end
234
+ else
235
+ $stdin.each_line do |l|
236
+ state.add_query(l.chomp,'pending',0)
237
+ end
238
+ end
239
+ pdnslookup(state,pdnsclient,options)
240
+ results_to_s(state,options)
241
+ end
242
+ end
243
+ CLI = PassiveDNS::CLInterface
244
+ end
245
+
@@ -0,0 +1,77 @@
1
+ # DESCRIPTION: Module to query PassiveTotal's passive DNS repository
2
+
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'openssl'
6
+
7
+ module PassiveDNS
8
+ class PassiveTotal < PassiveDB
9
+ # override
10
+ def self.name
11
+ "PassiveTotal"
12
+ end
13
+ #override
14
+ def self.config_section_name
15
+ "passivetotal"
16
+ end
17
+ #override
18
+ def self.option_letter
19
+ "p"
20
+ end
21
+
22
+ attr_accessor :debug
23
+ def initialize(options={})
24
+ @debug = options[:debug] || false
25
+ @apikey = options["APIKEY"] || raise("#{self.class.name} requires an APIKEY")
26
+ @url = options["URL"] || "https://www.passivetotal.org/api/passive"
27
+ end
28
+
29
+ def parse_json(page,query,response_time=0)
30
+ res = []
31
+ # need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
32
+ data = JSON.parse(page)
33
+ if data['results']
34
+ query = data['results']['value']
35
+ data['results']['resolutions'].each do |row|
36
+ first_seen = row['firstSeen']
37
+ last_seen = row['lastSeen']
38
+ value = row['value']
39
+ source = row['source'].join(",")
40
+ res << PDNSResult.new(self.class.name+"/"+source,response_time,
41
+ query, value, "A", 0, first_seen, last_seen)
42
+ end
43
+ end
44
+ res
45
+ rescue Exception => e
46
+ $stderr.puts "#{self.class.name} Exception: #{e}"
47
+ raise e
48
+ end
49
+
50
+ def lookup(label, limit=nil)
51
+ $stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
52
+ Timeout::timeout(240) {
53
+ url = @url
54
+ $stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
55
+ url = URI.parse url
56
+ http = Net::HTTP.new(url.host, url.port)
57
+ http.use_ssl = (url.scheme == 'https')
58
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
59
+ http.verify_depth = 5
60
+ request = Net::HTTP::Post.new(url.request_uri)
61
+ request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
62
+ request.set_form_data({"apikey" => @apikey, "value" => label})
63
+ t1 = Time.now
64
+ response = http.request(request)
65
+ t2 = Time.now
66
+ recs = parse_json(response.body, label, t2-t1)
67
+ if limit
68
+ recs[0,limit]
69
+ else
70
+ recs
71
+ end
72
+ }
73
+ rescue Timeout::Error => e
74
+ $stderr.puts "#{self.class.name} lookup timed out: #{label}"
75
+ end
76
+ end
77
+ end
@@ -1,5 +1,5 @@
1
1
  module PassiveDNS
2
2
  class Client
3
- VERSION = "2.0.1"
3
+ VERSION = "2.0.2"
4
4
  end
5
5
  end
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency 'sqlite3', '>= 1.3.3'
23
23
  spec.add_runtime_dependency 'structformatter', '~> 0.0.1'
24
24
  spec.add_runtime_dependency 'configparser', '~> 0.1.3'
25
+ spec.add_development_dependency "minitest", "~> 5.5"
25
26
  spec.add_development_dependency "bundler", "~> 1.3"
26
27
  spec.add_development_dependency "rake"
27
28
 
data/test/helper.rb CHANGED
@@ -1,2 +1,6 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
+ require 'minitest/test'
3
+ require 'minitest/unit'
4
+ require 'minitest/pride'
5
+ include MiniTest::Assertions
2
6
  require File.expand_path('../../lib/passivedns/client.rb', __FILE__)
@@ -9,7 +9,7 @@ end
9
9
  require_relative 'helper'
10
10
  require 'configparser'
11
11
 
12
- class TestPassiveDnsQuery < Test::Unit::TestCase
12
+ class TestPassiveDnsQuery < Minitest::Test
13
13
 
14
14
  def setup
15
15
  configfile="#{ENV['HOME']}/.passivedns-client"
@@ -23,194 +23,227 @@ class TestPassiveDnsQuery < Test::Unit::TestCase
23
23
  end
24
24
 
25
25
  def test_instantiate_Nonexisting_Client
26
- assert_raise RuntimeError do
26
+ assert_raises RuntimeError do
27
27
  PassiveDNS::Client.new(['doesnotexist'])
28
28
  end
29
29
  end
30
30
 
31
31
  def test_instantiate_All_Clients
32
- assert_nothing_raised do
33
- PassiveDNS::Client.new()
34
- end
32
+ PassiveDNS::Client.new()
35
33
  end
36
34
 
37
35
  def test_instantiate_Passive_DNS_State
38
- assert_not_nil(PassiveDNS::PDNSToolState.new)
36
+ refute_nil(PassiveDNS::PDNSToolState.new)
39
37
  end
40
38
 
41
39
  def test_instantiate_Passive_DNS_State_database
42
40
  if File.exists?("test/test.sqlite3")
43
41
  File.unlink("test/test.sqlite3")
44
42
  end
45
- assert_not_nil(PassiveDNS::PDNSToolStateDB.new("test/test.sqlite3"))
43
+ refute_nil(PassiveDNS::PDNSToolStateDB.new("test/test.sqlite3"))
46
44
  if File.exists?("test/test.sqlite3")
47
45
  File.unlink("test/test.sqlite3")
48
46
  end
49
47
  end
50
48
 
51
49
  def test_BFK
52
- assert_nothing_raised do
53
- PassiveDNS::Client.new(['bfk'])
54
- end
50
+ PassiveDNS::Client.new(['bfk'])
55
51
  d = PassiveDNS::BFK.new(@cp['bfk'] || {})
56
- assert_not_nil(d)
52
+ refute_nil(d)
57
53
  rows = d.lookup("example.org",3)
58
- assert_not_nil(rows)
59
- assert_not_nil(rows.to_s)
60
- assert_not_nil(rows.to_xml)
61
- assert_not_nil(rows.to_json)
62
- assert_not_nil(rows.to_yaml)
54
+ refute_nil(rows)
55
+ refute_nil(rows.to_s)
56
+ refute_nil(rows.to_xml)
57
+ refute_nil(rows.to_json)
58
+ refute_nil(rows.to_yaml)
63
59
  assert_equal(3, rows.length)
64
60
  rows = d.lookup("8.8.8.8")
65
- assert_not_nil(rows)
66
- assert_not_nil(rows.to_s)
67
- assert_not_nil(rows.to_xml)
68
- assert_not_nil(rows.to_json)
69
- assert_not_nil(rows.to_yaml)
61
+ refute_nil(rows)
62
+ refute_nil(rows.to_s)
63
+ refute_nil(rows.to_xml)
64
+ refute_nil(rows.to_json)
65
+ refute_nil(rows.to_yaml)
70
66
  end
71
67
 
72
68
  def test_CERTEE
73
- assert(false, "CERTEE is still offline")
74
- assert_nothing_raised do
75
- PassiveDNS::Client.new(['certee'])
76
- end
69
+ PassiveDNS::Client.new(['certee'])
77
70
  d = PassiveDNS::CERTEE.new(@cp['certee'] || {})
78
- assert_not_nil(d)
71
+ refute_nil(d)
79
72
  rows = d.lookup("sim.cert.ee",3)
80
- assert_not_nil(rows)
81
- assert_not_nil(rows.to_s)
82
- assert_not_nil(rows.to_xml)
83
- assert_not_nil(rows.to_json)
84
- assert_not_nil(rows.to_yaml)
73
+ refute_nil(rows)
74
+ refute_nil(rows.to_s)
75
+ refute_nil(rows.to_xml)
76
+ refute_nil(rows.to_json)
77
+ refute_nil(rows.to_yaml)
85
78
  assert_equal(3, rows.length)
86
79
  rows = d.lookup("8.8.8.8")
87
- assert_not_nil(rows)
88
- assert_not_nil(rows.to_s)
89
- assert_not_nil(rows.to_xml)
90
- assert_not_nil(rows.to_json)
91
- assert_not_nil(rows.to_yaml)
80
+ refute_nil(rows)
81
+ refute_nil(rows.to_s)
82
+ refute_nil(rows.to_xml)
83
+ refute_nil(rows.to_json)
84
+ refute_nil(rows.to_yaml)
92
85
  end
93
86
 
94
87
  def test_DNSDB
95
- assert_nothing_raised do
96
- PassiveDNS::Client.new(['dnsdb'])
97
- end
88
+ PassiveDNS::Client.new(['dnsdb'])
98
89
  d = PassiveDNS::DNSDB.new(@cp['dnsdb'] || {})
99
- assert_not_nil(d)
90
+ refute_nil(d)
100
91
  rows = d.lookup("example.org",3)
101
- assert_not_nil(rows)
102
- assert_not_nil(rows.to_s)
103
- assert_not_nil(rows.to_xml)
104
- assert_not_nil(rows.to_json)
105
- assert_not_nil(rows.to_yaml)
92
+ refute_nil(rows)
93
+ refute_nil(rows.to_s)
94
+ refute_nil(rows.to_xml)
95
+ refute_nil(rows.to_json)
96
+ refute_nil(rows.to_yaml)
106
97
  assert_equal(3, rows.length) # this will fail since DNSDB has an off by one error
107
98
  rows = d.lookup("8.8.8.8")
108
- assert_not_nil(rows)
109
- assert_not_nil(rows.to_s)
110
- assert_not_nil(rows.to_xml)
111
- assert_not_nil(rows.to_json)
112
- assert_not_nil(rows.to_yaml)
99
+ refute_nil(rows)
100
+ refute_nil(rows.to_s)
101
+ refute_nil(rows.to_xml)
102
+ refute_nil(rows.to_json)
103
+ refute_nil(rows.to_yaml)
113
104
  end
114
105
 
115
106
  def test_VirusTotal
116
- assert_nothing_raised do
117
- PassiveDNS::Client.new(['virustotal'])
118
- end
107
+ PassiveDNS::Client.new(['virustotal'])
119
108
  d = PassiveDNS::VirusTotal.new(@cp['virustotal'] || {})
120
- assert_not_nil(d)
109
+ refute_nil(d)
121
110
  rows = d.lookup("google.com",3)
122
- assert_not_nil(rows)
123
- assert_not_nil(rows.to_s)
124
- assert_not_nil(rows.to_xml)
125
- assert_not_nil(rows.to_json)
126
- assert_not_nil(rows.to_yaml)
111
+ refute_nil(rows)
112
+ refute_nil(rows.to_s)
113
+ refute_nil(rows.to_xml)
114
+ refute_nil(rows.to_json)
115
+ refute_nil(rows.to_yaml)
127
116
  assert_equal(3, rows.length)
128
117
  rows = d.lookup("8.8.8.8")
129
- assert_not_nil(rows)
130
- assert_not_nil(rows.to_s)
131
- assert_not_nil(rows.to_xml)
132
- assert_not_nil(rows.to_json)
133
- assert_not_nil(rows.to_yaml)
118
+ refute_nil(rows)
119
+ refute_nil(rows.to_s)
120
+ refute_nil(rows.to_xml)
121
+ refute_nil(rows.to_json)
122
+ refute_nil(rows.to_yaml)
134
123
  end
135
124
 
136
125
  def test_TCPIPUtils
137
- assert_nothing_raised do
138
- PassiveDNS::Client.new(['tcpiputils'])
139
- end
126
+ PassiveDNS::Client.new(['tcpiputils'])
140
127
  d = PassiveDNS::TCPIPUtils.new(@cp['tcpiputils'] || {})
141
- assert_not_nil(d)
128
+ refute_nil(d)
142
129
  rows = d.lookup("example.org")
143
- assert_not_nil(rows)
144
- assert_not_nil(rows.to_s)
145
- assert_not_nil(rows.to_xml)
146
- assert_not_nil(rows.to_json)
147
- assert_not_nil(rows.to_yaml)
130
+ refute_nil(rows)
131
+ refute_nil(rows.to_s)
132
+ refute_nil(rows.to_xml)
133
+ refute_nil(rows.to_json)
134
+ refute_nil(rows.to_yaml)
148
135
  rows = d.lookup("example.org",3)
149
- assert_not_nil(rows)
150
- assert_not_nil(rows.to_s)
151
- assert_not_nil(rows.to_xml)
152
- assert_not_nil(rows.to_json)
153
- assert_not_nil(rows.to_yaml)
136
+ refute_nil(rows)
137
+ refute_nil(rows.to_s)
138
+ refute_nil(rows.to_xml)
139
+ refute_nil(rows.to_json)
140
+ refute_nil(rows.to_yaml)
154
141
  assert_equal(3, rows.length)
155
142
  rows = d.lookup("8.8.8.8")
156
- assert_not_nil(rows)
157
- assert_not_nil(rows.to_s)
158
- assert_not_nil(rows.to_xml)
159
- assert_not_nil(rows.to_json)
160
- assert_not_nil(rows.to_yaml)
143
+ refute_nil(rows)
144
+ refute_nil(rows.to_s)
145
+ refute_nil(rows.to_xml)
146
+ refute_nil(rows.to_json)
147
+ refute_nil(rows.to_yaml)
161
148
  end
162
149
 
163
150
  def test_cn360
164
- assert_nothing_raised do
165
- PassiveDNS::Client.new(['cn360'])
166
- end
151
+ PassiveDNS::Client.new(['cn360'])
167
152
  d = PassiveDNS::CN360.new(@cp['cn360'] || {})
168
- assert_not_nil(d)
153
+ refute_nil(d)
169
154
  rows = d.lookup("example.org")
170
- assert_not_nil(rows)
171
- assert_not_nil(rows.to_s)
172
- assert_not_nil(rows.to_xml)
173
- assert_not_nil(rows.to_json)
174
- assert_not_nil(rows.to_yaml)
155
+ refute_nil(rows)
156
+ refute_nil(rows.to_s)
157
+ refute_nil(rows.to_xml)
158
+ refute_nil(rows.to_json)
159
+ refute_nil(rows.to_yaml)
175
160
  rows = d.lookup("example.org",3)
176
- assert_not_nil(rows)
177
- assert_not_nil(rows.to_s)
178
- assert_not_nil(rows.to_xml)
179
- assert_not_nil(rows.to_json)
180
- assert_not_nil(rows.to_yaml)
161
+ refute_nil(rows)
162
+ refute_nil(rows.to_s)
163
+ refute_nil(rows.to_xml)
164
+ refute_nil(rows.to_json)
165
+ refute_nil(rows.to_yaml)
181
166
  assert_equal(3, rows.length)
182
167
  rows = d.lookup("8.8.8.8")
183
- assert_not_nil(rows)
184
- assert_not_nil(rows.to_s)
185
- assert_not_nil(rows.to_xml)
186
- assert_not_nil(rows.to_json)
187
- assert_not_nil(rows.to_yaml)
168
+ refute_nil(rows)
169
+ refute_nil(rows.to_s)
170
+ refute_nil(rows.to_xml)
171
+ refute_nil(rows.to_json)
172
+ refute_nil(rows.to_yaml)
188
173
  end
189
174
 
190
175
  def test_nmemonic
191
- assert_nothing_raised do
192
- PassiveDNS::Client.new(['mnemonic'])
193
- end
176
+ PassiveDNS::Client.new(['mnemonic'])
194
177
  d = PassiveDNS::Mnemonic.new(@cp['mnemonic'] || {})
195
- assert_not_nil(d)
178
+ refute_nil(d)
179
+ rows = d.lookup("example.org")
180
+ refute_nil(rows)
181
+ refute_nil(rows.to_s)
182
+ refute_nil(rows.to_xml)
183
+ refute_nil(rows.to_json)
184
+ refute_nil(rows.to_yaml)
185
+ rows = d.lookup("example.org",3)
186
+ refute_nil(rows)
187
+ refute_nil(rows.to_s)
188
+ refute_nil(rows.to_xml)
189
+ refute_nil(rows.to_json)
190
+ refute_nil(rows.to_yaml)
191
+ assert_equal(3, rows.length)
192
+ rows = d.lookup("8.8.8.8")
193
+ refute_nil(rows)
194
+ refute_nil(rows.to_s)
195
+ refute_nil(rows.to_xml)
196
+ refute_nil(rows.to_json)
197
+ refute_nil(rows.to_yaml)
198
+ end
199
+
200
+ def test_passivetotal
201
+ PassiveDNS::Client.new(['passivetotal'])
202
+ d = PassiveDNS::PassiveTotal.new(@cp['passivetotal'] || {})
203
+ refute_nil(d)
204
+ rows = d.lookup("example.org")
205
+ refute_nil(rows)
206
+ refute_nil(rows.to_s)
207
+ refute_nil(rows.to_xml)
208
+ refute_nil(rows.to_json)
209
+ refute_nil(rows.to_yaml)
210
+ rows = d.lookup("example.org",3)
211
+ refute_nil(rows)
212
+ refute_nil(rows.to_s)
213
+ refute_nil(rows.to_xml)
214
+ refute_nil(rows.to_json)
215
+ refute_nil(rows.to_yaml)
216
+ assert_equal(3, rows.length)
217
+ rows = d.lookup("8.8.8.8")
218
+ refute_nil(rows)
219
+ refute_nil(rows.to_s)
220
+ refute_nil(rows.to_xml)
221
+ refute_nil(rows.to_json)
222
+ refute_nil(rows.to_yaml)
223
+ end
224
+
225
+ def test_circl
226
+ PassiveDNS::Client.new(['CIRCL'])
227
+ d = PassiveDNS::CIRCL.new(@cp['CIRCL'] || {})
228
+ refute_nil(d)
196
229
  rows = d.lookup("example.org")
197
- assert_not_nil(rows)
198
- assert_not_nil(rows.to_s)
199
- assert_not_nil(rows.to_xml)
200
- assert_not_nil(rows.to_json)
201
- assert_not_nil(rows.to_yaml)
230
+ refute_nil(rows)
231
+ refute_nil(rows.to_s)
232
+ refute_nil(rows.to_xml)
233
+ refute_nil(rows.to_json)
234
+ refute_nil(rows.to_yaml)
202
235
  rows = d.lookup("example.org",3)
203
- assert_not_nil(rows)
204
- assert_not_nil(rows.to_s)
205
- assert_not_nil(rows.to_xml)
206
- assert_not_nil(rows.to_json)
207
- assert_not_nil(rows.to_yaml)
236
+ refute_nil(rows)
237
+ refute_nil(rows.to_s)
238
+ refute_nil(rows.to_xml)
239
+ refute_nil(rows.to_json)
240
+ refute_nil(rows.to_yaml)
208
241
  assert_equal(3, rows.length)
209
242
  rows = d.lookup("8.8.8.8")
210
- assert_not_nil(rows)
211
- assert_not_nil(rows.to_s)
212
- assert_not_nil(rows.to_xml)
213
- assert_not_nil(rows.to_json)
214
- assert_not_nil(rows.to_yaml)
243
+ refute_nil(rows)
244
+ refute_nil(rows.to_s)
245
+ refute_nil(rows.to_xml)
246
+ refute_nil(rows.to_json)
247
+ refute_nil(rows.to_yaml)
215
248
  end
216
249
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passivedns-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - chrislee35
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-20 00:00:00.000000000 Z
11
+ date: 2015-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.1.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.5'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: bundler
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -112,11 +126,13 @@ files:
112
126
  - bin/pdnstool
113
127
  - lib/passivedns/client.rb
114
128
  - lib/passivedns/client/bfk.rb
115
- - lib/passivedns/client/certee.rb
129
+ - lib/passivedns/client/circl.rb
130
+ - lib/passivedns/client/cli.rb
116
131
  - lib/passivedns/client/cn360.rb
117
132
  - lib/passivedns/client/dnsdb.rb
118
133
  - lib/passivedns/client/mnemonic.rb
119
134
  - lib/passivedns/client/passivedb.rb
135
+ - lib/passivedns/client/passivetotal.rb
120
136
  - lib/passivedns/client/state.rb
121
137
  - lib/passivedns/client/tcpiputils.rb
122
138
  - lib/passivedns/client/version.rb
@@ -1,62 +0,0 @@
1
- require 'socket'
2
- require_relative 'passivedb'
3
-
4
- module PassiveDNS
5
- class CERTEE < PassiveDB
6
- # override
7
- def self.name
8
- "CERTEE"
9
- end
10
- #override
11
- def self.config_section_name
12
- "certee"
13
- end
14
- #override
15
- def self.option_letter
16
- "e"
17
- end
18
-
19
- attr_accessor :debug
20
- def initialize(options={})
21
- @debug = options[:debug] || false
22
- @host = options["HOST"] || "sim.cert.ee"
23
- @port = options["PORT"].to_i || 43
24
- end
25
-
26
- # override
27
- def lookup(label, limit=nil)
28
- $stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
29
- recs = []
30
- begin
31
- t1 = Time.now
32
- s = TCPSocket.new(@host,@port)
33
- s.puts(label)
34
- s.each_line do |l|
35
- if l =~ /Traceback \(most recent call last\):/
36
- # there is a bug in the CERTEE lookup tool
37
- raise "#{self.class.name} is currently offline"
38
- end
39
- (lbl,ans,fs,ls) = l.chomp.split(/\t/)
40
- rrtype = 'A'
41
- if ans =~ /^\d+\.\d+\.\d+\.\d+$/
42
- rrtype = 'A'
43
- elsif ans =~ /^ns/
44
- rrtype = 'NS'
45
- else
46
- rrtype = 'CNAME'
47
- end
48
- t2 = Time.now
49
- recs << PDNSResult.new(self.class.name,t2-t1,lbl,ans,rrtype,0,Time.parse(fs).utc.strftime("%Y-%m-%dT%H:%M:%SZ"),Time.parse(ls).utc.strftime("%Y-%m-%dT%H:%M:%SZ"))
50
- end
51
- rescue SocketError => e
52
- $stderr.puts e
53
- end
54
- return nil unless recs.length > 0
55
- if limit
56
- recs = recs[0,limit]
57
- else
58
- recs
59
- end
60
- end
61
- end
62
- end