nicinfo 0.2.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/bin/nicinfo +22 -0
  3. data/lib/autnum.rb +90 -0
  4. data/lib/bootstrap.rb +198 -0
  5. data/lib/bsfiles/asn.json +2326 -0
  6. data/lib/bsfiles/dns.json +25 -0
  7. data/lib/bsfiles/entity.json +52 -0
  8. data/lib/bsfiles/ipv4.json +244 -0
  9. data/lib/bsfiles/ipv6.json +97 -0
  10. data/lib/cache.rb +141 -0
  11. data/lib/common_json.rb +263 -0
  12. data/lib/common_names.rb +49 -0
  13. data/lib/config.rb +260 -0
  14. data/lib/constants.rb +113 -0
  15. data/lib/data_tree.rb +205 -0
  16. data/lib/demo/autnum.json +228 -0
  17. data/lib/demo/domain-dnr.json +695 -0
  18. data/lib/demo/domain-rir.json +569 -0
  19. data/lib/demo/domains.json +625 -0
  20. data/lib/demo/entities.json +545 -0
  21. data/lib/demo/entity-dnr.json +143 -0
  22. data/lib/demo/entity-rir.json +394 -0
  23. data/lib/demo/error-code.json +31 -0
  24. data/lib/demo/help.json +58 -0
  25. data/lib/demo/ip.json +306 -0
  26. data/lib/demo/nameservers.json +434 -0
  27. data/lib/demo/ns-simple.json +210 -0
  28. data/lib/demo/ns-very-simple.json +41 -0
  29. data/lib/demo/ns.json +63 -0
  30. data/lib/demo/simple-ip.json +41 -0
  31. data/lib/demo/simple.json +13 -0
  32. data/lib/domain.rb +203 -0
  33. data/lib/ds_data.rb +70 -0
  34. data/lib/entity.rb +372 -0
  35. data/lib/enum.rb +47 -0
  36. data/lib/error_code.rb +56 -0
  37. data/lib/female-first-names.txt +4275 -0
  38. data/lib/ip.rb +86 -0
  39. data/lib/key_data.rb +70 -0
  40. data/lib/last-names.txt +88799 -0
  41. data/lib/male-first-names.txt +1219 -0
  42. data/lib/nicinfo_logger.rb +370 -0
  43. data/lib/nicinfo_main.rb +1013 -0
  44. data/lib/notices.rb +110 -0
  45. data/lib/ns.rb +108 -0
  46. data/lib/utils.rb +189 -0
  47. metadata +90 -0
