bettercap 1.1.9 → 1.1.10

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +2 -2
  3. data/TODO.md +5 -2
  4. data/bin/bettercap +19 -11
  5. data/lib/bettercap.rb +5 -1
  6. data/lib/bettercap/base/ifirewall.rb +2 -0
  7. data/lib/bettercap/base/ispoofer.rb +2 -0
  8. data/lib/bettercap/context.rb +5 -2
  9. data/lib/bettercap/discovery/{arp.rb → agents/arp.rb} +15 -14
  10. data/lib/bettercap/discovery/{base.rb → agents/base.rb} +4 -4
  11. data/lib/bettercap/discovery/{icmp.rb → agents/icmp.rb} +2 -3
  12. data/lib/bettercap/discovery/{udp.rb → agents/udp.rb} +2 -2
  13. data/lib/bettercap/{discovery.rb → discovery/discovery.rb} +8 -1
  14. data/lib/bettercap/factories/firewall_factory.rb +2 -0
  15. data/lib/bettercap/factories/parser_factory.rb +11 -5
  16. data/lib/bettercap/factories/spoofer_factory.rb +3 -1
  17. data/lib/bettercap/firewalls/linux.rb +2 -0
  18. data/lib/bettercap/firewalls/osx.rb +2 -0
  19. data/lib/bettercap/firewalls/redirection.rb +2 -1
  20. data/lib/bettercap/httpd/server.rb +2 -3
  21. data/lib/bettercap/logger.rb +27 -10
  22. data/lib/bettercap/monkey/packetfu/utils.rb +5 -5
  23. data/lib/bettercap/network.rb +26 -16
  24. data/lib/bettercap/options.rb +27 -6
  25. data/lib/bettercap/proxy/certstore.rb +4 -3
  26. data/lib/bettercap/proxy/module.rb +2 -2
  27. data/lib/bettercap/proxy/proxy.rb +5 -5
  28. data/lib/bettercap/proxy/request.rb +2 -2
  29. data/lib/bettercap/proxy/response.rb +2 -0
  30. data/lib/bettercap/proxy/stream_logger.rb +15 -3
  31. data/lib/bettercap/proxy/streamer.rb +3 -1
  32. data/lib/bettercap/proxy/thread_pool.rb +4 -2
  33. data/lib/bettercap/shell.rb +2 -0
  34. data/lib/bettercap/sniffer/parsers/base.rb +3 -12
  35. data/lib/bettercap/sniffer/parsers/custom.rb +21 -0
  36. data/lib/bettercap/sniffer/parsers/ftp.rb +2 -0
  37. data/lib/bettercap/sniffer/parsers/httpauth.rb +4 -5
  38. data/lib/bettercap/sniffer/parsers/https.rb +3 -4
  39. data/lib/bettercap/sniffer/parsers/irc.rb +2 -0
  40. data/lib/bettercap/sniffer/parsers/mail.rb +2 -0
  41. data/lib/bettercap/sniffer/parsers/ntlmss.rb +3 -3
  42. data/lib/bettercap/sniffer/parsers/post.rb +7 -7
  43. data/lib/bettercap/sniffer/parsers/url.rb +11 -11
  44. data/lib/bettercap/sniffer/sniffer.rb +8 -2
  45. data/lib/bettercap/spoofers/arp.rb +15 -5
  46. data/lib/bettercap/spoofers/none.rb +2 -0
  47. data/lib/bettercap/target.rb +29 -10
  48. data/lib/bettercap/update_checker.rb +2 -0
  49. data/lib/bettercap/version.rb +1 -1
  50. metadata +8 -40
  51. data/Rakefile +0 -7
  52. data/test/factories/firewall_factory_test.rb +0 -54
  53. data/test/factories/parser_factory_test.rb +0 -36
  54. data/test/factories/spoofer_factory_test.rb +0 -15
  55. data/test/firewalls/linux_firewall_test.rb +0 -72
  56. data/test/firewalls/osx_firewall_test.rb +0 -72
  57. data/test/helpers/mock_shell.rb +0 -17
  58. data/test/logger_test.rb +0 -12
  59. data/test/network_test.rb +0 -14
  60. data/test/pcap/ftp.pcap +0 -0
  61. data/test/pcap/http.pcap +0 -0
  62. data/test/pcap/packets.pcap +0 -0
  63. data/test/proxy/response_test.rb +0 -56
  64. data/test/shell_test.rb +0 -15
  65. data/test/sniffer/parsers/base_parser_test.rb +0 -20
  66. data/test/sniffer/parsers/ftp_parser_test.rb +0 -27
  67. data/test/sniffer/parsers/url_parser_test.rb +0 -25
  68. data/test/target_test.rb +0 -24
  69. data/test/test_helper.rb +0 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f3a44959da9a916d0c0c67da9eaf293547006ba4
