nicinfo 1.3.0.pre.alpha1 → 1.5.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.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2011,2012,2013,2014 American Registry for Internet Numbers
1
+ # Copyright (C) 2011-2017 American Registry for Internet Numbers
2
2
  #
3
3
  # Permission to use, copy, modify, and/or distribute this software for any
4
4
  # purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@ require 'optparse'
17
17
  require 'net/http'
18
18
  require 'net/https'
19
19
  require 'uri'
20
+ require 'jcr'
20
21
  require 'nicinfo/config'
21
22
  require 'nicinfo/constants'
22
23
  require 'nicinfo/cache'
@@ -68,9 +69,17 @@ module NicInfo
68
69
 
69
70
  end
70
71
 
72
+ class JcrMode < NicInfo::Enum
73
+ JcrMode.add_item :NO_VALIDATION, "NONE"
74
+ JcrMode.add_item :STANDARD_VALIDATION, "STANDARD"
75
+ JcrMode.add_item :STRICT_VALIDATION, "STRICT"
76
+ end
77
+
71
78
  # The main class for the nicinfo command.
72
79
  class Main
73
80
 
81
+ attr_accessor :config, :cache, :jcr_context, :jcr_strict_context
82
+
74
83
  def initialize args, config = nil
75
84
 
76
85
  if config
@@ -80,6 +89,7 @@ module NicInfo
80
89
  end
81
90
 
82
91
  @config.options.require_query = true
92
+ @config.options.jcr = JcrMode::NO_VALIDATION
83
93
 
84
94
  @opts = OptionParser.new do |opts|
85
95
 
@@ -199,6 +209,15 @@ module NicInfo
199
209
  raise OptionParser::InvalidArgument, pager.to_s unless pager =~ /yes|no|true|false/i
200
210
  end
201
211
 
212
+ opts.on( "--color-scheme DARK|LIGHT|NONE",
213
+ "Determines color scheme to use:",
214
+ " dark - for terminals with dark backgrounds",
215
+ " light - for terminals with light backgrounds",
216
+ " none - turn off colors" ) do |cs|
217
+ @config.logger.color_scheme = cs.to_s.upcase
218
+ raise OptionParser::InvalidArgument, cs.to_s unless cs =~ /dark|light|none/i
219
+ end
220
+
202
221
  opts.on( "-V",
203
222
  "Equivalent to --messages all and --data extra" ) do |v|
204
223
  @config.logger.data_amount = NicInfo::DataAmount::EXTRA_DATA
@@ -256,6 +275,15 @@ module NicInfo
256
275
  @config.options.require_query = false
257
276
  end
258
277
 
278
+ opts.on( "--jcr STANDARD|STRICT",
279
+ "Validate RDAP response with JCR") do |mode|
280
+ upmode = mode.upcase
281
+ raise OptionParser::InvalidArgument, type.to_s unless JcrMode.has_value?(upmode)
282
+ @config.options.jcr = upmode
283
+ get_jcr_context if upmode == JcrMode::STANDARD_VALIDATION
284
+ get_jcr_strict_context if upmode == JcrMode::STRICT_VALIDATION
285
+ end
286
+
259
287
  end
260
288
 
261
289
  begin
@@ -379,7 +407,7 @@ module NicInfo
379
407
  def run
380
408
 
381
409
  @config.logger.run_pager
382
- @config.logger.mesg(NicInfo::VERSION_LABEL)
410
+ @config.logger.mesg(NicInfo::VERSION_LABEL, NicInfo::AttentionType::PRIMARY )
383
411
  @config.setup_workspace
384
412
  @config.check_config_version
385
413
  @cache = Cache.new(@config)
@@ -395,16 +423,16 @@ module NicInfo
395
423
  check_bsfiles_age = @config.check_bsfiles_age?
396
424
  update_bsfiles = @config.update_bsfiles?( check_bsfiles_age )
397
425
  if update_bsfiles
398
- @config.logger.mesg( "IANA RDAP bootstrap files are old and need to be updated." )
426
+ @config.logger.mesg( "IANA RDAP bootstrap files are old and need to be updated.", NicInfo::AttentionType::ERROR )
399
427
  get_iana_files
400
428
  elsif check_bsfiles_age