@@ -0,0 +1,1013 @@
1
+ # Copyright (C) 2011,2012,2013,2014 American Registry for Internet Numbers
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
13
+ # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+
16
+ require 'optparse'
17
+ require 'net/http'
18
+ require 'net/https'
19
+ require 'uri'
20
+ require 'config'
21
+ require 'constants'
22
+ require 'cache'
23
+ require 'enum'
24
+ require 'common_names'
25
+ require 'bootstrap'
26
+ require 'notices'
27
+ require 'entity'
28
+ require 'ip'
29
+ require 'ns'
30
+ require 'domain'
31
+ require 'autnum'
32
+ require 'error_code'
33
+ require 'ipaddr'
34
+ require 'data_tree'
35
+ begin
36
+ require 'json'
37
+ rescue LoadError
38
+ require 'rubygems'
39
+ require 'json'
40
+ end
41
+
42
+ module NicInfo
43
+
44
+ class QueryType < NicInfo::Enum
45
+
46
+ QueryType.add_item :BY_IP4_ADDR, "IP4ADDR"
47
+ QueryType.add_item :BY_IP6_ADDR, "IP6ADDR"
48
+ QueryType.add_item :BY_IP4_CIDR, "IP4CIDR"
49
+ QueryType.add_item :BY_IP6_CIDR, "IP6CIDR"
50
+ QueryType.add_item :BY_IP, "IP"
51
+ QueryType.add_item :BY_AS_NUMBER, "ASNUMBER"
52
+ QueryType.add_item :BY_DOMAIN, "DOMAIN"
53
+ QueryType.add_item :BY_RESULT, "RESULT"
54
+ QueryType.add_item :BY_ENTITY_HANDLE, "ENTITYHANDLE"
55
+ QueryType.add_item :BY_NAMESERVER, "NAMESERVER"
56
+ QueryType.add_item :SRCH_ENTITY_BY_NAME, "ESBYNAME"
57
+ QueryType.add_item :SRCH_DOMAINS, "DOMAINS"
58
+ QueryType.add_item :SRCH_DOMAIN_BY_NAME, "DSBYNAME"
59
+ QueryType.add_item :SRCH_DOMAIN_BY_NSNAME, "DSBYNSNAME"
60
+ QueryType.add_item :SRCH_DOMAIN_BY_NSIP, "DSBYNSIP"
61
+ QueryType.add_item :SRCH_NS, "NAMESERVERS"
62
+ QueryType.add_item :SRCH_NS_BY_NAME, "NSBYNAME"
63
+ QueryType.add_item :SRCH_NS_BY_IP, "NSBYIP"
64
+ QueryType.add_item :BY_SERVER_HELP, "HELP"
65
+ QueryType.add_item :BY_URL, "URL"
66
+
67
+ end
68
+
69
+ # The main class for the nicinfo command.
70
+ class Main
71
+
72
+ def initialize args, config = nil
73
+
74
+ if config
75
+ @config = config
76
+ else
77
+ @config = NicInfo::Config.new(NicInfo::Config::formulate_app_data_dir())
78
+ end
79
+
80
+ @config.options.require_query = true
81
+
82
+ @opts = OptionParser.new do |opts|
83
+
84
+ opts.banner = "Usage: nicinfo [options] QUERY_VALUE"
85
+ opts.version = NicInfo::VERSION_LABEL
86
+
87
+ opts.separator ""
88
+ opts.separator "Query Options:"
89
+
90
+ opts.on("-t", "--type TYPE",
91
+ "Specify type of the query value.",
92
+ " ip4addr - IPv4 address",
93
+ " ip6addr - IPv6 address",
94
+ " ip4cidr - IPv4 cidr block",
95
+ " ip6cidr - IPv6 cidr block",
96
+ " asnumber - autonomous system number",
97
+ " domain - domain name",
98
+ " entityhandle - handle or id of a contact, organization, registrar or other entity",
99
+ " nameserver - fully qualified domain name of a nameserver",
100
+ " result - result from a previous query",
101
+ " esbyname - entity search by name",
102
+ " dsbyname - domain search by name",
103
+ " dsbynsname - domain search by nameserver name",
104
+ " dsbynsip - domain search by nameserver IP address",
105
+ " nsbyname - nameserver search by nameserver name",
106
+ " nsbyip - nameserver search by IP address",
107
+ " url - RDAP URL",
108
+ " help - server help") do |type|
109
+ uptype = type.upcase
110
+ raise OptionParser::InvalidArgument, type.to_s unless QueryType.has_value?(uptype)
111
+ @config.options.query_type = uptype
112
+ @config.options.require_query = false if uptype == "HELP"
113
+ end
114
+
115
+ opts.on("-r", "--reverse",
116
+ "Creates a reverse DNS name from an IP address. ") do |reverse|
117
+ @config.options.reverse_ip = true
118
+ end
119
+
120
+ opts.on("-b", "--base (or bootstrap) URL",
121
+ "The base URL of the RDAP Service.",
122
+ "When set, the internal bootstrap is bypassed.") do |url|
123
+ @config.config[ NicInfo::BOOTSTRAP][ NicInfo::BOOTSTRAP_URL ] = url
124
+ end
125
+
126
+ opts.separator ""
127
+ opts.separator "Cache Options:"
128
+
129
+ opts.on("--cache-expiry SECONDS",
130
+ "Age in seconds of items in the cache to be considered expired.") do |s|
131
+ @config.config[ NicInfo::CACHE ][ NicInfo::CACHE_EXPIRY ] = s
132
+ end
133
+
134
+ opts.on("--cache YES|NO|TRUE|FALSE",
135
+ "Controls if the cache is used or not.") do |cc|
136
+ @config.config[ NicInfo::CACHE ][ NicInfo::USE_CACHE ] = false if cc =~ /no|false/i
137
+ @config.config[ NicInfo::CACHE ][ NicInfo::USE_CACHE ] = true if cc =~ /yes|true/i
138
+ raise OptionParser::InvalidArgument, cc.to_s unless cc =~ /yes|no|true|false/i
139
+ end
140
+
141
+ opts.on("--empty-cache",
142
+ "Empties the cache of all files regardless of eviction policy.") do |cc|
143
+ @config.options.empty_cache = true
144
+ @config.options.require_query = false
145
+ end
146
+
147
+ opts.on("--demo",
148
+ "Populates the cache with demonstration results.") do |cc|
149
+ @config.options.demo = true
150
+ @config.options.require_query = false
151
+ end
152
+
153
+ opts.separator ""
154
+ opts.separator "Output Options:"
155
+
156
+ opts.on( "--messages MESSAGE_LEVEL",
157
+ "Specify the message level",
158
+ " none - no messages are to be output",
159
+ " some - some messages but not all",
160
+ " all - all messages to be outupt" ) do |m|
161
+ @config.logger.message_level = m.to_s.upcase
162
+ begin
163
+ @config.logger.validate_message_level
164
+ rescue
165
+ raise OptionParser::InvalidArgument, m.to_s
166
+ end
167
+ end
168
+
169
+ opts.on( "--messages-out FILE",
170
+ "FILE where messages will be written." ) do |f|
171
+ @config.logger.messages_out = File.open( f, "w+" )
172
+ end
173
+
174
+ opts.on( "--data DATA_AMOUNT",
175
+ "Specify the amount of data",
176
+ " terse - enough data to identify the object",
177
+ " normal - normal view of data on objects",
178
+ " extra - all data about the object" ) do |d|
179
+ @config.logger.data_amount = d.to_s.upcase
180
+ begin
181
+ @config.logger.validate_data_amount
182
+ rescue
183
+ raise OptionParser::InvalidArgument, d.to_s
184
+ end
185
+ end
186
+
187
+ opts.on( "--data-out FILE",
188
+ "FILE where data will be written." ) do |f|
189
+ @config.logger.data_out = File.open( f, "w+" )
190
+ end
191
+
192
+ opts.on( "--pager YES|NO|TRUE|FALSE",
193
+ "Turns the pager on and off." ) do |pager|
194
+ @config.logger.pager = false if pager =~ /no|false/i
195
+ @config.logger.pager = true if pager =~ /yes|true/i
196
+ raise OptionParser::InvalidArgument, pager.to_s unless pager =~ /yes|no|true|false/i
197
+ end
198
+
199
+ opts.on( "-V",
200
+ "Equivalent to --messages all and --data extra" ) do |v|
201
+ @config.logger.data_amount = NicInfo::DataAmount::EXTRA_DATA
202
+ @config.logger.message_level = NicInfo::MessageLevel::ALL_MESSAGES
203
+ end
204
+
205
+ opts.on( "-Q",
206
+ "Equivalent to --messages none and --data extra and --pager false" ) do |q|
207
+ @config.logger.data_amount = NicInfo::DataAmount::EXTRA_DATA
208
+ @config.logger.message_level = NicInfo::MessageLevel::NO_MESSAGES
209
+ @config.logger.pager = false
210
+ end
211
+
212
+ opts.on( "--json",
213
+ "Output raw JSON response." ) do |json|
214
+ @config.options.output_json = true
215
+ end
216
+
217
+ opts.on( "--jv VALUE",
218
+ "Outputs a specific JSON value." ) do |value|
219
+ unless @config.options.json_values
220
+ @config.options.json_values = Array.new
221
+ end
222
+ @config.options.json_values << value
223
+ end
224
+
225
+ opts.separator ""
226
+ opts.separator "General Options:"
227
+
228
+ opts.on( "-h", "--help",
229
+ "Show this message" ) do
230
+ @config.options.help = true
231
+ @config.options.require_query = false
232
+ end
233
+
234
+ opts.on( "--reset",
235
+ "Reset configuration to defaults" ) do
236
+ @config.options.reset_config = true
237
+ @config.options.require_query = false
238
+ end
239
+
240
+ opts.on( "--iana",
241
+ "Download RDAP bootstrap files from IANA" ) do
242
+ @config.options.get_iana_files = true
243
+ @config.options.require_query = false
244
+ end
245
+
246
+ end
247
+
248
+ begin
249
+ @opts.parse!(args)
250
+ rescue OptionParser::InvalidOption => e
251
+ puts e.message
252
+ puts "use -h for help"
253
+ exit
254
+ rescue OptionParser::InvalidArgument => e
255
+ puts e.message
256
+ puts "use -h for help"
257
+ exit
258
+ end
259
+ @config.options.argv = args
260
+
261
+ end
262
+
263
+ def make_rdap_url( base_url, resource_path )
264
+ unless base_url.end_with?("/")
265
+ base_url << "/"
266
+ end
267
+ base_url << resource_path
268
+ end
269
+
270
+ # Do an HTTP GET with the path.
271
+ def get url, try
272
+
273
+ data = @cache.get(url)
274
+ if data == nil
275
+
276
+ @config.logger.trace("Issuing GET for " + url)
277
+ uri = URI.parse( URI::encode( url ) )
278
+ req = Net::HTTP::Get.new(uri.request_uri)
279
+ req["User-Agent"] = NicInfo::VERSION_LABEL
280
+ req["Accept"] = NicInfo::RDAP_CONTENT_TYPE + ", " + NicInfo::JSON_CONTENT_TYPE
281
+ req["Connection"] = "close"
282
+ http = Net::HTTP.new( uri.host, uri.port )
283
+ if uri.scheme == "https"
284
+ http.use_ssl=true
285
+ http.verify_mode=OpenSSL::SSL::VERIFY_NONE
286
+ end
287
+ res = http.start do |http_req|
288
+ http_req.request(req)
289
+ end
290
+
291
+ case res
292
+ when Net::HTTPSuccess
293
+ content_type = res[ "content-type" ].downcase
294
+ unless content_type.include?(NicInfo::RDAP_CONTENT_TYPE) or content_type.include?(NicInfo::JSON_CONTENT_TYPE)
295
+ raise Net::HTTPServerException.new("Bad Content Type", res)
296
+ end
297
+ if content_type.include? NicInfo::JSON_CONTENT_TYPE
298
+ @config.conf_msgs << "Server responded with non-RDAP content type but it is JSON"
299
+ end
300
+ data = res.body
301
+ @cache.create_or_update(url, data)
302
+ else
303
+ if res.code == "301" or res.code == "302" or res.code == "303" or res.code == "307" or res.code == "308"
304
+ res.error! if try >= 5
305
+ location = res["location"]
306
+ return get( location, try + 1)
307
+ end
308
+ res.error!
309
+ end #end case
310
+
311
+ end #end if
312
+
313
+ return data
314
+
315
+ end #end def
316
+
317
+ # Do an HTTP GET of a file
318
+ def get_file_via_http url, file_name, try
319
+
320
+ @config.logger.trace("Downloading " + url + " to " + file_name )
321
+ uri = URI.parse( URI::encode( url ) )
322
+ req = Net::HTTP::Get.new(uri.request_uri)
323
+ req["User-Agent"] = NicInfo::VERSION_LABEL
324
+ req["Accept"] = NicInfo::JSON_CONTENT_TYPE
325
+ req["Connection"] = "close"
326
+ http = Net::HTTP.new( uri.host, uri.port )
327
+ if uri.scheme == "https"
328
+ http.use_ssl=true
329
+ http.verify_mode=OpenSSL::SSL::VERIFY_NONE
330
+ end
331
+ res = http.start do |http_req|
332
+ http_req.request(req)
333
+ end
334
+
335
+ case res
336
+ when Net::HTTPSuccess
337
+ File.write(file_name, res.body)
338
+ else
339
+ if res.code == "301" or res.code == "302" or res.code == "303" or res.code == "307" or res.code == "308"
340
+ res.error! if try >= 5
341
+ location = res["location"]
342
+ return get_file_via_http( location, file_name, try + 1)
343
+ end
344
+ res.error!
345
+ end
346
+ end
347
+
348
+
349
+ def run
350
+
351
+ @config.logger.run_pager
352
+ @config.logger.mesg(NicInfo::VERSION_LABEL)
353
+ @config.setup_workspace
354
+ @cache = Cache.new(@config)
355
+ @cache.clean if @config.config[ NicInfo::CACHE ][ NicInfo::CLEAN_CACHE ]
356
+
357
+ if @config.options.empty_cache
358
+ @cache.empty
359
+ end
360
+
361
+ if @config.options.get_iana_files
362
+ get_file_via_http( "http://data.iana.org/rdap/asn.json", File.join( @config.rdap_bootstrap_dir, "asn.json" ), 0 )
363
+ get_file_via_http( "http://data.iana.org/rdap/ipv4.json", File.join( @config.rdap_bootstrap_dir, "ipv4.json" ), 0 )
364
+ get_file_via_http( "http://data.iana.org/rdap/ipv6.json", File.join( @config.rdap_bootstrap_dir, "ipv6.json" ), 0 )
365
+ get_file_via_http( "http://data.iana.org/rdap/dns.json", File.join( @config.rdap_bootstrap_dir, "dns.json" ), 0 )
366
+ end
367
+
368
+ if @config.options.demo
369
+ @config.logger.mesg( "Populating cache with demonstration results" )
370
+ @config.logger.mesg( "Try the following demonstration queries:" )
371
+ demo_dir = File.join( File.dirname( __FILE__ ), NicInfo::DEMO_DIR )
372
+ demo_files = Dir::entries( demo_dir )
373
+ demo_files.each do |file|
374
+ df = File.join( demo_dir, file )
375
+ if File.file?( df )
376
+ demo_data = File.read( df )
377
+ json_data = JSON.load demo_data
378
+ demo_url = json_data[ NicInfo::NICINFO_DEMO_URL ]
379
+ demo_hint = json_data[ NicInfo::NICINFO_DEMO_HINT ]
380
+ @cache.create( demo_url, demo_data )
381
+ @config.logger.mesg( " " + demo_hint )
382
+ end
383
+ end
384
+ end
385
+
386
+ if @config.options.help
387
+ help()
388
+ end
389
+
390
+ if @config.options.argv == nil || @config.options.argv == [] && !@config.options.query_type
391
+ unless @config.options.require_query
392
+ exit
393
+ else
394
+ help
395
+ end
396
+ end
397
+
398
+ if @config.options.query_type == nil
399
+ @config.options.query_type = guess_query_value_type(@config.options.argv)
400
+ if (@config.options.query_type == QueryType::BY_IP4_ADDR ||
401
+ @config.options.query_type == QueryType::BY_IP6_ADDR ) && @config.options.reverse_ip == true
402
+ ip = IPAddr.new( @config.options.argv[ 0 ] )
403
+ @config.options.argv[ 0 ] = ip.reverse.split( "\." )[ 1..-1 ].join( "." ) if ip.ipv4?
404
+ @config.options.argv[ 0 ] = ip.reverse.split( "\." )[ 24..-1 ].join( "." ) if ip.ipv6?
405
+ @config.logger.mesg( "Query value changed to " + @config.options.argv[ 0 ] )
406
+ @config.options.query_type = QueryType::BY_DOMAIN
407
+ @config.options.externally_queriable = true
408
+ elsif @config.options.query_type == QueryType::BY_RESULT
409
+ data_tree = @config.load_as_yaml( NicInfo::LASTTREE_YAML )
410
+ node = data_tree.find_node( @config.options.argv[ 0 ] )
411
+ if node and node.rest_ref
412
+ @config.options.argv[ 0 ] = node.rest_ref
413
+ @config.options.url = true
414
+ if node.data_type
415
+ @config.options.query_type = node.data_type
416
+ @config.options.externally_queriable = false
417
+ elsif node.rest_ref
418
+ @config.options.query_type = get_query_type_from_url( node.rest_ref )
419
+ @config.options.externally_queriable = true
420
+ end
421
+ else
422
+ @config.logger.mesg( "#{@config.options.argv[0]} is not retrievable.")
423
+ exit
424
+ end
425
+ elsif @config.options.query_type == QueryType::BY_URL
426
+ @config.options.url = @config.options.argv[0]
427
+ @config.options.query_type = get_query_type_from_url( @config.options.url )
428
+ else
429
+ @config.options.externally_queriable = true
430
+ end
431
+ if @config.options.query_type == nil
432
+ @config.logger.mesg("Unable to guess type of query. You must specify it.")
433
+ exit
434
+ else
435
+ @config.logger.trace("Assuming query value is " + @config.options.query_type)
436
+ end
437
+ end
438
+
439
+ if @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] == nil && !@config.options.url
440
+ bootstrap = Bootstrap.new( @config )
441
+ qtype = @config.options.query_type
442
+ if qtype == QueryType::BY_SERVER_HELP
443
+ qtype = guess_query_value_type( @config.options.argv )
444
+ end
445
+ case qtype
446
+ when QueryType::BY_IP4_ADDR
447
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_ip( @config.options.argv[ 0 ] )
448
+ when QueryType::BY_IP6_ADDR
449
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_ip( @config.options.argv[ 0 ] )
450
+ when QueryType::BY_IP4_CIDR
451
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_ip( @config.options.argv[ 0 ] )
452
+ when QueryType::BY_IP6_CIDR
453
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_ip( @config.options.argv[ 0 ] )
454
+ when QueryType::BY_AS_NUMBER
455
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_as( @config.options.argv[ 0 ] )
456
+ when QueryType::BY_DOMAIN
457
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_domain( @config.options.argv[ 0 ] )
458
+ when QueryType::BY_NAMESERVER
459
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_domain( @config.options.argv[ 0 ] )
460
+ when QueryType::BY_ENTITY_HANDLE
461
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_entity( @config.options.argv[ 0 ] )
462
+ when QueryType::SRCH_ENTITY_BY_NAME
463
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::ENTITY_ROOT_URL ]
464
+ when QueryType::SRCH_DOMAIN_BY_NAME
465
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_domain( @config.options.argv[ 0 ] )
466
+ when QueryType::SRCH_DOMAIN_BY_NSNAME
467
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_domain( @config.options.argv[ 0 ] )
468
+ when QueryType::SRCH_DOMAIN_BY_NSIP
469
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::DOMAIN_ROOT_URL ]
470
+ when QueryType::SRCH_NS_BY_NAME
471
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_domain( @config.options.argv[ 0 ] )
472
+ when QueryType::SRCH_NS_BY_IP
473
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = bootstrap.find_url_by_ip( @config.options.argv[ 0 ] )
474
+ else
475
+ @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] = @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::HELP_ROOT_URL ]
476
+ end
477
+ end
478
+
479
+ begin
480
+ rdap_url = nil
481
+ unless @config.options.url
482
+ path = create_resource_url(@config.options.argv, @config.options.query_type)
483
+ rdap_url = make_rdap_url(@config.config[NicInfo::BOOTSTRAP][NicInfo::BOOTSTRAP_URL], path)
484
+ else
485
+ rdap_url = @config.options.argv[0]
486
+ end
487
+ data = get( rdap_url, 0 )
488
+ json_data = JSON.load data
489
+ if (ec = json_data[ NicInfo::NICINFO_DEMO_ERROR ]) != nil
490
+ res = MyHTTPResponse.new( "1.1", ec, "Demo Exception" )
491
+ res["content-type"] = NicInfo::RDAP_CONTENT_TYPE
492
+ res.body=data
493
+ raise Net::HTTPServerException.new( "Demo Exception", res )
494
+ end
495
+ inspect_rdap_compliance json_data
496
+ cache_self_references json_data
497
+ if @config.options.output_json
498
+ @config.logger.raw( DataAmount::TERSE_DATA, data, false )
499
+ elsif @config.options.json_values
500
+ @config.options.json_values.each do |value|
501
+ @config.logger.raw( DataAmount::TERSE_DATA, eval_json_value( value, json_data), false )
502
+ end
503
+ else
504
+ Notices.new.display_notices json_data, @config, @config.options.query_type == QueryType::BY_SERVER_HELP
505
+ if @config.options.query_type != QueryType::BY_SERVER_HELP
506
+ result_type = get_query_type_from_result( json_data )
507
+ if result_type != nil
508
+ if result_type != @config.options.query_type
509
+ @config.logger.mesg( "Query type is " + @config.options.query_type + ". Result type is " + result_type + "." )
510
+ else
511
+ @config.logger.mesg( "Result type is " + result_type + "." )
512
+ end
513
+ @config.options.query_type = result_type
514
+ elsif json_data[ "errorCode" ] == nil
515
+ @config.conf_msgs << "Response has no result type."
516
+ end
517
+ data_tree = DataTree.new( )
518
+ case @config.options.query_type
519
+ when QueryType::BY_IP4_ADDR
520
+ NicInfo::display_ip( json_data, @config, data_tree )
521
+ when QueryType::BY_IP6_ADDR
522
+ NicInfo::display_ip( json_data, @config, data_tree )
523
+ when QueryType::BY_IP4_CIDR
524
+ NicInfo::display_ip( json_data, @config, data_tree )
525
+ when QueryType::BY_IP6_CIDR
526
+ NicInfo::display_ip( json_data, @config, data_tree )
527
+ when QueryType::BY_IP
528
+ NicInfo::display_ip( json_data, @config, data_tree )
529
+ when QueryType::BY_AS_NUMBER
530
+ NicInfo::display_autnum( json_data, @config, data_tree )
531
+ when "NicInfo::DsData"
532
+ NicInfo::display_ds_data( json_data, @config, data_tree )
533
+ when "NicInfo::KeyData"
534
+ NicInfo::display_key_data( json_data, @config, data_tree )
535
+ when QueryType::BY_DOMAIN
536
+ NicInfo::display_domain( json_data, @config, data_tree )
537
+ when QueryType::BY_NAMESERVER
538
+ NicInfo::display_ns( json_data, @config, data_tree )
539
+ when QueryType::BY_ENTITY_HANDLE
540
+ NicInfo::display_entity( json_data, @config, data_tree )
541
+ when QueryType::SRCH_DOMAINS
542
+ NicInfo::display_domains( json_data, @config, data_tree )
543
+ when QueryType::SRCH_DOMAIN_BY_NAME
544
+ NicInfo::display_domains( json_data, @config, data_tree )
545
+ when QueryType::SRCH_DOMAIN_BY_NSNAME
546
+ NicInfo::display_domains( json_data, @config, data_tree )
547
+ when QueryType::SRCH_DOMAIN_BY_NSIP
548
+ NicInfo::display_domains( json_data, @config, data_tree )
549
+ when QueryType::SRCH_ENTITY_BY_NAME
550
+ NicInfo::display_entities( json_data, @config, data_tree )
551
+ when QueryType::SRCH_NS
552
+ NicInfo::display_nameservers( json_data, @config, data_tree )
553
+ when QueryType::SRCH_NS_BY_NAME
554
+ NicInfo::display_nameservers( json_data, @config, data_tree )
555
+ when QueryType::SRCH_NS_BY_IP
556
+ NicInfo::display_nameservers( json_data, @config, data_tree )
557
+ end
558
+ @config.save_as_yaml( NicInfo::LASTTREE_YAML, data_tree ) if !data_tree.empty?
559
+ show_search_results_truncated json_data
560
+ show_conformance_messages
561
+ show_helpful_messages json_data, data_tree
562
+ end
563
+ end
564
+ @config.logger.end_run
565
+ rescue JSON::ParserError => a
566
+ @config.logger.mesg( "Server returned invalid JSON!" )
567
+ rescue SocketError => a
568
+ @config.logger.mesg(a.message)
569
+ rescue ArgumentError => a
570
+ @config.logger.mesg(a.message)
571
+ rescue Net::HTTPServerException => e
572
+ case e.response.code
573
+ when "200"
574
+ @config.logger.mesg( e.message )
575
+ when "401"
576
+ @config.logger.mesg("Authorization is required.")
577
+ handle_error_response e.response
578
+ when "404"
579
+ @config.logger.mesg("Query yielded no results.")
580
+ handle_error_response e.response
581
+ else
582
+ @config.logger.mesg("Error #{e.response.code}.")
583
+ handle_error_response e.response
584
+ end
585
+ @config.logger.trace("Server response code was " + e.response.code)
586
+ rescue Net::HTTPFatalError => e
587
+ case e.response.code
588
+ when "500"
589
+ @config.logger.mesg("RDAP server is reporting an internal error.")
590
+ handle_error_response e.response
591
+ when "501"
592
+ @config.logger.mesg("RDAP server does not implement the query.")
593
+ handle_error_response e.response
594
+ when "503"
595
+ @config.logger.mesg("RDAP server is reporting that it is unavailable.")
596
+ handle_error_response e.response
597
+ else
598
+ @config.logger.mesg("Error #{e.response.code}.")
599
+ handle_error_response e.response
600
+ end
601
+ @config.logger.trace("Server response code was " + e.response.code)
602
+ end
603
+
604
+ end
605
+
606
+ def handle_error_response (res)
607
+ if res["content-type"] == NicInfo::RDAP_CONTENT_TYPE && res.body && res.body.to_s.size > 0
608
+ json_data = JSON.load( res.body )
609
+ inspect_rdap_compliance json_data
610
+ Notices.new.display_notices json_data, @config, true
611
+ ErrorCode.new.display_error_code json_data, @config
612
+ end
613
+ end
614
+
615
+ def inspect_rdap_compliance json
616
+ rdap_conformance = json[ "rdapConformance" ]
617
+ if rdap_conformance
618
+ rdap_conformance.each do |conformance|
619
+ @config.logger.trace( "Server conforms to #{conformance}" )
620
+ end
621
+ else
622
+ @config.conf_msgs << "Response has no RDAP Conformance level specified."
623
+ end
624
+ end
625
+
626
+ def help
627
+
628
+ puts NicInfo::VERSION_LABEL
629
+ puts NicInfo::COPYRIGHT
630
+ puts <<HELP_SUMMARY
631
+
632
+ SYNOPSIS
633
+ nicinfo [OPTIONS] QUERY_VALUE
634
+
635
+ SUMMARY
636
+ NicInfo is a Registry Data Access Protocol (RDAP) client capable of querying RDAP
637
+ servers containing IP address, Autonomous System, and Domain name information.
638
+
639
+ The general usage is "nicinfo QUERY_VALUE" where the type of QUERY_VALUE influences the
640
+ type of query performed. This program will attempt to guess the type of QUERY_VALUE,
641
+ but the QUERY_VALUE type maybe explicitly set using the -t option.
642
+
643
+ Given the type of query to perform, this program will attempt to use the most appropriate
644
+ RDAP server it can determine, and follow referrals from that server if necessary.
645
+
646
+ HELP_SUMMARY
647
+ puts @opts.help
648
+ puts EXTENDED_HELP
649
+ exit
650
+
651
+ end
652
+
653
+ # Looks at the returned JSON and attempts to match that
654
+ # to a query type.
655
+ def get_query_type_from_result( json_data )
656
+ retval = nil
657
+ object_class_name = json_data[ "objectClassName" ]
658
+ if object_class_name != nil
659
+ case object_class_name
660
+ when "domain"
661
+ retval = QueryType::BY_DOMAIN
662
+ when "ip network"
663
+ retval = QueryType::BY_IP
664
+ when "entity"
665
+ retval = QueryType::BY_ENTITY_HANDLE
666
+ when "autnum"
667
+ retval = QueryType::BY_AS_NUMBER
668
+ when "nameserver"
669
+ retval = QueryType::BY_NAMESERVER
670
+ end
671
+ end
672
+ if json_data[ "domainSearchResults" ]
673
+ retval = QueryType::SRCH_DOMAINS
674
+ elsif json_data[ "nameserverSearchResults" ]
675
+ retval = QueryType::SRCH_NS
676
+ elsif json_data[ "entitySearchResults" ]
677
+ retval = QueryType::SRCH_ENTITY_BY_NAME
678
+ end
679
+ return retval
680
+ end
681
+
682
+ # Evaluates the args and guesses at the type of query.
683
+ # Args is an array of strings, most likely what is left
684
+ # over after parsing ARGV
685
+ def guess_query_value_type(args)
686
+ retval = nil
687
+
688
+ if args.length() == 1
689
+
690
+ case args[0]
691
+ when NicInfo::URL_REGEX
692
+ retval = QueryType::BY_URL
693
+ when NicInfo::IPV4_REGEX
694
+ retval = QueryType::BY_IP4_ADDR
695
+ when NicInfo::IPV6_REGEX
696
+ retval = QueryType::BY_IP6_ADDR
697
+ when NicInfo::IPV6_HEXCOMPRESS_REGEX
698
+ retval = QueryType::BY_IP6_ADDR
699
+ when NicInfo::AS_REGEX
700
+ retval = QueryType::BY_AS_NUMBER
701
+ when NicInfo::ASN_REGEX
702
+ old = args[0]
703
+ args[0] = args[0].sub(/^AS/i, "")
704
+ @config.logger.trace("Interpretting " + old + " as autonomous system number " + args[0])
705
+ retval = QueryType::BY_AS_NUMBER
706
+ when NicInfo::IP4_ARPA
707
+ retval = QueryType::BY_DOMAIN
708
+ when NicInfo::IP6_ARPA
709
+ retval = QueryType::BY_DOMAIN
710
+ when /(.*)\/\d/
711
+ ip = $+
712
+ if ip =~ NicInfo::IPV4_REGEX
713
+ retval = QueryType::BY_IP4_CIDR
714
+ elsif ip =~ NicInfo::IPV6_REGEX || ip =~ NicInfo::IPV6_HEXCOMPRESS_REGEX
715
+ retval = QueryType::BY_IP6_CIDR
716
+ end
717
+ when NicInfo::DATA_TREE_ADDR_REGEX
718
+ retval = QueryType::BY_RESULT
719
+ when NicInfo::NS_REGEX
720
+ retval = QueryType::BY_NAMESERVER
721
+ when NicInfo::DOMAIN_REGEX
722
+ retval = QueryType::BY_DOMAIN
723
+ when NicInfo::ENTITY_REGEX
724
+ retval = QueryType::BY_ENTITY_HANDLE
725
+ else
726
+ last_name = args[ 0 ].sub( /\*/, '' ).upcase
727
+ if NicInfo::is_last_name( last_name )
728
+ retval = QueryType::SRCH_ENTITY_BY_NAME
729
+ end
730
+ end
731
+
732
+ elsif args.length() == 2
733
+
734
+ last_name = args[ 1 ].sub( /\*/, '' ).upcase
735
+ first_name = args[ 0 ].sub( /\*/, '' ).upcase
736
+ if NicInfo::is_last_name(last_name) && (NicInfo::is_male_name(first_name) || NicInfo::is_female_name(first_name))
737
+ retval = QueryType::SRCH_ENTITY_BY_NAME
738
+ end
739
+
740
+ elsif args.length() == 3
741
+
742
+ last_name = args[ 2 ].sub( /\*/, '' ).upcase
743
+ first_name = args[ 0 ].sub( /\*/, '' ).upcase
744
+ if NicInfo::is_last_name(last_name) && (NicInfo::is_male_name(first_name) || NicInfo::is_female_name(first_name))
745
+ retval = QueryType::SRCH_ENTITY_BY_NAME
746
+ end
747
+
748
+ end
749
+
750
+ return retval
751
+ end
752
+
753
+ # Creates a query type
754
+ def create_resource_url(args, queryType)
755
+
756
+ path = ""
757
+ case queryType
758
+ when QueryType::BY_IP4_ADDR
759
+ path << "ip/" << args[0]
760
+ when QueryType::BY_IP6_ADDR
761
+ path << "ip/" << args[0]
762
+ when QueryType::BY_IP4_CIDR
763
+ path << "ip/" << args[0]
764
+ when QueryType::BY_IP6_CIDR
765
+ path << "ip/" << args[0]
766
+ when QueryType::BY_AS_NUMBER
767
+ path << "autnum/" << args[0]
768
+ when QueryType::BY_NAMESERVER
769
+ path << "nameserver/" << args[0]
770
+ when QueryType::BY_DOMAIN
771
+ path << "domain/" << args[0]
772
+ when QueryType::BY_RESULT
773
+ tree = @config.load_as_yaml(NicInfo::ARININFO_LASTTREE_YAML)
774
+ path = tree.find_rest_ref(args[0])
775
+ raise ArgumentError.new("Unable to find result for " + args[0]) unless path
776
+ when QueryType::BY_ENTITY_HANDLE
777
+ path << "entity/" << URI.escape( args[ 0 ] )
778
+ when QueryType::SRCH_ENTITY_BY_NAME
779
+ case args.length
780
+ when 1
781
+ path << "entities?fn=" << URI.escape( args[ 0 ] )
782
+ when 2
783
+ path << "entities?fn=" << URI.escape( args[ 0 ] + " " + args[ 1 ] )
784
+ when 3
785
+ path << "entities?fn=" << URI.escape( args[ 0 ] + " " + args[ 1 ] + " " + args[ 2 ] )
786
+ end
787
+ when QueryType::SRCH_DOMAIN_BY_NAME
788
+ path << "domains?name=" << args[ 0 ]
789
+ when QueryType::SRCH_DOMAIN_BY_NSNAME
790
+ path << "domains?nsLdhName=" << args[ 0 ]
791
+ when QueryType::SRCH_DOMAIN_BY_NSIP
792
+ path << "domains?nsIp=" << args[ 0 ]
793
+ when QueryType::SRCH_NS_BY_NAME
794
+ path << "nameservers?name=" << args[ 0 ]
795
+ when QueryType::SRCH_NS_BY_IP
796
+ path << "nameservers?ip=" << args[ 0 ]
797
+ when QueryType::BY_SERVER_HELP
798
+ path << "help"
799
+ else
800
+ raise ArgumentError.new("Unable to create a resource URL for " + queryType)
801
+ end
802
+
803
+ return path
804
+ end
805
+
806
+ def get_query_type_from_url url
807
+ queryType = nil
808
+ case url
809
+ when /.*\/ip\/.*/
810
+ # covers all IP cases
811
+ queryType = QueryType::BY_IP
812
+ when /.*\/autnum\/.*/
813
+ queryType = QueryType::BY_AS_NUMBER
814
+ when /.*\/nameserver\/.*/
815
+ queryType = QueryType::BY_NAMESERVER
816
+ when /.*\/domain\/.*/
817
+ queryType = QueryType::BY_DOMAIN
818
+ when /.*\/entity\/.*/
819
+ queryType = QueryType::BY_ENTITY_HANDLE
820
+ when /.*\/entities.*/
821
+ queryType = QueryType::SRCH_ENTITY_BY_NAME
822
+ when /.*\/domains.*/
823
+ # covers all domain searches
824
+ queryType = QueryType::SRCH_DOMAIN
825
+ when /.*\/nameservers.*/
826
+ # covers all nameserver searches
827
+ queryType = QueryType::SRCH_NS
828
+ when /.*\/help.*/
829
+ queryType = QueryType::BY_SERVER_HELP
830
+ else
831
+ raise ArgumentError.new( "Unable to determine query type from url '#{url}'" )
832
+ end
833
+ return queryType
834
+ end
835
+
836
+ def eval_json_value json_value, json_data
837
+ appended_code = String.new
838
+ values = json_value.split( "." )
839
+ values.each do |value|
840
+ i = Integer( value ) rescue false
841
+ if i
842
+ appended_code << "[#{i}]"
843
+ else
844
+ appended_code << "[\"#{value}\"]"
845
+ end
846
+ end
847
+ code = "json_data#{appended_code}"
848
+ return eval( code )
849
+ end
850
+
851
+ def cache_self_references json_data
852
+ links = NicInfo::get_links json_data, @config
853
+ if links
854
+ self_link = NicInfo.get_self_link links
855
+ if self_link
856
+ pretty = JSON::pretty_generate( json_data )
857
+ @cache.create( self_link, pretty )
858
+ end
859
+ end
860
+ entities = NicInfo::get_entitites json_data
861
+ entities.each do |entity|
862
+ cache_self_references( entity )
863
+ end if entities
864
+ nameservers = NicInfo::get_nameservers json_data
865
+ nameservers.each do |ns|
866
+ cache_self_references( ns )
867
+ end if nameservers
868
+ ds_data_objs = NicInfo::get_ds_data_objs json_data
869
+ ds_data_objs.each do |ds|
870
+ cache_self_references( ds )
871
+ end if ds_data_objs
872
+ key_data_objs = NicInfo::get_key_data_objs json_data
873
+ key_data_objs.each do |key|
874
+ cache_self_references( key )
875
+ end if key_data_objs
876
+ end
877
+
878
+ def show_conformance_messages
879
+ return if @config.conf_msgs.size == 0
880
+ @config.logger.mesg( "** WARNING: There are problems in the response that might cause some data to discarded. **" )
881
+ i = 1
882
+ @config.conf_msgs.each do |msg|
883
+ @config.logger.trace( "#{i} : #{msg}" )
884
+ i = i + 1
885
+ end
886
+ end
887
+
888
+ def show_search_results_truncated json_data
889
+ truncated = json_data[ "searchResultsTruncated" ]
890
+ if truncated.instance_of?(TrueClass) || truncated.instance_of?(FalseClass)
891
+ if truncated
892
+ @config.logger.mesg( "Results have been truncated by the server." )
893
+ end
894
+ end
895
+ if truncated != nil
896
+ @config.conf_msgs << "'searchResultsTruncated' is not part of the RDAP specification and was removed before standardization."
897
+ end
898
+ end
899
+
900
+ def show_helpful_messages json_data, data_tree
901
+ arg = @config.options.argv[0]
902
+ case @config.options.query_type
903
+ when QueryType::BY_IP4_ADDR
904
+ @config.logger.mesg("Use \"nicinfo -r #{arg}\" to see reverse DNS information.");
905
+ when QueryType::BY_IP6_ADDR
906
+ @config.logger.mesg("Use \"nicinfo -r #{arg}\" to see reverse DNS information.");
907
+ when QueryType::BY_AS_NUMBER
908
+ @config.logger.mesg("Use \"nicinfo #{arg}\" or \"nicinfo as#{arg}\" for autnums.");
909
+ end
910
+ unless data_tree.empty?
911
+ @config.logger.mesg("Use \"nicinfo 1=\" to show #{data_tree.roots.first}")
912
+ unless data_tree.roots.first.empty?
913
+ children = data_tree.roots.first.children
914
+ @config.logger.mesg("Use \"nicinfo 1.1=\" to show #{children.first}") if children.first.rest_ref
915
+ if children.first != children.last
916
+ len = children.length
917
+ @config.logger.mesg("Use \"nicinfo 1.#{len}=\" to show #{children.last}") if children.last.rest_ref
918
+ end
919
+ end
920
+ end
921
+ self_link = NicInfo.get_self_link( NicInfo.get_links( json_data, @config ) )
922
+ @config.logger.mesg("Use \"nicinfo #{self_link}\" to directly query this resource in the future.") if self_link and @config.options.externally_queriable
923
+ @config.logger.mesg('Use "nicinfo -h" for help.')
924
+ end
925
+
926
+ end
927
+
928
+ class MyHTTPResponse < Net::HTTPResponse
929
+ attr_accessor :body
930
+ end
931
+
932
+ EXTENDED_HELP = <<EXTENDED_HELP
933
+
934
+ QUERIES
935
+ For most query values, the query type is inferred. However, some types of queries
936
+ cannot be inferred and so the -t option must be used. The domain search by name
937
+ (dsbyname) and entity search by name (esbyname) queries can take wildcards ('*'),
938
+ but these must be quoted or escaped to avoid processing by the invoking OS shell
939
+ on Unix-like operating systems.
940
+
941
+ CONFIGURATION
942
+ When this program is run for the first time, it creates a directory called .NicInfo
943
+ (on Unix style platforms) or NicInfo (on Windows) in the users home directory. The
944
+ home directory is determined by the $HOME environment variable on Unix style platforms
945
+ and $APPDATA on Windows.
946
+
947
+ A configuration file is created in this directory called config.yaml. This is a YAML
948
+ file and contains a means for specifying most of the features of this program (instead
949
+ of needing to specify them on the command line as options). To set the configuration
950
+ back to the installation defaults, use the --reset option. This maybe desirable when
951
+ updating versions of this program.
952
+
953
+ A directory called rdap_cache is also created inside this directory. It holds cached
954
+ values from previously executed queries.
955
+
956
+ CACHING
957
+ This program will write query responses to a cache. By default, answers are pulled
958
+ from the cache if present. This can be turned on or off with the --cache option or
959
+ using the cache/use_cache value in the configuration file.
960
+
961
+ Expiration of items in the cache and eviction of items from the cache can also be
962
+ controlled. The cache can be manually emptied using the --empty-cache option.
963
+
964
+ BOOTSTRAPPING
965
+ Bootstrapping is the process of finding an appropriate RDAP server in which to send
966
+ queries. This program has a three tier bootstrapping process.
967
+
968
+ The first tier looks up the most appropriate server using internal tables compiled
969
+ from IANA registries. If an appropriate server cannot be found, bootstrapping falls
970
+ to the second tier.
971
+
972
+ The second tier has a default server for each type of RDAP query (domain, ip, autnum,
973
+ nameserver, and entity). If this program cannot determine the type of query, bootstrapping
974
+ falls to the third tier.
975
+
976
+ The third tier is a default server for all queries.
977
+
978
+ All bootstrap URLs are specified in the configuration file. Bootstrapping maybe
979
+ bypassed using the -b or --base option (or by setting the bootstrap/base_url in the
980
+ configuration file).
981
+
982
+ USAGE FOR SCRIPTING
983
+ For usage with shell scripting, there are a couple of useful command line switches.
984
+
985
+ The --json option suppresses the human-readable output and instead emits the JSON
986
+ returned by the server. When not writing to an output file, this options should be
987
+ used with the -Q option to suppress the pager and program runtime messages so that
988
+ the JSON maybe run through a JSON parser.
989
+
990
+ The --jv option instructs this program to parse the JSON and emit specific JSON
991
+ values. This option is also useful in combination with the -Q option to feed the
992
+ JSON values into other programs. The syntax for specifying a JSON value is a
993
+ list of JSON object member names or integers signifying JSON array indexes separated
994
+ by a period, such as name1.name2.3.name4.5. For example, "entities.0.handle" would
995
+ be useful for getting at the "handle" value from the following JSON:
996
+
997
+ { "entities" : [ { "handle": "foo" } ] }
998
+
999
+ Multiple --jv options may be specified.
1000
+
1001
+ DEMONSTRATION QUERIES
1002
+ There are several built-in demonstration queries that may be exercised to show the
1003
+ utility of RDAP. To use these queries, the --demo option must be used to populate
1004
+ the query answers into the cache. If the cache is already populated with items, it
1005
+ may be necessary to clean the cache using the --empty-cache option.
1006
+
1007
+ When the --demo option is given, the list of demonstration queries will be printed
1008
+ out.
1009
+ EXTENDED_HELP
1010
+
1011
+ end
1012
+
1013
+