4
- data.tar.gz: 78e4dcf3c3da73095e0a291038de9f38928875bc
3
+ metadata.gz: 397d0cf63901e645278f09bb5b2eafd9745945ed
4
+ data.tar.gz: 8c67517734bb52946622f279c5590301b592f7d6
5
5
  SHA512:
6
- metadata.gz: 4f8fffb6c4c2c47d6e5fee83434a7f969f9201486a77a007d2f2a0d4486e5ade866421bbed71aa9373150ddb340b0b77567b462ac68987192d98f2b72bac2052
7
- data.tar.gz: c312331cded4f9e729f0255860f66e6a1402ba29cfc4613dc8a2edca42e8bc263d1430e2aae3e4c99b69b5e1520abc02204b93b285232ec4177035c3e13a7408
6
+ metadata.gz: 6957c7da7b6781a1efdf229b93ce565d875f32ee6a8ccaa6361767ef2b31b79f421400b21397f5b3717aef7b065afd97a7e37de3a1c3efeaeab44ca6cd224d51
7
+ data.tar.gz: fb5dd3cdc11a9b7ce6406e02cac9632204aba0ded518ea46335689bef6ceb690f678e23dcad29b07c4013c65c27034f57a18f82c2bc0681902d79d9b6e7e0d66
@@ -21,7 +21,7 @@ Once you've gone through this list, open an issue and please give us as much as
21
21
 
22
22
  Also, you should attach to the issue a debug log that you can generate with:
23
23
 
24
- sudo bettercap [arguments you are using for testing] --debug --log=debug.log
24
+ [sudo|rvmsudo] bettercap [arguments you are using for testing] --debug --log=debug.log
25
25
 
26
26
  Wait for the error to happen then close bettercap and paste the **debug.log** file inside the issue.
27
27
 