401
- @config.logger.mesg( "IANA RDAP bootstrap files are old. Update them with --iana option" )
429
+ @config.logger.mesg( "IANA RDAP bootstrap files are old. Update them with --iana option", NicInfo::AttentionType::ERROR )
402
430
  end
403
431
  end
404
432
 
405
433
  if @config.options.demo
406
- @config.logger.mesg( "Populating cache with demonstration results" )
407
- @config.logger.mesg( "Try the following demonstration queries:" )
434
+ @config.logger.mesg( "Populating cache with demonstration results", NicInfo::AttentionType::INFO )
435
+ @config.logger.mesg( "Try the following demonstration queries:", NicInfo::AttentionType::INFO )
408
436
  demo_dir = File.join( File.dirname( __FILE__ ), NicInfo::DEMO_DIR )
409
437
  demo_files = Dir::entries( demo_dir )
410
438
  demo_files.each do |file|
@@ -415,7 +443,7 @@ module NicInfo
415
443
  demo_url = json_data[ NicInfo::NICINFO_DEMO_URL ]
416
444
  demo_hint = json_data[ NicInfo::NICINFO_DEMO_HINT ]
417
445
  @cache.create( demo_url, demo_data )
418
- @config.logger.mesg( " " + demo_hint )
446
+ @config.logger.mesg( " " + demo_hint, NicInfo::AttentionType::INFO )
419
447
  end
420
448
  end
421
449
  end
@@ -426,7 +454,7 @@ module NicInfo
426
454
 
427
455
  if @config.options.argv == nil || @config.options.argv == [] && !@config.options.query_type
428
456
  unless @config.options.require_query
429
- exit
457
+ return
430
458
  else
431
459
  help
432
460
  end
@@ -440,9 +468,9 @@ module NicInfo
440
468
  if json_data["data"] == nil || json_data["data"]["ip"] == nil
441
469
  @config.logger.mesg("Server repsonded with unknown JSON")
442
470
  @config.logger.mesg("Unable to determine your IP Address. You must specify it.")
443
- exit
471
+ return
444
472
  elsif
445
- @config.logger.mesg("Your IP address is " + json_data["data"]["ip"])
473
+ @config.logger.mesg("Your IP address is " + json_data["data"]["ip"], NicInfo::AttentionType::SUCCESS )
446
474
  @config.options.argv[0] = json_data["data"]["ip"]
447
475
  end
448
476
  end
@@ -472,7 +500,7 @@ module NicInfo
472
500
  end
473
501
  else
474
502
  @config.logger.mesg( "#{@config.options.argv[0]} is not retrievable.")
475
- exit
503
+ return
476
504
  end
477
505
  elsif @config.options.query_type == QueryType::BY_URL
478
506
  @config.options.url = @config.options.argv[0]
@@ -482,7 +510,7 @@ module NicInfo
482
510
  end
483
511
  if @config.options.query_type == nil
484
512
  @config.logger.mesg("Unable to guess type of query. You must specify it.")
485
- exit
513
+ return
486
514
  else
487
515
  @config.logger.trace("Assuming query value is " + @config.options.query_type)
488
516
  end
@@ -506,7 +534,7 @@ module NicInfo
506
534
  end
507
535
  else
508
536
  json_data = do_rdap_query
509
- display_rdap_query( json_data, true )
537
+ display_rdap_query( json_data, true ) if json_data
510
538
  end
511
539
 
512
540
 
@@ -521,6 +549,7 @@ module NicInfo
521
549
  end
522
550
 
523
551
  def do_rdap_query
552
+ retval = nil
524
553
  if @config.config[ NicInfo::BOOTSTRAP ][ NicInfo::BOOTSTRAP_URL ] == nil && !@config.options.url
525
554
  bootstrap = Bootstrap.new( @config )
526
555
  qtype = @config.options.query_type
@@ -578,56 +607,60 @@ module NicInfo
578
607
  end
579
608
  inspect_rdap_compliance json_data
580
609
  cache_self_references json_data
581
- json_data
610
+ retval = json_data
582
611
  rescue JSON::ParserError => a
583
- @config.logger.mesg( "Server returned invalid JSON!" )
612
+ @config.logger.mesg( "Server returned invalid JSON!", NicInfo::AttentionType::ERROR )
584
613
  rescue SocketError => a