@@ -39,4 +39,4 @@ There're plenty of things you can to do improve the software:
39
39
  * Implement a new proxy module and push it to the [dedicated repository](https://github.com/evilsocket/bettercap-proxy-modules).
40
40
  * Implement a new [Spoofer module](https://github.com/evilsocket/bettercap/blob/master/lib/bettercap/spoofers/arp.rb).
41
41
  * Implement a new [Sniffer credentials parser](https://github.com/evilsocket/bettercap/blob/master/lib/bettercap/sniffer/parsers/post.rb).
42
- * Fix, extend or improve the core.
42
+ * Fix, extend or improve the core.
data/TODO.md CHANGED
@@ -5,8 +5,10 @@ This is a list of TODOs I use to keep track of tasks and upcoming features.
5
5
  - [x] Implement `--ignore ADDR,ADDR,ADDR` option to filter out specific addresses from the targets list.
6
6
  - [x] HTTP 1.1 chunked response support.
7
7
  - [x] Ip address to hostname resolution.
8
- - [ ] Wrap every class with `module BetterCap` and refactor old code.
9
- - [ ] Use StreamLogger for both Proxy and Sniffer traffic.
8
+ - [x] Wrap every class with `module BetterCap` and refactor old code.
9
+ - [x] Use StreamLogger for both Proxy and Sniffer traffic.
10
+ - [x] Implement `--custom-parser REGEX` option.
11
+ - [x] Let `-T|--target` [support MAC addresses](https://github.com/evilsocket/bettercap/issues/82).
10
12
  - [ ] Implement event-driven core plugin infrastructure ( for webui, etc ).
11
13
  - [ ] Implement web-ui core plugin.
12
14
  - [ ] Rewrite proxy class using [em-proxy](https://github.com/igrigorik/em-proxy) library.
@@ -24,3 +26,4 @@ This is a list of TODOs I use to keep track of tasks and upcoming features.
24
26
  - [ ] ICMP Redirect ? ( only half duplex and filtered by many firewalls anyway ... dunno ).
25
27
  - [ ] DNS Spoofing ( not sure if it actually makes any sense ).
26
28
  - [ ] sslstrip ( not really sure, currently is quite useless )
29
+ - [ ] Implement `--mkcert FILENAME` option to create custom HTTPS `crt` files.
@@ -20,12 +20,16 @@ begin
20
20
 
21
21
  # Create global context, parse command line arguments and perform basic
22
22
  # error checking.
23
- ctx = Options.parse!
23
+ ctx = BetterCap::Options.parse!
24
24
 
25
25
  # Start targets auto discovery if needed.
26
26
  if ctx.options.target.nil?
27
- Logger.info( "Targeting the whole subnet #{ctx.ifconfig[:ip4_obj].to_range} ..." ) unless ctx.options.has_spoofer?
27
+ BetterCap::Logger.info( "Targeting the whole subnet #{ctx.ifconfig[:ip4_obj].to_range} ..." ) unless ctx.options.has_spoofer?
28
28
  ctx.discovery.start
29
+ # give some time to the discovery thread to spawn its workers,
30
+ # this will prevent 'Too many open files' errors to delay host
31
+ # discovery.
32
+ sleep 1.5
29
33
  end
30
34
 
31
35
  # Start network spoofers if any.
@@ -36,7 +40,7 @@ begin
36
40
  # Start proxies and setup port redirection.
37
41
  if ctx.options.proxy
38
42
  if ctx.options.has_http_sniffer_enabled?
39
- Logger.warn "WARNING: Both HTTP transparent proxy and URL parser are enabled, you're gonna see duplicated logs."
43
+ BetterCap::Logger.warn "WARNING: Both HTTP transparent proxy and URL parser are enabled, you're gonna see duplicated logs."
40
44
  end
41
45
  ctx.create_proxies
42
46
  end
@@ -45,16 +49,16 @@ begin
45
49
 
46
50
  # Start local HTTP server.
47
51
  if ctx.options.httpd
48
- ctx.httpd = HTTPD::Server.new( ctx.options.httpd_port, ctx.options.httpd_path )
52
+ ctx.httpd = BetterCap::HTTPD::Server.new( ctx.options.httpd_port, ctx.options.httpd_path )
49
53
  ctx.httpd.start
50
54
  end
51
55
 
52
56
  # Start network sniffer.
53
57
  if ctx.options.sniffer
54
- Sniffer.start ctx
58
+ BetterCap::Sniffer.start ctx
55
59
  else
56
- Logger.warn 'WARNING: Sniffer module was NOT enabled ( -X argument ), this '\
57
- 'will cause the MITM to run but no data to be collected.' unless ctx.options.has_spoofer?
60
+ BetterCap::Logger.warn 'WARNING: Sniffer module was NOT enabled ( -X argument ), this '\
61
+ 'will cause the MITM to run but no data to be collected.' unless ctx.options.has_spoofer?
58
62
  end
59
63
 
60
64
  loop do
@@ -62,15 +66,19 @@ begin
62
66
  end
63
67
 
64
68
  rescue SystemExit, Interrupt
65
- Logger.raw "\n"
69
+ BetterCap::Logger.raw "\n"
66
70
 
67
71
  rescue BetterCap::Error => e
68
- Logger.error e.message
72
+
73
+ BetterCap::Logger.error e.message
69
74
 
70
75
  rescue Exception => e
71
- Logger.error e.message
72
- Logger.error e.backtrace.join("\n")
76
+ BetterCap::Logger.error e.message
77
+ BetterCap::Logger.error e.backtrace.join("\n")
73
78
 
74
79
  ensure
80
+ # Make sure all the messages on the logger queue are printed.
81
+ BetterCap::Logger.wait!
82
+
75
83
  ctx.finalize unless ctx.nil?
76
84
  end
@@ -27,7 +27,11 @@ Config = RbConfig
27
27
  require 'bettercap/update_checker'
28
28
  require 'bettercap/error'
29
29
  require 'bettercap/options'
30
- require 'bettercap/discovery'
30
+ require 'bettercap/discovery/agents/base'
31
+ require 'bettercap/discovery/agents/arp'
32
+ require 'bettercap/discovery/agents/icmp'
33
+ require 'bettercap/discovery/agents/udp'
34
+ require 'bettercap/discovery/discovery'
31
35
  require 'bettercap/context'
32
36
  require 'bettercap/monkey/packetfu/utils'
33
37
  require 'bettercap/factories/firewall_factory'
@@ -9,6 +9,7 @@ Blog : http://www.evilsocket.net/
9
9
  This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
+ module BetterCap
12
13
  class IFirewall
13
14
  def initialize
14
15
  @frwd_initial_state = forwarding_enabled?
@@ -42,3 +43,4 @@ private
42
43
  raise NotImplementedError, 'IFirewall: Unimplemented method!'
43
44
  end
44
45
  end
46
+ end
@@ -9,6 +9,7 @@ Blog : http://www.evilsocket.net/
9
9
  This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
+ module BetterCap
12
13
  class ISpoofer
13
14
  def initialize
14
15
  not_implemented_method!
@@ -28,3 +29,4 @@ private
28
29
  raise NotImplementedError, 'ISpoofer: Unimplemented method!'
29
30
  end
30
31
  end
32
+ end
@@ -13,6 +13,7 @@ This project is released under the GPL 3 license.
13
13
  # this class holds global states & data
14
14
  require 'bettercap/error'
15
15
 
16
+ module BetterCap
16
17
  class Context
17
18
  attr_accessor :options, :ifconfig, :firewall, :gateway,
18
19
  :targets, :discovery, :spoofer, :httpd,
@@ -87,7 +88,7 @@ class Context
87
88
 
88
89
  Proxy::Module.register_modules
89
90
 
90
- raise BetterCap::Error, "#{@options.proxy_module} is not a valid bettercap proxy module." unless !Proxy::Module.modules.empty?
91
+ raise BetterCap::Error, "#{@options.proxy_module} is not a valid bettercap proxy module." if Proxy::Module.modules.empty?
91
92
  end
92
93
 
93
94
  @proxy_processor = Proc.new do |request,response|
@@ -137,7 +138,8 @@ class Context
137
138
  def finalize
138
139
  @running = false
139
140
 
140
- Logger.info 'Shutting down, hang on ...'
141
+ # Logger is silent if @running == false
142
+ puts "\nShutting down, hang on ...\n"
141
143
 
142
144
  Logger.debug 'Stopping target discovery manager ...'
143
145
  @discovery.stop
@@ -163,3 +165,4 @@ class Context
163
165
  @httpd.stop unless @httpd.nil?
164
166
  end
165
167
  end
168
+ end
@@ -9,15 +9,10 @@ Blog : http://www.evilsocket.net/
9
9
  This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
- require 'bettercap/logger'
13
- require 'bettercap/shell'
14
- require 'bettercap/target'
15
- require 'bettercap/discovery/base'
16
- require 'bettercap/context'
17
12
 
18
13
  # Parse the ARP table searching for new hosts.
14
+ module BetterCap
19
15
  class ArpAgent < BaseAgent
20
-
21
16
  def self.parse( ctx )
22
17
  targets = []
23
18
  self.parse_cache do |ip,mac|
@@ -47,19 +42,24 @@ class ArpAgent < BaseAgent
47
42
  nil
48
43
  end
49
44
 
45
+ def self.find_mac( address )
46
+ self.parse_cache do |ip,mac|
47
+ if mac == address
48
+ return ip
49
+ end
50
+ end
51
+ nil
52
+ end
53
+
50
54
  private
51
55
 
52
56
  def self.parse_cache
53
- arp = Shell.arp
54
-
55
- Logger.debug "ARP CACHE:\n#{arp}"
56
-
57
- arp.split("\n").each do |line|
57
+ Shell.arp.split("\n").each do |line|
58
58
  m = self.parse_cache_line(line)
59
- if !m.nil?
59
+ unless m.nil?
60
60
  ip = m[1]
61
- hw = m[2]
62
- if hw != 'ff:ff:ff:ff:ff:ff'
61
+ hw = Target.normalized_mac( m[2] )
62
+ if hw != 'FF:FF:FF:FF:FF:FF'
63
63
  yield( ip, hw )
64
64
  end
65
65
  end
@@ -82,3 +82,4 @@ class ArpAgent < BaseAgent
82
82
  pkt.to_w( @ifconfig[:iface] )
83
83
  end
84
84
  end
85
+ end
@@ -9,9 +9,9 @@ Blog : http://www.evilsocket.net/
9
9
  This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
- require 'bettercap/logger'
13
12
 
14
13
  # Base class for discovery agents.
14
+ module BetterCap
15
15
  class BaseAgent
16
16
  def initialize( ifconfig, gw_ip, local_ip )
17
17
  @local_ip = local_ip
@@ -35,7 +35,7 @@ class BaseAgent
35
35
  @workers = (0...4).map do
36
36
  Thread.new do
37
37
  begin
38
- while ip = @queue.pop(true)
38
+ while ip = @queue.pop(true)
39
39
  loop do
40
40
  Logger.debug "#{self.class.name} : Probing #{ip} ..."
41
41
 
@@ -44,7 +44,7 @@ class BaseAgent
44
44
 
45
45
  break
46
46
  rescue Exception => e
47
- Logger.debug "#{self.class.name}#send_probe : #{ip} -> #{e.message}"
47
+ Logger.debug "#{self.class.name}#send_probe : #{ip} -> #{e.message}"
48
48
 
49
49
  # If we've got an error message such as:
50
50
  # (cannot open BPF device) /dev/bpf0: Too many open files
@@ -80,4 +80,4 @@ class BaseAgent
80
80
  Logger.warn "#{self.class.name} not implemented!"
81
81
  end
82
82
  end
83
-
83
+ end
@@ -9,11 +9,9 @@ Blog : http://www.evilsocket.net/
9
9
  This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
- require 'bettercap/logger'
13
- require 'bettercap/shell'
14
- require 'bettercap/factories/firewall_factory'
15
12
 
16
13
  # Send a broadcast ping trying to filling the ARP table.
14
+ module BetterCap
17
15
  class IcmpAgent
18
16
  def initialize( timeout = 5 )
19
17
  @thread = Thread.new {
@@ -35,3 +33,4 @@ class IcmpAgent
35
33
  end
36
34
  end
37
35
  end
36
+ end
@@ -9,9 +9,9 @@ Blog : http://www.evilsocket.net/
9
9
  This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
- require 'bettercap/discovery/base'
13
12
 
14
13
  # Send UDP probes trying to filling the ARP table.
14
+ module BetterCap
15
15
  class UdpAgent < BaseAgent
16
16
  private
17
17
 
@@ -36,4 +36,4 @@ class UdpAgent < BaseAgent
36
36
  # TODO: Parse response for hostname?
37
37
  end
38
38
  end
39
-
39
+ end
@@ -9,6 +9,7 @@ Blog : http://www.evilsocket.net/
9
9
  This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
+ module BetterCap
12
13
  class Discovery
13
14
  def initialize( ctx )
14
15
  @ctx = ctx
@@ -48,10 +49,16 @@ class Discovery
48
49
 
49
50
  if empty_list and not @ctx.targets.empty?
50
51
  Logger.info "Collected #{@ctx.targets.size} total targets."
52
+
53
+ msg = "\n"
51
54
  @ctx.targets.each do |target|
52
- Logger.info " #{target}"
55
+ msg += " #{target}\n"
53
56
  end
57
+ Logger.raw msg
54
58
  end
59
+
60
+ sleep(5) if @ctx.options.arpcache
55
61
  end
56
62
  end
57
63
  end
64
+ end
@@ -13,6 +13,7 @@ require 'bettercap/error'
13
13
  require 'bettercap/firewalls/osx'
14
14
  require 'bettercap/firewalls/linux'
15
15
 
16
+ module BetterCap
16
17
  class FirewallFactory
17
18
  @@instance = nil
18
19
 
@@ -34,3 +35,4 @@ class FirewallFactory
34
35
  @@instance = nil
35
36
  end
36
37
  end
38
+ end
@@ -13,6 +13,7 @@ This project is released under the GPL 3 license.
13
13
  require 'bettercap/error'
14
14
  require 'bettercap/logger'
15
15
 
16
+ module BetterCap
16
17
  class ParserFactory
17
18
  @@path = File.dirname(__FILE__) + '/../sniffer/parsers/'
18
19
 
@@ -20,7 +21,7 @@ class ParserFactory
20
21
  def available
21
22
  avail = []
22
23
  Dir.foreach( @@path ) do |file|
23
- if file =~ /.rb/ and file != 'base.rb'
24
+ if file =~ /.rb/ and file != 'base.rb' and file != 'custom.rb'
24
25
  avail << file.gsub('.rb','').upcase
25
26
  end
26
27
  end
@@ -38,20 +39,25 @@ class ParserFactory
38
39
  list
39
40
  end
40
41
 
41
- def ParserFactory.load_by_names(parsers)
42
+ def load_by_names(parsers)
42
43
  loaded = []
43
44
  Dir.foreach( @@path ) do |file|
44
45
  cname = file.gsub('.rb','').upcase
45
- if file =~ /.rb/ and file != 'base.rb' and ( parsers.include?(cname) or parsers == ['*'] )
46
+ if file =~ /.rb/ and file != 'base.rb' and file != 'custom.rb' and ( parsers.include?(cname) or parsers == ['*'] )
46
47
  Logger.debug "Loading parser #{cname} ..."
47
48
 
48
49
  require_relative "#{@@path}#{file}"
49
50
 
50
- loaded << Kernel.const_get("#{cname.capitalize}Parser").new
51
+ loaded << Kernel.const_get("BetterCap::#{cname.capitalize}Parser").new
51
52
  end
52
53
  end
53
54
  loaded
54
55
  end
56
+
57
+ def load_custom(expression)
58
+ require_relative "#{@@path}custom.rb"
59
+ [ BetterCap::CustomParser.new(expression) ]
60
+ end
55
61
  end
56
62
  end
57
-
63
+ end
@@ -11,6 +11,7 @@ This project is released under the GPL 3 license.
11
11
  =end
12
12
  require 'bettercap/error'
13
13
 
14
+ module BetterCap
14
15
  class SpooferFactory
15
16
  class << self
16
17
  def available
@@ -30,7 +31,7 @@ class SpooferFactory
30
31
 
31
32
  require_relative "../spoofers/#{name}"
32
33
 
33
- Kernel.const_get("#{name.capitalize}Spoofer").new
34
+ Kernel.const_get("BetterCap::#{name.capitalize}Spoofer").new
34
35
  end
35
36
 
36
37
  private
@@ -40,3 +41,4 @@ class SpooferFactory
40
41
  end
41
42
  end
42
43
  end
44
+ end
@@ -12,6 +12,7 @@ This project is released under the GPL 3 license.
12
12
  require 'bettercap/base/ifirewall'
13
13
  require 'bettercap/shell'
14
14
 
15
+ module BetterCap
15
16
  class LinuxFirewall < IFirewall
16
17
  def enable_forwarding(enabled)
17
18
  shell.execute("echo #{enabled ? 1 : 0} > /proc/sys/net/ipv4/ip_forward")
@@ -47,3 +48,4 @@ class LinuxFirewall < IFirewall
47
48
  Shell
48
49
  end
49
50
  end
51
+ end
@@ -12,6 +12,7 @@ This project is released under the GPL 3 license.
12
12
  require 'bettercap/base/ifirewall'
13
13
  require 'bettercap/shell'
14
14
 
15
+ module BetterCap
15
16
  class OSXFirewall < IFirewall
16
17
  def enable_forwarding(enabled)
17
18
  shell.execute("sysctl -w net.inet.ip.forwarding=#{enabled ? 1 : 0}")
@@ -66,3 +67,4 @@ class OSXFirewall < IFirewall
66
67
  Shell
67
68
  end
68
69
  end
70
+ end