585
- @config.logger.mesg(a.message)
614
+ @config.logger.mesg(a.message, NicInfo::AttentionType::ERROR )
586
615
  rescue ArgumentError => a
587
- @config.logger.mesg(a.message)
616
+ @config.logger.mesg(a.message, NicInfo::AttentionType::ERROR )
588
617
  rescue Net::HTTPServerException => e
589
618
  case e.response.code
590
619
  when "200"
591
- @config.logger.mesg( e.message )
620
+ @config.logger.mesg( e.message, NicInfo::AttentionType::SUCCESS )
592
621
  when "401"
593
- @config.logger.mesg("Authorization is required.")
622
+ @config.logger.mesg("Authorization is required.", NicInfo::AttentionType::ERROR )
594
623
  handle_error_response e.response
595
624
  when "404"
596
- @config.logger.mesg("Query yielded no results.")
625
+ @config.logger.mesg("Query yielded no results.", NicInfo::AttentionType::INFO )
597
626
  handle_error_response e.response
598
627
  else
599
- @config.logger.mesg("Error #{e.response.code}.")
628
+ @config.logger.mesg("Error #{e.response.code}.", NicInfo::AttentionType::ERROR )
600
629
  handle_error_response e.response
601
630
  end
602
631
  @config.logger.trace("Server response code was " + e.response.code)
603
632
  rescue Net::HTTPFatalError => e
604
633
  case e.response.code
605
634
  when "500"
606
- @config.logger.mesg("RDAP server is reporting an internal error.")
635
+ @config.logger.mesg("RDAP server is reporting an internal error.", NicInfo::AttentionType::ERROR )
607
636
  handle_error_response e.response
608
637
  when "501"
609
- @config.logger.mesg("RDAP server does not implement the query.")
638
+ @config.logger.mesg("RDAP server does not implement the query.", NicInfo::AttentionType::ERROR )
610
639
  handle_error_response e.response
611
640
  when "503"
612
- @config.logger.mesg("RDAP server is reporting that it is unavailable.")
641
+ @config.logger.mesg("RDAP server is reporting that it is unavailable.", NicInfo::AttentionType::ERROR )
613
642
  handle_error_response e.response
614
643
  else
615
- @config.logger.mesg("Error #{e.response.code}.")
644
+ @config.logger.mesg("Error #{e.response.code}.", NicInfo::AttentionType::ERROR )
616
645
  handle_error_response e.response
617
646
  end
618
647
  @config.logger.trace("Server response code was " + e.response.code)
648
+ rescue Net::HTTPRetriableError => e
649
+ @config.logger.mesg("Too many redirections, retries, or a redirect loop has been detected." )
619
650
  end
651
+
652
+ return retval
620
653
  end
621
654
 
622
655
  def display_rdap_query json_data, show_help = true
623
656
  if @config.options.output_json
624
- @config.logger.raw( DataAmount::TERSE_DATA, json_data, false )
657
+ @config.logger.raw( DataAmount::TERSE_DATA, JSON.generate( json_data ), false )
625
658
  elsif @config.options.json_values
626
659
  @config.options.json_values.each do |value|
627
- @config.logger.raw( DataAmount::TERSE_DATA, eval_json_value( value, json_data), false )
660
+ @config.logger.raw( DataAmount::TERSE_DATA, JSON.generate( eval_json_value( value, json_data) ), false )
628
661
  end
629
662
  else
630
- Notices.new.display_notices json_data, @config, @config.options.query_type == QueryType::BY_SERVER_HELP
663
+ @config.factory.new_notices.display_notices json_data, @config.options.query_type == QueryType::BY_SERVER_HELP
631
664
  if @config.options.query_type != QueryType::BY_SERVER_HELP
632
665
  result_type = get_query_type_from_result( json_data )
633
666
  if result_type != nil
@@ -644,42 +677,59 @@ module NicInfo
644
677
  case @config.options.query_type
645
678
  when QueryType::BY_IP4_ADDR
646
679
  NicInfo::display_ip( json_data, @config, data_tree )
680
+ do_jcr( json_data, NicInfo::JCR_ROOT_NETWORK )
647
681
  when QueryType::BY_IP6_ADDR
648
682
  NicInfo::display_ip( json_data, @config, data_tree )
683
+ do_jcr( json_data, NicInfo::JCR_ROOT_NETWORK )
649
684
  when QueryType::BY_IP4_CIDR
650
685
  NicInfo::display_ip( json_data, @config, data_tree )
686
+ do_jcr( json_data, NicInfo::JCR_ROOT_NETWORK )
651
687
  when QueryType::BY_IP6_CIDR
652
688
  NicInfo::display_ip( json_data, @config, data_tree )
689
+ do_jcr( json_data, NicInfo::JCR_ROOT_NETWORK )
653
690
  when QueryType::BY_IP
654
691
  NicInfo::display_ip( json_data, @config, data_tree )
692
+ do_jcr( json_data, NicInfo::JCR_ROOT_NETWORK )
655
693
  when QueryType::BY_AS_NUMBER
656
694
  NicInfo::display_autnum( json_data, @config, data_tree )
695
+ do_jcr( json_data, NicInfo::JCR_ROOT_AUTNUM )
657
696
  when "NicInfo::DsData"
658
697
  NicInfo::display_ds_data( json_data, @config, data_tree )
659
698
  when "NicInfo::KeyData"
660
699
  NicInfo::display_key_data( json_data, @config, data_tree )
661
700
  when QueryType::BY_DOMAIN
662
701
  NicInfo::display_domain( json_data, @config, data_tree )
702
+ do_jcr( json_data, NicInfo::JCR_ROOT_DOMAIN )
663
703
  when QueryType::BY_NAMESERVER
664
704
  NicInfo::display_ns( json_data, @config, data_tree )
705
+ do_jcr( json_data, NicInfo::JCR_ROOT_NAMESERVER )
665
706
  when QueryType::BY_ENTITY_HANDLE
666
707
  NicInfo::display_entity( json_data, @config, data_tree )
708
+ do_jcr( json_data, NicInfo::JCR_ROOT_ENTITY )
667
709
  when QueryType::SRCH_DOMAINS
668
710
  NicInfo::display_domains( json_data, @config, data_tree )
711
+ do_jcr( json_data, NicInfo::JCR_ROOT_DOMAIN_SEARCH )
669
712
  when QueryType::SRCH_DOMAIN_BY_NAME
670
713
  NicInfo::display_domains( json_data, @config, data_tree )
714
+ do_jcr( json_data, NicInfo::JCR_ROOT_DOMAIN_SEARCH )
671
715
  when QueryType::SRCH_DOMAIN_BY_NSNAME
672
716
  NicInfo::display_domains( json_data, @config, data_tree )
717
+ do_jcr( json_data, NicInfo::JCR_ROOT_DOMAIN_SEARCH )
673
718
  when QueryType::SRCH_DOMAIN_BY_NSIP
674
719
  NicInfo::display_domains( json_data, @config, data_tree )
720
+ do_jcr( json_data, NicInfo::JCR_ROOT_DOMAIN_SEARCH )
675
721
  when QueryType::SRCH_ENTITY_BY_NAME
676
722
  NicInfo::display_entities( json_data, @config, data_tree )
723
+ do_jcr( json_data, NicInfo::JCR_ROOT_ENTITY_SEARCH )
677
724
  when QueryType::SRCH_NS
678
725
  NicInfo::display_nameservers( json_data, @config, data_tree )
726
+ do_jcr( json_data, NicInfo::JCR_ROOT_NAMESERVER_SEARCH )
679
727
  when QueryType::SRCH_NS_BY_NAME
680
728
  NicInfo::display_nameservers( json_data, @config, data_tree )
729
+ do_jcr( json_data, NicInfo::JCR_ROOT_NAMESERVER_SEARCH )
681
730
  when QueryType::SRCH_NS_BY_IP
682
731
  NicInfo::display_nameservers( json_data, @config, data_tree )
732
+ do_jcr( json_data, NicInfo::JCR_ROOT_NAMESERVER_SEARCH )
683
733
  end
684
734
  @config.save_as_yaml( NicInfo::LASTTREE_YAML, data_tree ) if !data_tree.empty?
685
735
  show_search_results_truncated json_data
@@ -694,8 +744,8 @@ module NicInfo
694
744
  if res["content-type"] == NicInfo::RDAP_CONTENT_TYPE && res.body && res.body.to_s.size > 0
695
745
  json_data = JSON.load( res.body )
696
746
  inspect_rdap_compliance json_data
697
- Notices.new.display_notices json_data, @config, true
698
- ErrorCode.new.display_error_code json_data, @config
747
+ @config.factory.new_notices.display_notices json_data, true
748
+ @config.factory.new_error_code.display_error_code( json_data )
699
749
  end
700
750
  end
701
751
 
@@ -703,13 +753,60 @@ module NicInfo
703
753
  rdap_conformance = json[ "rdapConformance" ]
704
754
  if rdap_conformance
705
755
  rdap_conformance.each do |conformance|
706
- @config.logger.trace( "Server conforms to #{conformance}" )
756
+ @config.logger.trace( "Server conforms to #{conformance}", NicInfo::AttentionType::SECONDARY )
707
757
  end
708
758
  else
709
759
  @config.conf_msgs << "Response has no RDAP Conformance level specified."
710
760
  end
711
761
  end
712
762
 
763
+ def get_jcr_context
764
+ if @jcr_context != nil
765
+ return @jcr_context
766
+ end
767
+ #else
768
+ ruleset_file = File.join( File.dirname( __FILE__ ), NicInfo::JCR_DIR, NicInfo::RDAP_JCR )
769
+ ruleset = File.open( ruleset_file ).read
770
+ @jcr_context = JCR::Context.new(ruleset, false )
771
+ return @jcr_context
772
+ end
773
+
774
+ def get_jcr_strict_context
775
+ if @jcr_strict_context != nil
776
+ return @jcr_strict_context
777
+ end
778
+ #else
779
+ strict_file = File.join( File.dirname( __FILE__ ), NicInfo::JCR_DIR, NicInfo::STRICT_RDAP_JCR )
780
+ strict = File.open( strict_file ).read
781
+ rdap_context = get_jcr_context()
782
+ @jcr_strict_context = rdap_context.override(strict )
783
+ return @jcr_strict_context
784
+ end
785
+
786
+ def do_jcr( json_data, root_name )
787
+
788
+ jcr_context = nil
789
+ if config.options.jcr == JcrMode::STANDARD_VALIDATION
790
+ config.logger.trace( "Standard JSON Content Rules validation mode enabled.")
791
+ jcr_context = get_jcr_context()
792
+ elsif config.options.jcr == JcrMode::STRICT_VALIDATION
793
+ config.logger.trace( "Strict JSON Content Rules validation mode enabled.")
794
+ jcr_context = get_jcr_strict_context()
795
+ else
796
+ return
797
+ end
798
+
799
+ e1 = jcr_context.evaluate( json_data, root_name )
800
+
801
+ unless e1.success
802
+ jcr_context.failure_report.each do |line|
803
+ config.conf_msgs << line
804
+ end
805
+ else
806
+ config.logger.trace( "JSON Content Rules validation was successful." )
807
+ end
808
+ end
809
+
713
810
  def help
714
811
 
715
812
  puts NicInfo::VERSION_LABEL
@@ -965,10 +1062,11 @@ HELP_SUMMARY
965
1062
 
966
1063
  def show_conformance_messages
967
1064
  return if @config.conf_msgs.size == 0
968
- @config.logger.mesg( "** WARNING: There are problems in the response that might cause some data to discarded. **" )
1065
+ @config.logger.mesg( "** WARNING: There are problems in the response that might cause some data to discarded. **", NicInfo::AttentionType::ERROR )
969
1066
  i = 1
1067
+ pad = @config.conf_msgs.length.to_s.length
970
1068
  @config.conf_msgs.each do |msg|
971
- @config.logger.trace( "#{i} : #{msg}" )
1069
+ @config.logger.trace( "#{i.to_s.rjust(pad," ")} : #{msg}", NicInfo::AttentionType::ERROR )
972
1070
  i = i + 1
973
1071
  end
974
1072
  end
@@ -977,7 +1075,7 @@ HELP_SUMMARY
977
1075
  truncated = json_data[ "searchResultsTruncated" ]
978
1076
  if truncated.instance_of?(TrueClass) || truncated.instance_of?(FalseClass)
979
1077
  if truncated
980
- @config.logger.mesg( "Results have been truncated by the server." )
1078
+ @config.logger.mesg( "Results have been truncated by the server.", NicInfo::AttentionType::INFO )
981
1079
  end
982
1080
  end
983
1081
  if truncated != nil
@@ -1021,7 +1119,7 @@ HELP_SUMMARY
1021
1119
 
1022
1120
  QUERIES
1023
1121
  For most query values, the query type is inferred. However, some types of queries
1024
- cannot be inferred and so the -t option must be used. The domain search by name
1122
+ cannot be inferred and so the -t parameter must be used. The domain search by name
1025
1123
  (dsbyname) and entity search by name (esbyname) queries can take wildcards ('*'),
1026
1124
  but these must be quoted or escaped to avoid processing by the invoking OS shell
1027
1125
  on Unix-like operating systems.
@@ -1043,11 +1141,11 @@ CONFIGURATION
1043
1141
 
1044
1142
  CACHING
1045
1143
  This program will write query responses to a cache. By default, answers are pulled
1046
- from the cache if present. This can be turned on or off with the --cache option or
1144
+ from the cache if present. This can be turned on or off with the --cache parameter or
1047
1145
  using the cache/use_cache value in the configuration file.
1048
1146
 
1049
1147
  Expiration of items in the cache and eviction of items from the cache can also be
1050
- controlled. The cache can be manually emptied using the --empty-cache option.
1148
+ controlled. The cache can be manually emptied using the --empty-cache parameter.
1051
1149
 
1052
1150
  BOOTSTRAPPING
1053
1151
  Bootstrapping is the process of finding an appropriate RDAP server in which to send
@@ -1068,15 +1166,15 @@ BOOTSTRAPPING
1068
1166
  configuration file).
1069
1167
 
1070
1168
  USAGE FOR SCRIPTING
1071
- For usage with shell scripting, there are a couple of useful command line switches.
1169
+ For usage with shell scripting, there are a couple of useful command line parameters.
1072
1170
 
1073
- The --json option suppresses the human-readable output and instead emits the JSON
1171
+ The --json parameter suppresses the human-readable output and instead emits the JSON
1074
1172
  returned by the server. When not writing to an output file, this options should be
1075
1173
  used with the -Q option to suppress the pager and program runtime messages so that
1076
1174
  the JSON maybe run through a JSON parser.
1077
1175
 
1078
- The --jv option instructs this program to parse the JSON and emit specific JSON
1079
- values. This option is also useful in combination with the -Q option to feed the
1176
+ The --jv parameter instructs this program to parse the JSON and emit specific JSON
1177
+ values. This parameter is also useful in combination with the -Q option to feed the
1080
1178
  JSON values into other programs. The syntax for specifying a JSON value is a
1081
1179
  list of JSON object member names or integers signifying JSON array indexes separated
1082
1180
  by a period, such as name1.name2.3.name4.5. For example, "entities.0.handle" would
@@ -1084,16 +1182,27 @@ USAGE FOR SCRIPTING
1084
1182
 
1085
1183
  { "entities" : [ { "handle": "foo" } ] }
1086
1184
 
1087
- Multiple --jv options may be specified.
1185
+ Multiple --jv parameters may be specified.
1088
1186
 
1089
1187
  DEMONSTRATION QUERIES
1090
1188
  There are several built-in demonstration queries that may be exercised to show the
1091
- utility of RDAP. To use these queries, the --demo option must be used to populate
1189
+ utility of RDAP. To use these queries, the --demo parameter must be used to populate
1092
1190
  the query answers into the cache. If the cache is already populated with items, it
1093
- may be necessary to clean the cache using the --empty-cache option.
1191
+ may be necessary to clean the cache using the --empty-cache parameter.
1094
1192
 
1095
- When the --demo option is given, the list of demonstration queries will be printed
1193
+ When the --demo parameter is given, the list of demonstration queries will be printed
1096
1194
  out.
1195
+
1196
+ RDAP VALIDATION
1197
+ This program has built-in checks for verifying the validity of RDAP responses.
1198
+ Beyond these normal built-in checks, it can also JSON Content Rules to check
1199
+ the validity of the responses using the --jcr parameter, which requires either
1200
+ the standard (i.e. --jcr standard) or strict (i.e. --jcr strict) parameter
1201
+ options.
1202
+
1203
+ MORE INFORMATION
1204
+ More information about this program may be found at
1205
+ https://github.com/arineng/nicinfo/wiki
1097
1206
  EXTENDED_HELP
1098
1207
 
1099
1208
  end