diff --git a/Gemfile.lock b/Gemfile.lock index c07f1cd054..dedb811f56 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -35,12 +35,14 @@ PATH redcarpet rex-arch rex-bin_tools + rex-core rex-java rex-mime rex-ole rex-powershell rex-random_identifier rex-registry + rex-socket rex-struct2 rex-text rex-zip @@ -85,7 +87,7 @@ GEM arel (6.0.3) arel-helpers (2.3.0) activerecord (>= 3.1.0, < 6) - aruba (0.14.1) + aruba (0.14.2) childprocess (~> 0.5.6) contracts (~> 0.9) cucumber (>= 1.3.19) @@ -95,7 +97,7 @@ GEM bcrypt (3.1.11) bit-struct (0.15.0) builder (3.2.2) - capybara (2.7.1) + capybara (2.8.1) addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -237,7 +239,7 @@ GEM rex-core rex-struct2 rex-text - rex-core (0.1.1) + rex-core (0.1.2) rex-java (0.1.2) rex-mime (0.1.1) rex-text @@ -249,6 +251,8 @@ GEM rex-random_identifier (0.1.0) rex-text rex-registry (0.1.0) + rex-socket (0.1.0) + rex-core rex-struct2 (0.1.0) rex-text (0.2.1) rex-zip (0.1.0) @@ -263,7 +267,7 @@ GEM rspec-mocks (3.5.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) - rspec-rails (3.5.1) + rspec-rails (3.5.2) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index bd4f01995f..90643b6452 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -3,7 +3,7 @@ require 'rex/io/stream_abstraction' require 'rex/sync/ref' require 'rex/payloads/meterpreter/uri_checksum' require 'rex/post/meterpreter' -require 'rex/parser/x509_certificate' +require 'rex/socket/x509_certificate' require 'msf/core/payload/windows/verify_ssl' require 'rex/user_agent' diff --git a/lib/msf/core/payload/windows/verify_ssl.rb b/lib/msf/core/payload/windows/verify_ssl.rb index 3c038d488f..8cb1c97fe3 100644 --- a/lib/msf/core/payload/windows/verify_ssl.rb +++ b/lib/msf/core/payload/windows/verify_ssl.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- require 'msf/core' -require 'rex/parser/x509_certificate' +require 'rex/socket/x509_certificate' module Msf @@ -25,7 +25,7 @@ module Payload::Windows::VerifySsl raise ArgumentError, "Verifying SSL cert is enabled but no handler cert is configured" end - hash = Rex::Parser::X509Certificate.get_cert_file_hash(handler_cert) + hash = Rex::Socket::X509Certificate.get_cert_file_hash(handler_cert) print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") hash end diff --git a/lib/msf/ui/web/comm.rb b/lib/msf/ui/web/comm.rb index e4b6aaf335..b71f45fd77 100644 --- a/lib/msf/ui/web/comm.rb +++ b/lib/msf/ui/web/comm.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'rex/ui/text/bidirectional_pipe' module Msf module Ui module Web @@ -83,7 +84,7 @@ module Comm end def self.create_session_pipe(session) - pipe = Rex::IO::BidirectionalPipe.new + pipe = Rex::Ui::BidirectionalPipe.new @session_pipes[session.id] = pipe diff --git a/lib/msf/ui/web/console.rb b/lib/msf/ui/web/console.rb index 9e81b63434..075c4c1a52 100644 --- a/lib/msf/ui/web/console.rb +++ b/lib/msf/ui/web/console.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'rex/ui/text/bidirectional_pipe' module Msf module Ui module Web @@ -18,7 +19,7 @@ class WebConsole attr_accessor :thread # Wrapper class in case we need to extend the pipe - class WebConsolePipe < Rex::IO::BidirectionalPipe + class WebConsolePipe < Rex::Ui::Text::BidirectionalPipe def prompting? false end diff --git a/lib/msf/ui/web/driver.rb b/lib/msf/ui/web/driver.rb index 5fcc1bdc27..1ac3583d3b 100644 --- a/lib/msf/ui/web/driver.rb +++ b/lib/msf/ui/web/driver.rb @@ -8,7 +8,7 @@ module Msf module Ui module Web -require 'rex/io/bidirectional_pipe' +require 'rex/ui/text/bidirectional_pipe' require 'msf/ui/web/console' diff --git a/lib/rex/io/datagram_abstraction.rb b/lib/rex/io/datagram_abstraction.rb deleted file mode 100644 index bf9492dfa2..0000000000 --- a/lib/rex/io/datagram_abstraction.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: binary -*- - -require 'rex/io/socket_abstraction' - -module Rex -module IO - -### -# -# This class provides an abstraction to a datagram based -# connection through the use of a datagram socketpair. -# -### -module DatagramAbstraction - include Rex::IO::SocketAbstraction - - # - # Creates a streaming socket pair - # - def initialize_abstraction - self.lsock, self.rsock = Rex::Socket.udp_socket_pair - end - -end - -end; end diff --git a/lib/rex/io/ring_buffer.rb b/lib/rex/io/ring_buffer.rb deleted file mode 100644 index 84f95c7c33..0000000000 --- a/lib/rex/io/ring_buffer.rb +++ /dev/null @@ -1,369 +0,0 @@ -# -*- coding: binary -*- -# -# This class implements a ring buffer with "cursors" in the form of sequence numbers. -# To use this class, pass in a file descriptor and a ring size, the class will read -# data from the file descriptor and store it in the ring. If the ring becomes full, -# the oldest item will be overwritten. To emulate a stream interface, call read_data -# to grab the last sequence number and any buffered data, call read_data again, -# passing in the sequence number and all data newer than that sequence will be -# returned, along with a new sequence to read from. -# - -require 'rex/socket' - -module Rex -module IO - -class RingBuffer - - attr_accessor :queue # The data queue, essentially an array of two-element arrays, containing a sequence and data buffer - attr_accessor :seq # The next available sequence number - attr_accessor :fd # The associated socket or IO object for this ring buffer - attr_accessor :size # The number of available slots in the queue - attr_accessor :mutex # The mutex locking access to the queue - attr_accessor :beg # The index of the earliest data fragment in the ring - attr_accessor :cur # The sequence number of the earliest data fragment in the ring - attr_accessor :monitor # The thread handle of the built-in monitor when used - attr_accessor :monitor_thread_error # :nodoc: # - - # - # Create a new ring buffer - # - def initialize(socket, opts={}) - self.size = opts[:size] || (1024 * 4) - self.fd = socket - self.seq = 0 - self.beg = 0 - self.cur = 0 - self.queue = Array.new( self.size ) - self.mutex = Mutex.new - end - - def inspect - "#" - end - - # - # Start the built-in monitor, not called when used in a larger framework - # - def start_monitor - self.monitor = monitor_thread if not self.monitor - end - - # - # Stop the built-in monitor - # - def stop_monitor - self.monitor.kill if self.monitor - self.monitor = nil - end - - # - # The built-in monitor thread (normally unused with Metasploit) - # - def monitor_thread - Thread.new do - begin - while self.fd - buff = self.fd.get_once(-1, 1.0) - next if not buff - store_data(buff) - end - rescue ::Exception => e - self.monitor_thread_error = e - end - end - end - - # - # Push data back into the associated stream socket. Logging must occur - # elsewhere, this function is simply a passthrough. - # - def put(data, opts={}) - self.fd.put(data, opts={}) - end - - # - # The clear_data method wipes the ring buffer - # - def clear_data - self.mutex.synchronize do - self.seq = 0 - self.beg = 0 - self.cur = 0 - self.queue = Array.new( self.size ) - end - end - - # - # The store_data method is used to insert data into the ring buffer. - # - def store_data(data) - self.mutex.synchronize do - # self.cur points to the array index of queue containing the last item - # adding data will result in cur + 1 being used to store said data - # if cur is larger than size - 1, it will wrap back around. If cur - # is *smaller* beg, beg is increemnted to cur + 1 (and wrapped if - # necessary - - loc = 0 - if self.seq > 0 - loc = ( self.cur + 1 ) % self.size - - if loc <= self.beg - self.beg = (self.beg + 1) % self.size - end - end - - self.queue[loc] = [self.seq += 1, data] - self.cur = loc - end - end - - # - # The read_data method returns a two element array with the new reader cursor (a sequence number) - # and the returned data buffer (if any). A result of nil/nil indicates that no data is available - # - def read_data(ptr=nil) - self.mutex.synchronize do - - # Verify that there is data in the queue - return [nil,nil] if not self.queue[self.beg] - - # Configure the beginning read pointer (sequence number, not index) - ptr ||= self.queue[self.beg][0] - return [nil,nil] if not ptr - - # If the pointer is below our baseline, we lost some data, so jump forward - if ptr < self.queue[self.beg][0] - ptr = self.queue[self.beg][0] - end - - # Calculate how many blocks exist between the current sequence number - # and the requested pointer, this becomes the number of blocks we will - # need to read to satisfy the result. Due to the mutex block, we do - # not need to scan to find the sequence of the starting block or - # check the sequence of the ending block. - dis = self.seq - ptr - - # If the requested sequnce number is less than our base pointer, it means - # that no new data is available and we should return empty. - return [nil,nil] if dis < 0 - - # Calculate the beginning block index and number of blocks to read - off = ptr - self.queue[self.beg][0] - set = (self.beg + off) % self.size - - - # Build the buffer by reading forward by the number of blocks needed - # and return the last read sequence number, plus one, as the new read - # pointer. - buff = "" - cnt = 0 - lst = ptr - ptr.upto(self.seq) do |i| - block = self.queue[ (set + cnt) % self.size ] - lst,data = block[0],block[1] - buff += data - cnt += 1 - end - - return [lst + 1, buff] - - end - end - - # - # The base_sequence method returns the earliest sequence number in the queue. This is zero until - # all slots are filled and the ring rotates. - # - def base_sequence - self.mutex.synchronize do - return 0 if not self.queue[self.beg] - return self.queue[self.beg][0] - end - end - - # - # The last_sequence method returns the "next" sequence number where new data will be - # available. - # - def last_sequence - self.seq - end - - # - # The create_steam method assigns a IO::Socket compatible object to the ringer buffer - # - def create_stream - Stream.new(self) - end - - # - # The select method returns when there is a chance of new data - # XXX: This is mostly useless and requires a rewrite to use a - # real select or notify mechanism - # - def select - ::IO.select([ self.fd ], nil, [ self.fd ], 0.10) - end - - # - # The wait method blocks until new data is available - # - def wait(seq) - nseq = nil - while not nseq - nseq,data = read_data(seq) - select - end - end - - # - # The wait_for method blocks until new data is available or the timeout is reached - # - def wait_for(seq,timeout=1) - begin - ::Timeout.timeout(timeout) do - wait(seq) - end - rescue ::Timeout::Error - end - end - - # - # This class provides a backwards compatible "stream" socket that uses - # the parents ring buffer. - # - class Stream - attr_accessor :ring - attr_accessor :seq - attr_accessor :buff - - def initialize(ring) - self.ring = ring - self.seq = ring.base_sequence - self.buff = '' - end - - def read(len=nil) - if len and self.buff.length >= len - data = self.buff.slice!(0,len) - return data - end - - while true - lseq, data = self.ring.read_data( self.seq ) - return if not lseq - - self.seq = lseq - self.buff << data - if len - if self.buff.length >= len - return self.buff.slice!(0,len) - else - IO.select(nil, nil, nil, 0.25) - next - end - end - - data = self.buff - self.buff = '' - - return data - - # Not reached - break - end - - end - - def write(data) - self.ring.write(data) - end - end - -end - -end -end - -=begin - -server = Rex::Socket.create_tcp_server('LocalPort' => 0) -lport = server.getsockname[2] -client = Rex::Socket.create_tcp('PeerHost' => '127.0.0.1', 'PeerPort' => lport) -conn = server.accept - -r = Rex::IO::RingBuffer.new(conn, {:size => 1024*1024}) -client.put("1") -client.put("2") -client.put("3") - -s,d = r.read_data - -client.put("4") -client.put("5") -client.put("6") -s,d = r.read_data(s) - -client.put("7") -client.put("8") -client.put("9") -s,d = r.read_data(s) - -client.put("0") -s,d = r.read_data(s) - -test_counter = 11 -1.upto(100) do - client.put( "X" ) - test_counter += 1 -end - -sleep(1) - -s,d = r.read_data -p s -p d - -fdata = '' -File.open("/bin/ls", "rb") do |fd| - fdata = fd.read(fd.stat.size) - fdata = fdata * 10 - client.put(fdata) -end - -sleep(1) - -s,vdata = r.read_data(s) - -if vdata != fdata - puts "DATA FAILED" -else - puts "DATA VERIFIED" -end - -r.clear_data - -a = r.create_stream -b = r.create_stream - -client.put("ABC123") -sleep(1) - -p a.read -p b.read - -client.put("$$$$$$") -sleep(1) - -p a.read -p b.read - -c = r.create_stream -p c.read - -=end - - diff --git a/lib/rex/io/socket_abstraction.rb b/lib/rex/io/socket_abstraction.rb deleted file mode 100644 index c010c8a1b9..0000000000 --- a/lib/rex/io/socket_abstraction.rb +++ /dev/null @@ -1,205 +0,0 @@ -# -*- coding: binary -*- - -require 'socket' -require 'fcntl' - -module Rex -module IO - -### -# -# This class provides an abstraction to a stream based -# connection through the use of a streaming socketpair. -# -### -module SocketAbstraction - - ### - # - # Extension information for required Stream interface. - # - ### - module Ext - - # - # Initializes peer information. - # - def initinfo(peer,local) - @peer = peer - @local = local - end - - # - # Symbolic peer information. - # - def peerinfo - (@peer || "Remote Pipe") - end - - # - # Symbolic local information. - # - def localinfo - (@local || "Local Pipe") - end - end - - # - # Override this method to init the abstraction - # - def initialize_abstraction - self.lsock, self.rsock = Rex::Compat.pipe - end - - # - # This method cleans up the abstraction layer. - # - def cleanup_abstraction - self.lsock.close if (self.lsock and !self.lsock.closed?) - self.rsock.close if (self.rsock and !self.rsock.closed?) - - self.lsock = nil - self.rsock = nil - end - - # - # Low-level write to the local side. - # - def syswrite(buffer) - lsock.syswrite(buffer) - end - - # - # Low-level read from the local side. - # - def sysread(length) - lsock.sysread(length) - end - - # - # Shuts down the local side of the stream abstraction. - # - def shutdown(how) - lsock.shutdown(how) - end - - # - # Closes both sides of the stream abstraction. - # - def close - cleanup_abstraction - super - end - - # - # Symbolic peer information. - # - def peerinfo - "Remote-side of Pipe" - end - - # - # Symbolic local information. - # - def localinfo - "Local-side of Pipe" - end - - # - # The left side of the stream. - # - attr_reader :lsock - # - # The right side of the stream. - # - attr_reader :rsock - -protected - - def monitor_rsock(threadname = "SocketMonitorRemote") - self.monitor_thread = Rex::ThreadFactory.spawn(threadname, false) { - loop do - closed = false - buf = nil - - if not self.rsock - wlog("monitor_rsock: the remote socket is nil, exiting loop") - break - end - - begin - s = Rex::ThreadSafe.select( [ self.rsock ], nil, nil, 0.2 ) - if( s == nil || s[0] == nil ) - next - end - rescue Exception => e - wlog("monitor_rsock: exception during select: #{e.class} #{e}") - closed = true - end - - if( closed == false ) - begin - buf = self.rsock.sysread( 32768 ) - if buf == nil - closed = true - wlog("monitor_rsock: closed remote socket due to nil read") - end - rescue EOFError => e - closed = true - dlog("monitor_rsock: EOF in rsock") - rescue ::Exception => e - closed = true - wlog("monitor_rsock: exception during read: #{e.class} #{e}") - end - end - - if( closed == false ) - total_sent = 0 - total_length = buf.length - while( total_sent < total_length ) - begin - data = buf[total_sent, buf.length] - - # Note that this must be write() NOT syswrite() or put() or anything like it. - # Using syswrite() breaks SSL streams. - sent = self.write( data ) - - # sf: Only remove the data off the queue is write was successfull. - # This way we naturally perform a resend if a failure occured. - # Catches an edge case with meterpreter TCP channels where remote send - # failes gracefully and a resend is required. - if (sent.nil?) - closed = true - wlog("monitor_rsock: failed writing, socket must be dead") - break - elsif (sent > 0) - total_sent += sent - end - rescue ::IOError, ::EOFError => e - closed = true - wlog("monitor_rsock: exception during write: #{e.class} #{e}") - break - end - end - end - - if( closed ) - begin - self.close_write if self.respond_to?('close_write') - rescue IOError - end - break - end - end - } - end - -protected - attr_accessor :monitor_thread - attr_writer :lsock - attr_writer :rsock - -end - -end; end - diff --git a/lib/rex/io/stream.rb b/lib/rex/io/stream.rb deleted file mode 100644 index f64a7999d1..0000000000 --- a/lib/rex/io/stream.rb +++ /dev/null @@ -1,312 +0,0 @@ -# -*- coding: binary -*- -require 'rex/sync/thread_safe' - -module Rex -module IO - -### -# -# This mixin is an abstract representation of a streaming connection. Streams -# extend classes that must implement the following methods: -# -# syswrite(buffer) -# sysread(length) -# shutdown(how) -# close -# peerinfo -# localinfo -# -### -module Stream - - ## - # - # Abstract methods - # - ## - - # - # This method writes the supplied buffer to the stream. This method - # intelligent reduces the size of supplied buffers so that ruby doesn't get - # into a potential global thread blocking state when used on blocking - # sockets. That is, this method will send the supplied buffer in chunks - # of, at most, 32768 bytes. - # - def write(buf, opts = {}) - total_sent = 0 - total_length = buf.length - block_size = 32768 - - begin - while( total_sent < total_length ) - s = Rex::ThreadSafe.select( nil, [ fd ], nil, 0.2 ) - if( s == nil || s[0] == nil ) - next - end - data = buf[total_sent, block_size] - sent = fd.write_nonblock( data ) - if sent > 0 - total_sent += sent - end - end - rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK - # Sleep for a half a second, or until we can write again - Rex::ThreadSafe.select( nil, [ fd ], nil, 0.5 ) - # Decrement the block size to handle full sendQs better - block_size = 1024 - # Try to write the data again - retry - rescue ::IOError, ::Errno::EPIPE - return nil - end - - total_sent - end - - # - # This method reads data of the supplied length from the stream. - # - def read(length = nil, opts = {}) - - begin - return fd.read_nonblock( length ) - rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK - # Sleep for a half a second, or until we can read again - Rex::ThreadSafe.select( [ fd ], nil, nil, 0.5 ) - # Decrement the block size to handle full sendQs better - retry - rescue ::IOError, ::Errno::EPIPE - return nil - end - end - - # - # Polls the stream to see if there is any read data available. Returns - # true if data is available for reading, otherwise false is returned. - # - def has_read_data?(timeout = nil) - - # Allow a timeout of "0" that waits almost indefinitely for input, this - # mimics the behavior of Rex::ThreadSafe.select() and fixes some corner - # cases of unintentional no-wait timeouts. - timeout = 3600 if (timeout and timeout == 0) - - begin - if ((rv = ::IO.select([ fd ], nil, nil, timeout)) and - (rv[0]) and - (rv[0][0] == fd)) - true - else - false - end - rescue ::Errno::EBADF, ::Errno::ENOTSOCK - raise ::EOFError - rescue StreamClosedError, ::IOError, ::EOFError, ::Errno::EPIPE - # Return false if the socket is dead - return false - end - end - - # - # This method returns the selectable file descriptor, or self by default. - # - def fd - self - end - - ## - # - # Common methods - # - ## - - # - # This method writes the supplied buffer to the stream by calling the write - # routine. - # - def <<(buf) - return write(buf.to_s) - end - - # - # This method calls get_once() to read pending data from the socket - # - def >> - get_once - end - - # - # This method writes to the stream, optionally timing out after a period of - # time. - # - def timed_write(buf, wait = def_write_timeout, opts = {}) - if (wait and wait > 0) - Timeout.timeout(wait) { - return write(buf, opts) - } - else - return write(buf, opts) - end - end - - # - # This method reads from the stream, optionally timing out after a period - # of time. - # - def timed_read(length = nil, wait = def_read_timeout, opts = {}) - if (wait and wait > 0) - Timeout.timeout(wait) { - return read(length, opts) - } - else - return read(length, opts) - end - end - - # - # This method writes the full contents of the supplied buffer, optionally - # with a timeout. - # - def put(buf, opts = {}) - return 0 if (buf == nil or buf.length == 0) - - send_len = buf.length - send_idx = 0 - wait = opts['Timeout'] || 0 - - # Keep writing until our send length drops to zero - while (send_idx < send_len) - curr_len = timed_write(buf[send_idx, buf.length-send_idx], wait, opts) - - # If the write operation failed due to an IOError, then we fail. - return buf.length - send_len if (curr_len == nil) - - send_len -= curr_len - send_idx += curr_len - end - - return buf.length - send_len - end - - - # - # This method emulates the behavior of Pex::Socket::Recv in MSF2 - # - def get_once(length = -1, timeout = def_read_timeout) - - if (has_read_data?(timeout) == false) - return nil - end - - bsize = (length == -1) ? def_block_size : length - data = read(bsize) - raise EOFError if data.nil? - data - end - - # - # This method reads as much data as it can from the wire given a maximum - # timeout. - # - def get(timeout = nil, ltimeout = def_read_loop_timeout, opts = {}) - # For those people who are used to being able to use a negative timeout! - if (timeout and timeout.to_i < 0) - timeout = nil - end - - # No data in the first place? bust. - if (has_read_data?(timeout) == false) - return nil - end - - buf = "" - lps = 0 - eof = false - - # Keep looping until there is no more data to be gotten.. - while (has_read_data?(ltimeout) == true) - # Catch EOF errors so that we can handle them properly. - begin - temp = read(def_block_size) - rescue EOFError - eof = true - end - - # If we read zero bytes and we had data, then we've hit EOF - if (temp and temp.length == 0) - eof = true - end - - # If we reached EOF and there are no bytes in the buffer we've been - # reading into, then throw an EOF error. - if (eof) - # If we've already read at least some data, then it's time to - # break out and let it be processed before throwing an EOFError. - if (buf.length > 0) - break - else - raise EOFError - end - end - - break if (temp == nil or temp.empty? == true) - - buf += temp - lps += 1 - - break if (lps >= def_max_loops) - end - - # Return the entire buffer we read in - return buf - end - - ## - # - # Defaults - # - ## - - # - # The default number of seconds to wait for a write operation to timeout. - # - def def_write_timeout - 10 - end - - # - # The default number of seconds to wait for a read operation to timeout. - # - def def_read_timeout - 10 - end - - # - # The default number of seconds to wait while in a read loop after read - # data has been found. - # - def def_read_loop_timeout - 0.1 - end - - # - # The maximum number of read loops to perform before returning to the - # caller. - # - def def_max_loops - 1024 - end - - # - # The default block size to read in chunks from the wire. - # - def def_block_size - 16384 - end - -protected - -end - -end end - diff --git a/lib/rex/io/stream_abstraction.rb b/lib/rex/io/stream_abstraction.rb deleted file mode 100644 index 837cfd945a..0000000000 --- a/lib/rex/io/stream_abstraction.rb +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: binary -*- - -require 'rex/io/socket_abstraction' - -module Rex -module IO - -### -# -# This class provides an abstraction to a stream based -# connection through the use of a streaming socketpair. -# -### -module StreamAbstraction - include Rex::IO::SocketAbstraction - - # - # This method creates a streaming socket pair and initializes it. - # - def initialize_abstraction - self.lsock, self.rsock = Rex::Socket.tcp_socket_pair() - self.lsock.extend(Rex::IO::Stream) - self.lsock.extend(Ext) - self.rsock.extend(Rex::IO::Stream) - - self.monitor_rsock("StreamMonitorRemote") - end - -end - -end; end - diff --git a/lib/rex/io/stream_server.rb b/lib/rex/io/stream_server.rb deleted file mode 100644 index 188fb63131..0000000000 --- a/lib/rex/io/stream_server.rb +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: binary -*- -require 'thread' - -module Rex -module IO - -### -# -# This mixin provides the framework and interface for implementing a streaming -# server that can listen for and accept stream client connections. Stream -# servers extend this class and are required to implement the following -# methods: -# -# accept -# fd -# -### -module StreamServer - - ## - # - # Abstract methods - # - ## - - ## - # - # Default server monitoring and client management implementation follows - # below. - # - ## - - # - # This callback is notified when a client connects. - # - def on_client_connect(client) - if (on_client_connect_proc) - on_client_connect_proc.call(client) - end - end - - # - # This callback is notified when a client connection has data that needs to - # be processed. - # - def on_client_data(client) - if (on_client_data_proc) - on_client_data_proc.call(client) - end - end - - # - # This callback is notified when a client connection has closed. - # - def on_client_close(client) - if (on_client_close_proc) - on_client_close_proc.call(client) - end - end - - # - # Start monitoring the listener socket for connections and keep track of - # all client connections. - # - def start - self.clients = [] - self.client_waiter = ::Queue.new - - self.listener_thread = Rex::ThreadFactory.spawn("StreamServerListener", false) { - monitor_listener - } - self.clients_thread = Rex::ThreadFactory.spawn("StreamServerClientMonitor", false) { - monitor_clients - } - end - - # - # Terminates the listener monitoring threads and closes all active clients. - # - def stop - self.listener_thread.kill - self.clients_thread.kill - - self.clients.each { |cli| - close_client(cli) - } - end - - # - # This method closes a client connection and cleans up the resources - # associated with it. - # - def close_client(client) - if (client) - clients.delete(client) - - begin - client.close - rescue IOError - end - end - end - - # - # This method waits on the server listener thread - # - def wait - self.listener_thread.join if self.listener_thread - end - - ## - # - # Callback procedures. - # - ## - - # - # This callback procedure can be set and will be called when new clients - # connect. - # - attr_accessor :on_client_connect_proc - # - # This callback procedure can be set and will be called when clients - # have data to be processed. - # - attr_accessor :on_client_data_proc - # - # This callback procedure can be set and will be called when a client - # disconnects from the server. - # - attr_accessor :on_client_close_proc - - attr_accessor :clients # :nodoc: - attr_accessor :listener_thread, :clients_thread # :nodoc: - attr_accessor :client_waiter - -protected - - # - # This method monitors the listener socket for new connections and calls - # the +on_client_connect+ callback routine. - # - def monitor_listener - - while true - begin - cli = accept - if not cli - elog("The accept() returned nil in stream server listener monitor: #{fd.inspect}") - ::IO.select(nil, nil, nil, 0.10) - next - end - - # Append to the list of clients - self.clients << cli - - # Initialize the connection processing - on_client_connect(cli) - - # Notify the client monitor - self.client_waiter.push(cli) - - # Skip exceptions caused by accept() [ SSL ] - rescue ::EOFError, ::Errno::ECONNRESET, ::Errno::ENOTCONN, ::Errno::ECONNABORTED - rescue ::Interrupt - raise $! - rescue ::Exception - elog("Error in stream server server monitor: #{$!}") - rlog(ExceptionCallStack) - break - end - end - end - - # - # This method monitors client connections for data and calls the - # +on_client_data+ routine when new data arrives. - # - def monitor_clients - begin - - # Wait for a notify if our client list is empty - if (clients.length == 0) - self.client_waiter.pop - next - end - - sd = Rex::ThreadSafe.select(clients, nil, nil, nil) - - sd[0].each { |cfd| - begin - on_client_data(cfd) - rescue ::EOFError, ::Errno::ECONNRESET, ::Errno::ENOTCONN, ::Errno::ECONNABORTED - on_client_close(cfd) - close_client(cfd) - rescue ::Interrupt - raise $! - rescue ::Exception - close_client(cfd) - elog("Error in stream server client monitor: #{$!}") - rlog(ExceptionCallStack) - - end - } - - rescue ::Rex::StreamClosedError => e - # Remove the closed stream from the list - clients.delete(e.stream) - rescue ::Interrupt - raise $! - rescue ::Exception - elog("Error in stream server client monitor: #{$!}") - rlog(ExceptionCallStack) - end while true - end - -end - -end -end - diff --git a/lib/rex/parser/x509_certificate.rb b/lib/rex/parser/x509_certificate.rb deleted file mode 100644 index 61a0a4b179..0000000000 --- a/lib/rex/parser/x509_certificate.rb +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: binary -*- - -require 'openssl' - -module Rex -module Parser - -### -# -# This class parses the contents of a PEM-encoded X509 certificate file containing -# a private key, a public key, and any appended glue certificates. -# -### -class X509Certificate - - # - # Parse a certificate in unified PEM format that contains a private key and - # one or more certificates. The first certificate is the primary, while any - # additional certificates are treated as intermediary certificates. This emulates - # the behavior of web servers like nginx. - # - # @param [String] ssl_cert - # @return [String, String, Array] - def self.parse_pem(ssl_cert) - cert = nil - key = nil - chain = nil - - certs = [] - ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| - if pem =~ /PRIVATE KEY/ - key = OpenSSL::PKey::RSA.new(pem) - elsif pem =~ /CERTIFICATE/ - certs << OpenSSL::X509::Certificate.new(pem) - end - end - - cert = certs.shift - if certs.length > 0 - chain = certs - end - - [key, cert, chain] - end - - # - # Parse a certificate in unified PEM format from a file - # - # @param [String] ssl_cert_file - # @return [String, String, Array] - def self.parse_pem_file(ssl_cert_file) - data = '' - ::File.open(ssl_cert_file, 'rb') do |fd| - data << fd.read(fd.stat.size) - end - parse_pem(data) - end - - # - # Parse a certificate in unified PEM format and retrieve - # the SHA1 hash. - # - # @param [String] ssl_cert - # @return [String] - def self.get_cert_hash(ssl_cert) - hcert = parse_pem(ssl_cert) - - unless hcert and hcert[0] and hcert[1] - raise ArgumentError, "Could not parse a private key and certificate" - end - - Rex::Text.sha1_raw(hcert[1].to_der) - end - - # - # Parse a file that contains a certificate in unified PEM - # format and retrieve the SHA1 hash. - # - # @param [String] ssl_cert_file - # @return [String] - def self.get_cert_file_hash(ssl_cert_file) - data = '' - ::File.open(ssl_cert_file, 'rb') do |fd| - data << fd.read(fd.stat.size) - end - get_cert_hash(data) - end - -end - -end -end diff --git a/lib/rex/payloads/meterpreter/config.rb b/lib/rex/payloads/meterpreter/config.rb index f28ef71662..28452b6385 100644 --- a/lib/rex/payloads/meterpreter/config.rb +++ b/lib/rex/payloads/meterpreter/config.rb @@ -2,7 +2,7 @@ require 'msf/core/payload/uuid' require 'msf/core/payload/windows' require 'msf/core/reflective_dll_loader' -require 'rex/parser/x509_certificate' +require 'rex/socket/x509_certificate' class Rex::Payloads::Meterpreter::Config diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index 44c952e6ac..d951181711 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -13,7 +13,7 @@ require 'msf/core/payload/uuid' require 'rex/payloads/meterpreter/uri_checksum' # certificate hash checking -require 'rex/parser/x509_certificate' +require 'rex/socket/x509_certificate' module Rex module Post @@ -686,7 +686,7 @@ class ClientCore < Extension request.add_tlv(TLV_TYPE_TRANS_UA, opts[:ua]) if transport == METERPRETER_TRANSPORT_HTTPS && opts[:cert] - hash = Rex::Parser::X509Certificate.get_cert_file_hash(opts[:cert]) + hash = Rex::Socket::X509Certificate.get_cert_file_hash(opts[:cert]) request.add_tlv(TLV_TYPE_TRANS_CERT_HASH, hash) end diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb deleted file mode 100644 index 88e125462c..0000000000 --- a/lib/rex/socket.rb +++ /dev/null @@ -1,796 +0,0 @@ -# -*- coding: binary -*- -require 'socket' -require 'thread' -require 'resolv' -require 'rex/exceptions' - -module Rex - -### -# -# Base class for all sockets. -# -### -module Socket - - module Comm - end - - require 'rex/socket/parameters' - require 'rex/socket/tcp' - require 'rex/socket/tcp_server' - - require 'rex/socket/comm' - require 'rex/socket/comm/local' - - require 'rex/socket/switch_board' - require 'rex/socket/subnet_walker' - require 'rex/socket/range_walker' - - ## - # - # Factory methods - # - ## - - # - # Create a socket instance using the supplied parameter hash. - # - def self.create(opts = {}) - return create_param(Rex::Socket::Parameters.from_hash(opts)) - end - - # - # Create a socket using the supplied Rex::Socket::Parameter instance. - # - def self.create_param(param) - return param.comm.create(param) - end - - # - # Create a TCP socket using the supplied parameter hash. - # - def self.create_tcp(opts = {}) - return create_param(Rex::Socket::Parameters.from_hash(opts.merge('Proto' => 'tcp'))) - end - - # - # Create a TCP server socket using the supplied parameter hash. - # - def self.create_tcp_server(opts = {}) - return create_tcp(opts.merge('Server' => true)) - end - - # - # Create a UDP socket using the supplied parameter hash. - # - def self.create_udp(opts = {}) - return create_param(Rex::Socket::Parameters.from_hash(opts.merge('Proto' => 'udp'))) - end - - # - # Create a IP socket using the supplied parameter hash. - # - def self.create_ip(opts = {}) - return create_param(Rex::Socket::Parameters.from_hash(opts.merge('Proto' => 'ip'))) - end - - - # - # Common Regular Expressions - # - - MATCH_IPV6 = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/ - - MATCH_IPV4 = /^\s*(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))\s*$/ - - MATCH_IPV4_PRIVATE = /^\s*(?:10\.|192\.168|172.(?:1[6-9]|2[0-9]|3[01])\.|169\.254)/ - - ## - # - # Serialization - # - ## - - - # Cache our IPv6 support flag - @@support_ipv6 = nil - - # - # Determine whether we support IPv6 - # - def self.support_ipv6? - return @@support_ipv6 if not @@support_ipv6.nil? - - @@support_ipv6 = false - - if (::Socket.const_defined?('AF_INET6')) - begin - s = ::Socket.new(::Socket::AF_INET6, ::Socket::SOCK_DGRAM, ::Socket::IPPROTO_UDP) - s.close - @@support_ipv6 = true - rescue - end - end - - return @@support_ipv6 - end - - # - # Determine whether this is an IPv4 address - # - def self.is_ipv4?(addr) - ( addr =~ MATCH_IPV4 ) ? true : false - end - - # - # Determine whether this is an IPv6 address - # - def self.is_ipv6?(addr) - ( addr =~ MATCH_IPV6 ) ? true : false - end - - # - # Checks to see if the supplied address is in "dotted" form - # - def self.dotted_ip?(addr) - # Match IPv6 - return true if (support_ipv6? and addr =~ MATCH_IPV6) - - # Match IPv4 - return true if (addr =~ MATCH_IPV4) - - false - end - - # - # Return true if +addr+ is within the ranges specified in RFC1918, or - # RFC5735/RFC3927 - # - def self.is_internal?(addr) - if self.dotted_ip?(addr) - addr =~ MATCH_IPV4_PRIVATE - else - false - end - end - - # Get the first address returned by a DNS lookup for +hostname+. - # - # @see .getaddresses - # - # @param (see .getaddresses) - # @return [String] ASCII IP address - def self.getaddress(hostname, accept_ipv6 = true) - getaddresses(hostname, accept_ipv6).first - end - - # - # Wrapper for +::Socket.gethostbyname+ that takes special care to see if the - # supplied address is already an ASCII IP address. This is necessary to - # prevent blocking while waiting on a DNS reverse lookup when we already - # have what we need. - # - # @param hostname [String] A hostname or ASCII IP address - # @return [Array] - def self.getaddresses(hostname, accept_ipv6 = true) - if hostname =~ MATCH_IPV4 or (accept_ipv6 and hostname =~ MATCH_IPV6) - return [hostname] - end - - res = ::Socket.gethostbyname(hostname) - return [] if not res - - # Shift the first three elements out, leaving just the list of - # addresses - res.shift # name - res.shift # alias hostnames - res.shift # address_family - - # Rubinius has a bug where gethostbyname returns dotted quads instead of - # NBO, but that's what we want anyway, so just short-circuit here. - if res[0] =~ MATCH_IPV4 || res[0] =~ MATCH_IPV6 - unless accept_ipv6 - res.reject!{ |ascii| ascii =~ MATCH_IPV6 } - end - else - unless accept_ipv6 - res.reject!{ |nbo| nbo.length != 4 } - end - res.map!{ |nbo| self.addr_ntoa(nbo) } - end - - res - end - - # - # Wrapper for Socket.gethostbyname which takes into account whether or not - # an IP address is supplied. If it is, then reverse DNS resolution does - # not occur. This is done in order to prevent delays, such as would occur - # on Windows. - # - def self.gethostbyname(host) - if (is_ipv4?(host)) - return [ host, [], 2, host.split('.').map{ |c| c.to_i }.pack("C4") ] - end - - if is_ipv6?(host) - # pop off the scopeid since gethostbyname isn't smart enough to - # deal with it. - host, _ = host.split('%', 2) - end - - ::Socket.gethostbyname(host) - end - - # - # Create a sockaddr structure using the supplied IP address, port, and - # address family - # - def self.to_sockaddr(ip, port) - - if (ip == '::ffff:0.0.0.0') - ip = support_ipv6?() ? '::' : '0.0.0.0' - end - - return ::Socket.pack_sockaddr_in(port, ip) - end - - # - # Returns the address family, host, and port of the supplied sockaddr as - # [ af, host, port ] - # - def self.from_sockaddr(saddr) - port, host = ::Socket::unpack_sockaddr_in(saddr) - af = ::Socket::AF_INET - if (support_ipv6?() and is_ipv6?(host)) - af = ::Socket::AF_INET6 - end - return [ af, host, port ] - end - - # - # Resolves a host to raw network-byte order. - # - def self.resolv_nbo(host) - self.gethostbyname( Rex::Socket.getaddress(host, true) )[3] - end - - # - # Resolves a host to raw network-byte order. - # - def self.resolv_nbo_list(host) - Rex::Socket.getaddresses(host).map{|addr| self.gethostbyname(addr)[3] } - end - - # - # Resolves a host to a network-byte order ruby integer. - # - def self.resolv_nbo_i(host) - addr_ntoi(resolv_nbo(host)) - end - - # - # Resolves a host to a list of network-byte order ruby integers. - # - def self.resolv_nbo_i_list(host) - resolv_nbo_list(host).map{|addr| addr_ntoi(addr) } - end - - # - # Converts an ASCII IP address to a CIDR mask. Returns - # nil if it's not convertable. - # - def self.addr_atoc(mask) - mask_i = resolv_nbo_i(mask) - cidr = nil - 0.upto(32) do |i| - if ((1 << i)-1) << (32-i) == mask_i - cidr = i - break - end - end - return cidr - end - - # - # Resolves a CIDR bitmask into a dotted-quad. Returns - # nil if it's not convertable. - # - def self.addr_ctoa(cidr) - return nil unless (0..32) === cidr.to_i - addr_itoa(((1 << cidr)-1) << 32-cidr) - end - - # - # Resolves a host to a dotted address. - # - def self.resolv_to_dotted(host) - addr_ntoa(addr_aton(host)) - end - - # - # Converts a ascii address into an integer - # - def self.addr_atoi(addr) - resolv_nbo_i(addr) - end - - # - # Converts a ascii address into a list of addresses - # - def self.addr_atoi_list(addr) - resolv_nbo_i_list(addr) - end - - # - # Converts an integer address into ascii - # - # @param (see #addr_iton) - # @return (see #addr_ntoa) - def self.addr_itoa(addr, v6=false) - nboa = addr_iton(addr, v6) - - addr_ntoa(nboa) - end - - # - # Converts a ascii address to network byte order - # - def self.addr_aton(addr) - resolv_nbo(addr) - end - - # - # Converts a network byte order address to ascii - # - # @param addr [String] Packed network-byte-order address - # @return [String] Human readable IP address. - def self.addr_ntoa(addr) - # IPv4 - if (addr.length == 4) - return addr.unpack('C4').join('.') - end - - # IPv6 - if (addr.length == 16) - return compress_address(addr.unpack('n8').map{ |c| "%x" % c }.join(":")) - end - - raise RuntimeError, "Invalid address format" - end - - # - # Implement zero compression for IPv6 addresses. - # Uses the compression method from Marco Ceresa's IPAddress GEM - # - # @see https://github.com/bluemonk/ipaddress/blob/master/lib/ipaddress/ipv6.rb - # - # @param addr [String] Human readable IPv6 address - # @return [String] Human readable IPv6 address with runs of 0s removed - def self.compress_address(addr) - return addr unless is_ipv6?(addr) - addr = addr.dup - while true - break if addr.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::') - break if addr.sub!(/\b0:0:0:0:0:0:0\b/, ':') - break if addr.sub!(/\b0:0:0:0:0:0\b/, ':') - break if addr.sub!(/\b0:0:0:0:0\b/, ':') - break if addr.sub!(/\b0:0:0:0\b/, ':') - break if addr.sub!(/\b0:0:0\b/, ':') - break if addr.sub!(/\b0:0\b/, ':') - break - end - addr.sub(/:{3,}/, '::') - end - - # - # Converts a network byte order address to an integer - # - def self.addr_ntoi(addr) - - bits = addr.unpack("N*") - - if (bits.length == 1) - return bits[0] - end - - if (bits.length == 4) - val = 0 - bits.each_index { |i| val += ( bits[i] << (96 - (i * 32)) ) } - return val - end - - raise RuntimeError, "Invalid address format" - end - - # - # Converts an integer into a network byte order address - # - # @param addr [Numeric] The address as a number - # @param v6 [Boolean] Whether +addr+ is IPv6 - def self.addr_iton(addr, v6=false) - if(addr < 0x100000000 && !v6) - return [addr].pack('N') - else - w = [] - w[0] = (addr >> 96) & 0xffffffff - w[1] = (addr >> 64) & 0xffffffff - w[2] = (addr >> 32) & 0xffffffff - w[3] = addr & 0xffffffff - return w.pack('N4') - end - end - - # - # Converts a colon-delimited MAC address into a 6-byte binary string - # - def self.eth_aton(mac) - mac.split(":").map{|c| c.to_i(16) }.pack("C*") - end - - # - # Converts a 6-byte binary string into a colon-delimited MAC address - # - def self.eth_ntoa(bin) - bin.unpack("C6").map{|x| "%.2x" % x }.join(":").upcase - end - - # - # Converts a CIDR subnet into an array (base, bcast) - # - def self.cidr_crack(cidr, v6=false) - tmp = cidr.split('/') - - tst,scope = tmp[0].split("%",2) - scope = "%" + scope if scope - scope ||= "" - - addr = addr_atoi(tst) - - bits = 32 - mask = 0 - use6 = false - - if (addr > 0xffffffff or v6 or cidr =~ /:/) - use6 = true - bits = 128 - end - - mask = (2 ** bits) - (2 ** (bits - tmp[1].to_i)) - base = addr & mask - - stop = base + (2 ** (bits - tmp[1].to_i)) - 1 - return [self.addr_itoa(base, use6) + scope, self.addr_itoa(stop, use6) + scope] - end - - # - # Converts a netmask (255.255.255.240) into a bitmask (28). This is the - # lame kid way of doing it. - # - def self.net2bitmask(netmask) - - nmask = resolv_nbo(netmask) - imask = addr_ntoi(nmask) - bits = 32 - - if (imask > 0xffffffff) - bits = 128 - end - - 0.upto(bits-1) do |bit| - p = 2 ** bit - return (bits - bit) if ((imask & p) == p) - end - - 0 - end - - # - # Converts a bitmask (28) into a netmask (255.255.255.240) - # - def self.bit2netmask(bitmask, ipv6=false) - if bitmask > 32 or ipv6 - i = ((~((2 ** (128 - bitmask)) - 1)) & (2**128-1)) - n = Rex::Socket.addr_iton(i, true) - return Rex::Socket.addr_ntoa(n) - else - [ (~((2 ** (32 - bitmask)) - 1)) & 0xffffffff ].pack('N').unpack('CCCC').join('.') - end - end - - - def self.portspec_crack(pspec) - portspec_to_portlist(pspec) - end - - # - # Converts a port specification like "80,21-25,!24,443" into a sorted, - # unique array of valid port numbers like [21,22,23,25,80,443] - # - def self.portspec_to_portlist(pspec) - ports = [] - remove = [] - - # Build ports array from port specification - pspec.split(/,/).each do |item| - target = ports - - item.strip! - - if item.start_with? '!' - item.delete! '!' - target = remove - end - - start, stop = item.split(/-/).map { |p| p.to_i } - - start ||= 0 - stop ||= item.match(/-/) ? 65535 : start - - start, stop = stop, start if stop < start - - start.upto(stop) { |p| target << p } - end - - if ports.empty? and not remove.empty? then - ports = 1.upto 65535 - end - - # Sort, and remove dups and invalid ports - ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 or remove.include? p } - end - - # - # Converts a port list like [1,2,3,4,5,100] into a - # range specification like "1-5,100" - # - def self.portlist_to_portspec(parr) - ranges = [] - range = [] - lastp = nil - - parr.uniq.sort{|a,b| a<=>b}.map{|a| a.to_i}.each do |n| - next if (n < 1 or n > 65535) - if not lastp - range = [n] - lastp = n - next - end - - if lastp == n - 1 - range << n - else - ranges << range - range = [n] - end - lastp = n - end - - ranges << range - ranges.delete(nil) - ranges.uniq.map{|x| x.length == 1 ? "#{x[0]}" : "#{x[0]}-#{x[-1]}"}.join(",") - end - - ## - # - # Utility class methods - # - ## - - # - # This method does NOT send any traffic to the destination, instead, it uses a - # "bound" UDP socket to determine what source address we would use to - # communicate with the specified destination. The destination defaults to - # Google's DNS server to make the standard behavior determine which IP - # we would use to communicate with the internet. - # - def self.source_address(dest='8.8.8.8', comm = ::Rex::Socket::Comm::Local) - begin - s = self.create_udp( - 'PeerHost' => dest, - 'PeerPort' => 31337, - 'Comm' => comm - ) - r = s.getsockname[1] - s.close - - # Trim off the trailing interface ID for link-local IPv6 - return r.split('%').first - rescue ::Exception - return '127.0.0.1' - end - end - - # - # Identifies the link-local address of a given interface (if IPv6 is enabled) - # - def self.ipv6_link_address(intf) - r = source_address("FF02::1%#{intf}") - return nil if r.nil? || r !~ /^fe80/i - r - end - - # - # Identifies the mac address of a given interface (if IPv6 is enabled) - # - def self.ipv6_mac(intf) - r = ipv6_link_address(intf) - return if not r - raw = addr_aton(r)[-8, 8] - (raw[0,3] + raw[5,3]).unpack("C*").map{|c| "%.2x" % c}.join(":") - end - - # - # Create a TCP socket pair. - # - # sf: This create a socket pair using native ruby sockets and will work - # on Windows where ::Socket.pair is not implemented. - # Note: OpenSSL requires native ruby sockets for its io. - # - # Note: Even though sub-threads are smashing the parent threads local, there - # is no concurrent use of the same locals and this is safe. - def self.tcp_socket_pair - lsock = nil - rsock = nil - laddr = '127.0.0.1' - lport = 0 - threads = [] - mutex = ::Mutex.new - - threads << Rex::ThreadFactory.spawn('TcpSocketPair', false) { - server = nil - mutex.synchronize { - threads << Rex::ThreadFactory.spawn('TcpSocketPairClient', false) { - mutex.synchronize { - rsock = ::TCPSocket.new( laddr, lport ) - } - } - server = ::TCPServer.new(laddr, 0) - if (server.getsockname =~ /127\.0\.0\.1:/) - # JRuby ridiculousness - caddr, lport = server.getsockname.split(":") - caddr = caddr[1,caddr.length] - lport = lport.to_i - else - # Sane implementations where Socket#getsockname returns a - # sockaddr - lport, caddr = ::Socket.unpack_sockaddr_in( server.getsockname ) - end - } - lsock, _ = server.accept - server.close - } - - threads.each { |t| t.join } - - return [lsock, rsock] - end - - # - # Create a UDP socket pair using native ruby UDP sockets. - # - def self.udp_socket_pair - laddr = '127.0.0.1' - - lsock = ::UDPSocket.new - lsock.bind( laddr, 0 ) - - rsock = ::UDPSocket.new - rsock.bind( laddr, 0 ) - - rsock.connect( *lsock.addr.values_at(3,1) ) - - lsock.connect( *rsock.addr.values_at(3,1) ) - - return [lsock, rsock] - end - - - ## - # - # Class initialization - # - ## - - # - # Initialize general socket parameters. - # - def initsock(params = nil) - if (params) - self.peerhost = params.peerhost - self.peerport = params.peerport - self.localhost = params.localhost - self.localport = params.localport - self.context = params.context || {} - self.ipv = params.v6 ? 6 : 4 - end - end - - # - # By default, all sockets are themselves selectable file descriptors. - # - def fd - self - end - - # - # Returns local connection information. - # - def getsockname - Socket.from_sockaddr(super) - end - - # - # Wrapper around getsockname - # - def getlocalname - getsockname - end - - # - # Return peer connection information. - # - def getpeername_as_array - peer_name = nil - begin - peer_name = Socket.from_sockaddr(self.getpeername) - rescue ::Errno::EINVAL => e - # Ruby's getpeername method may call rb_sys_fail("getpeername(2)") - elog("#{e.message} (#{e.class})#{e.backtrace * "\n"}\n", 'core', LEV_3) - end - - return peer_name - end - - # - # Returns a string that indicates the type of the socket, such as 'tcp'. - # - def type? - raise NotImplementedError, "Socket type is not supported." - end - - # - # The peer host of the connected socket. - # - attr_reader :peerhost - # - # The peer port of the connected socket. - # - attr_reader :peerport - # - # The local host of the connected socket. - # - attr_reader :localhost - # - # The local port of the connected socket. - # - attr_reader :localport - # - # The IP version of the socket - # - attr_reader :ipv - # - # Contextual information that describes the source and other - # instance-specific attributes. This comes from the param.context - # attribute. - # - attr_reader :context - -protected - - attr_writer :peerhost, :peerport, :localhost, :localport # :nodoc: - attr_writer :context # :nodoc: - attr_writer :ipv # :nodoc: - -end - -end - -# -# Globalized socket constants -# -SHUT_RDWR = ::Socket::SHUT_RDWR -SHUT_RD = ::Socket::SHUT_RD -SHUT_WR = ::Socket::SHUT_WR - diff --git a/lib/rex/socket/comm.rb b/lib/rex/socket/comm.rb deleted file mode 100644 index b92f4d311a..0000000000 --- a/lib/rex/socket/comm.rb +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' - -module Rex -module Socket - -### -# -# This mixin provides the basic interface that a derived class must implement -# in order to be a compatible comm class. The base comm class also supports -# registering event handlers that can be notified when sockets are being -# created and have been created. This allows code to extend sockets on -# creation from the single point that they are created. -# -### -module Comm - - ### - # - # This mixin provides stubs for event notification handlers that can be - # registered with a Comm factory to be called when various events occur, - # such as socket instantiation. - # - ### - module Events - - # - # This callback is notified when a socket is being created and is passed - # the parameters that will be used to create it. - # - def on_before_socket_create(comm, param) - end - - # - # This callback is notified when a new socket is created and the - # parameters that were used to create it. This provides the callback - # with a chance to extend or otherwise modify the socket before it's - # passed on to the actual requestor. - # - def on_socket_created(comm, sock, param) - end - - end - - # - # Creates a compatible socket based on the supplied uniform parameters. - # - def self.create(param) - raise NotImplementedError - end - - # - # Indicates whether or not this comm can be chained with other chainable - # comms. This is particularly important for things like Proxy Comms that - # can be proxied through one another. The semantics of this are currently - # undefined and will probably need some more thought. - # - def chainable? - false - end - - # - # Registers an event handler that implements the Rex::Socket::Comm::Event - # interface in at least some fashion. Event handlers are notified when - # sockets are created through the Comm instance that they register against. - # - def register_event_handler(handler) - if (handlers == nil) - self.handlers = [] - end - - self.handlers << handler - end - - # - # Deregisters a previously registered event handler. - # - def deregister_event_handler(handler) - if (handlers) - handlers.delete(handler) - end - end - - # - # Enumerates each registered event handler so that they can be notified of - # an event. - # - def each_event_handler(&block) - if (handlers) - handlers.each(&block) - end - end - - # - # Notifies handlers of the before socket create event. - # - def notify_before_socket_create(comm, param) - each_event_handler() { |handler| - handler.on_before_socket_create(comm, param) - } - end - - # - # Notifies handlers of the socket created event. - # - def notify_socket_created(comm, sock, param) - each_event_handler() { |handler| - handler.on_socket_created(comm, sock, param) - } - end - -protected - - attr_accessor :handlers # :nodoc: - attr_accessor :handlers_rwlock # :nodoc: - -end - -end -end diff --git a/lib/rex/socket/comm/local.rb b/lib/rex/socket/comm/local.rb deleted file mode 100644 index 787d6f3fd5..0000000000 --- a/lib/rex/socket/comm/local.rb +++ /dev/null @@ -1,529 +0,0 @@ -# -*- coding: binary -*- -require 'singleton' -require 'rex/socket' -require 'rex/socket/tcp' -require 'rex/socket/ssl_tcp' -require 'rex/socket/ssl_tcp_server' -require 'rex/socket/udp' -require 'rex/socket/ip' -require 'timeout' - -### -# -# Local communication class factory. -# -### -class Rex::Socket::Comm::Local - - include Singleton - include Rex::Socket::Comm - - # - # Creates an instance of a socket using the supplied parameters. - # - def self.create(param) - - # Work around jRuby socket implementation issues - if(RUBY_PLATFORM == 'java') - return self.create_jruby(param) - end - - case param.proto - when 'tcp' - return create_by_type(param, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP) - when 'udp' - return create_by_type(param, ::Socket::SOCK_DGRAM, ::Socket::IPPROTO_UDP) - when 'ip' - return create_ip(param) - else - raise Rex::UnsupportedProtocol.new(param.proto), caller - end - end - - # - # Creates an instance of a socket using the supplied parameters. - # Use various hacks to make this work with jRuby - # - def self.create_jruby(param) - sock = nil - - # Notify handlers of the before socket create event. - self.instance.notify_before_socket_create(self, param) - - case param.proto - when 'tcp' - if (param.server?) - sock = TCPServer.new(param.localport, param.localhost) - klass = Rex::Socket::TcpServer - if (param.ssl) - klass = Rex::Socket::SslTcpServer - end - sock.extend(klass) - - else - sock = TCPSocket.new(param.peerhost, param.peerport) - klass = Rex::Socket::Tcp - if (param.ssl) - klass = Rex::Socket::SslTcp - end - sock.extend(klass) - end - when 'udp' - if (param.server?) - sock = UDPServer.new(param.localport, param.localhost) - klass = Rex::Socket::UdpServer - sock.extend(klass) - else - sock = UDPSocket.new(param.peerhost, param.peerport) - klass = Rex::Socket::Udp - sock.extend(klass) - end - else - raise Rex::UnsupportedProtocol.new(param.proto), caller - end - - sock.initsock(param) - self.instance.notify_socket_created(self, sock, param) - return sock - end - - - # - # Creates a raw IP socket using the supplied Parameter instance. - # Special-cased because of how different it is from UDP/TCP - # - def self.create_ip(param) - self.instance.notify_before_socket_create(self, param) - - sock = ::Socket.open(::Socket::PF_INET, ::Socket::SOCK_RAW, ::Socket::IPPROTO_RAW) - sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_HDRINCL, 1) - - # Configure broadcast support - sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true) - - if (param.bare? == false) - sock.extend(::Rex::Socket::Ip) - sock.initsock(param) - end - - self.instance.notify_socket_created(self, sock, param) - - sock - end - - - # - # Creates a socket using the supplied Parameter instance. - # - def self.create_by_type(param, type, proto = 0) - - # Whether to use IPv6 addressing - usev6 = false - - # Detect IPv6 addresses and enable IPv6 accordingly - if ( Rex::Socket.support_ipv6?()) - - # Allow the caller to force IPv6 - if (param.v6) - usev6 = true - end - - # Force IPv6 mode for non-connected UDP sockets - if (type == ::Socket::SOCK_DGRAM and not param.peerhost) - # FreeBSD allows IPv6 socket creation, but throws an error on sendto() - # Windows 7 SP1 and newer also fail to sendto with IPv6 udp sockets - unless Rex::Compat.is_freebsd or Rex::Compat.is_windows - usev6 = true - end - end - - local = Rex::Socket.resolv_nbo(param.localhost) if param.localhost - peer = Rex::Socket.resolv_nbo(param.peerhost) if param.peerhost - - if (local and local.length == 16) - usev6 = true - end - - if (peer and peer.length == 16) - usev6 = true - end - - if (usev6) - if (local and local.length == 4) - if (local == "\x00\x00\x00\x00") - param.localhost = '::' - elsif (local == "\x7f\x00\x00\x01") - param.localhost = '::1' - else - param.localhost = '::ffff:' + Rex::Socket.getaddress(param.localhost, true) - end - end - - if (peer and peer.length == 4) - if (peer == "\x00\x00\x00\x00") - param.peerhost = '::' - elsif (peer == "\x7f\x00\x00\x01") - param.peerhost = '::1' - else - param.peerhost = '::ffff:' + Rex::Socket.getaddress(param.peerhost, true) - end - end - - param.v6 = true - end - else - # No IPv6 support - param.v6 = false - end - - # Notify handlers of the before socket create event. - self.instance.notify_before_socket_create(self, param) - - # Create the socket - sock = nil - if (param.v6) - sock = ::Socket.new(::Socket::AF_INET6, type, proto) - else - sock = ::Socket.new(::Socket::AF_INET, type, proto) - end - - # Bind to a given local address and/or port if they are supplied - if param.localport or param.localhost - begin - sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true) - sock.bind(Rex::Socket.to_sockaddr(param.localhost, param.localport)) - - rescue ::Errno::EADDRNOTAVAIL,::Errno::EADDRINUSE - sock.close - raise Rex::BindFailed.new(param.localhost, param.localport), caller - end - end - - # Configure broadcast support for all datagram sockets - if (type == ::Socket::SOCK_DGRAM) - sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true) - end - - # If a server TCP instance is being created... - if (param.server?) - sock.listen(256) - - if (param.bare? == false) - klass = Rex::Socket::TcpServer - if (param.ssl) - klass = Rex::Socket::SslTcpServer - end - sock.extend(klass) - - sock.initsock(param) - end - # Otherwise, if we're creating a client... - else - chain = [] - - # If we were supplied with host information - if (param.peerhost) - - # A flag that indicates whether we need to try multiple scopes - retry_scopes = false - - # Always retry with link-local IPv6 addresses - if Rex::Socket.is_ipv6?( param.peerhost ) and param.peerhost =~ /^fe80::/ - retry_scopes = true - end - - # Prepare a list of scope IDs to try when connecting to - # link-level addresses. Read from /proc if it is available, - # otherwise increment through the first 255 IDs. - @@ip6_lla_scopes ||= [] - - if @@ip6_lla_scopes.length == 0 and retry_scopes - - # Linux specific interface lookup code - if ::File.exist?( "/proc/self/net/igmp6" ) - ::File.open("/proc/self/net/igmp6") do |fd| - fd.each_line do |line| - line = line.strip - tscope, tint, junk = line.split(/\s+/, 3) - next if not tint - - # Specifying lo in any connect call results in the socket - # being unusable, even if the correct interface is set. - next if tint == "lo" - - @@ip6_lla_scopes << tscope - end - end - else - # Other Unix-like platforms should support a raw scope ID - [*(1 .. 255)].map{ |x| @@ip6_lla_scopes << x.to_s } - end - end - - ip6_scope_idx = 0 - ip = param.peerhost - port = param.peerport - - if param.proxies - chain = param.proxies.dup - chain.push(['host',param.peerhost,param.peerport]) - ip = chain[0][1] - port = chain[0][2].to_i - end - - begin - - begin - Timeout.timeout(param.timeout) do - sock.connect(Rex::Socket.to_sockaddr(ip, port)) - end - rescue ::Timeout::Error - raise ::Errno::ETIMEDOUT - end - - rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL,::Errno::ENOPROTOOPT - - # Rescue errors caused by a bad Scope ID for a link-local address - if retry_scopes and @@ip6_lla_scopes[ ip6_scope_idx ] - ip = param.peerhost + "%" + @@ip6_lla_scopes[ ip6_scope_idx ] - ip6_scope_idx += 1 - retry - end - - sock.close - raise Rex::HostUnreachable.new(ip, port), caller - - rescue ::Errno::EADDRNOTAVAIL,::Errno::EADDRINUSE - sock.close - raise Rex::InvalidDestination.new(ip, port), caller - - rescue Errno::ETIMEDOUT - sock.close - raise Rex::ConnectionTimeout.new(ip, port), caller - - rescue ::Errno::ECONNRESET,::Errno::ECONNREFUSED,::Errno::ENOTCONN,::Errno::ECONNABORTED - sock.close - # Report the actual thing we were trying to connect to here, not - # param.peerhost, since that's the eventual target at the end of the - # proxy chain - raise Rex::ConnectionRefused.new(ip, port.to_i), caller - end - end - - if (param.bare? == false) - case param.proto - when 'tcp' - klass = Rex::Socket::Tcp - sock.extend(klass) - sock.initsock(param) - when 'udp' - sock.extend(Rex::Socket::Udp) - sock.initsock(param) - end - end - - if chain.size > 1 - chain.each_with_index { - |proxy, i| - next_hop = chain[i + 1] - if next_hop - proxy(sock, proxy[0], next_hop[1], next_hop[2]) - end - } - end - - # Now extend the socket with SSL and perform the handshake - if(param.bare? == false and param.ssl) - klass = Rex::Socket::SslTcp - sock.extend(klass) - sock.initsock(param) - end - - - end - - # Notify handlers that a socket has been created. - self.instance.notify_socket_created(self, sock, param) - - sock - end - - def self.proxy(sock, type, host, port) - case type.downcase - when 'sapni' - packet_type = 'NI_ROUTE' - route_info_version = 2 - ni_version = 39 - num_of_entries = 2 - talk_mode = 1 # ref: http://help.sap.com/saphelp_dimp50/helpdata/En/f8/bb960899d743378ccb8372215bb767/content.htm - num_rest_nodes = 1 - - _af, shost, sport = sock.getpeername_as_array - first_route_item = [shost, 0, sport, 0, 0].pack("A*CA*cc") - route_data = [first_route_item.length, first_route_item].pack("NA*") - route_data << [host, 0, port.to_s, 0, 0].pack("A*CA*cc") - - ni_packet = [ - packet_type, - 0, - route_info_version, - ni_version, - num_of_entries, - talk_mode, - 0, - 0, - num_rest_nodes - ].pack("A8c8") - # Add the data block, according to sap documentation: - # A 4-byte header precedes each data block. These 4 bytes give the - # length of the data block (length without leading 4 bytes) - # The data block (the route data) - ni_packet << [route_data.length - 4].pack('N') + route_data - # Now that we've built the whole packet, prepend its length before writing it to the wire - ni_packet = [ni_packet.length].pack('N') + ni_packet - - size = sock.put(ni_packet) - - if size != ni_packet.length - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller - end - - begin - ret_len = sock.get_once(4, 30).unpack('N')[0] - if ret_len and ret_len != 0 - ret = sock.get_once(ret_len, 30) - end - rescue IOError - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller - end - - if ret and ret.length < 4 - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller - end - - if ret =~ /NI_RTERR/ - case ret - when /timed out/ - raise Rex::ConnectionProxyError.new(host, port, type, "Connection to remote host #{host} timed out") - when /refused/ - raise Rex::ConnectionProxyError.new(host, port, type, "Connection to remote port #{port} closed") - when /denied/ - raise Rex::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} blocked by ACL") - else - raise Rex::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} failed (Unknown fail)") - end - elsif ret =~ /NI_PONG/ - # success case - # would like to print this "[*] remote native connection to #{host}:#{port} established\n" - else - raise Rex::ConnectionProxyError.new(host, port, type, "Connection to #{host}:#{port} failed (Unknown fail)") - end - - when 'http' - setup = "CONNECT #{host}:#{port} HTTP/1.0\r\n\r\n" - size = sock.put(setup) - if (size != setup.length) - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller - end - - begin - ret = sock.get_once(39,30) - rescue IOError - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller - end - - if ret.nil? - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller - end - - resp = Rex::Proto::Http::Response.new - resp.update_cmd_parts(ret.split(/\r?\n/)[0]) - - if resp.code != 200 - raise Rex::ConnectionProxyError.new(host, port, type, "The proxy returned a non-OK response"), caller - end - when 'socks4' - setup = [4,1,port.to_i].pack('CCn') + Socket.gethostbyname(host)[3] + Rex::Text.rand_text_alpha(rand(8)+1) + "\x00" - size = sock.put(setup) - if (size != setup.length) - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller - end - - begin - ret = sock.get_once(8, 30) - rescue IOError - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller - end - - if (ret.nil? or ret.length < 8) - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller - end - if ret[1,1] != "\x5a" - raise Rex::ConnectionProxyError.new(host, port, type, "Proxy responded with error code #{ret[0,1].unpack("C")[0]}"), caller - end - when 'socks5' - auth_methods = [5,1,0].pack('CCC') - size = sock.put(auth_methods) - if (size != auth_methods.length) - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller - end - ret = sock.get_once(2,30) - if (ret[1,1] == "\xff") - raise Rex::ConnectionProxyError.new(host, port, type, "The proxy requires authentication"), caller - end - - if (Rex::Socket.is_ipv4?(host)) - addr = Rex::Socket.gethostbyname(host)[3] - setup = [5,1,0,1].pack('C4') + addr + [port.to_i].pack('n') - elsif (Rex::Socket.support_ipv6? and Rex::Socket.is_ipv6?(host)) - # IPv6 stuff all untested - addr = Rex::Socket.gethostbyname(host)[3] - setup = [5,1,0,4].pack('C4') + addr + [port.to_i].pack('n') - else - # Then it must be a domain name. - # Unfortunately, it looks like the host has always been - # resolved by the time it gets here, so this code never runs. - setup = [5,1,0,3].pack('C4') + [host.length].pack('C') + host + [port.to_i].pack('n') - end - - size = sock.put(setup) - if (size != setup.length) - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to send the entire request to the proxy"), caller - end - - begin - response = sock.get_once(10, 30) - rescue IOError - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a response from the proxy"), caller - end - - if (response.nil? or response.length < 10) - raise Rex::ConnectionProxyError.new(host, port, type, "Failed to receive a complete response from the proxy"), caller - end - if response[1,1] != "\x00" - raise Rex::ConnectionProxyError.new(host, port, type, "Proxy responded with error code #{response[1,1].unpack("C")[0]}"), caller - end - else - raise RuntimeError, "The proxy type specified is not valid", caller - end - end - - ## - # - # Registration - # - ## - - def self.register_event_handler(handler) # :nodoc: - self.instance.register_event_handler(handler) - end - - def self.deregister_event_handler(handler) # :nodoc: - self.instance.deregister_event_handler(handler) - end - - def self.each_event_handler(handler) # :nodoc: - self.instance.each_event_handler(handler) - end - -end diff --git a/lib/rex/socket/ip.rb b/lib/rex/socket/ip.rb deleted file mode 100644 index 4399df98c5..0000000000 --- a/lib/rex/socket/ip.rb +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' - -### -# -# This class provides methods for interacting with a IP socket. -# -### -module Rex::Socket::Ip - - include Rex::Socket - - ## - # - # Factory - # - ## - - # - # Creates the client using the supplied hash. - # - def self.create(hash = {}) - hash['Proto'] = 'ip' - self.create_param(Rex::Socket::Parameters.from_hash(hash)) - end - - # - # Wrapper around the base socket class' creation method that automatically - # sets the parameter's protocol to IP. - # - def self.create_param(param) - param.proto = 'ip' - Rex::Socket.create_param(param) - end - - ## - # - # IP connected state methods - # - ## - - # - # Write the supplied datagram to the connected IP socket. - # - def write(gram) - raise RuntimeError, "IP sockets must use sendto(), not write()" - end - - alias put write - - # - # Read a datagram from the IP socket. - # - def read(length = 65535) - raise RuntimeError, "IP sockets must use recvfrom(), not read()" - end - - ## - # - # IP non-connected state methods - # - ## - - # - # Sends a datagram to the supplied host:port with optional flags. - # - def sendto(gram, peerhost, flags = 0) - dest = ::Socket.pack_sockaddr_in(0, peerhost) - - # Some BSDs require byteswap for len and offset - if( - Rex::Compat.is_freebsd or - Rex::Compat.is_netbsd or - Rex::Compat.is_bsdi or - Rex::Compat.is_macosx - ) - gram=gram.dup - # Note that these are *intentionally* host order for BSD support - gram[2,2]=gram[2,2].unpack("n").pack("s") - gram[6,2]=gram[6,2].unpack("n").pack("s") - end - - begin - send(gram, flags, dest) - rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL,::Errno::EADDRNOTAVAIL - return nil - end - - end - - # - # Receives a datagram and returns the data and host of the requestor - # as [ data, host ]. - # - def recvfrom(length = 65535, timeout=def_read_timeout) - begin - if ((rv = ::IO.select([ fd ], nil, nil, timeout)) and - (rv[0]) and (rv[0][0] == fd) - ) - data, saddr = super(length) - af, host = Rex::Socket.from_sockaddr(saddr) - - return [ data, host ] - else - return [ '', nil ] - end - rescue Exception - return [ '', nil ] - end - end - - # - # Calls recvfrom and only returns the data - # - def get(timeout=nil) - data, saddr = recvfrom(65535, timeout) - return data - end - - # - # The default number of seconds to wait for a read operation to timeout. - # - def def_read_timeout - 10 - end - - def type? - return 'ip' - end - -end - diff --git a/lib/rex/socket/parameters.rb b/lib/rex/socket/parameters.rb deleted file mode 100644 index 50c4924449..0000000000 --- a/lib/rex/socket/parameters.rb +++ /dev/null @@ -1,372 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' - -### -# -# This class represents the set of parameters that are used to create -# a socket, whether it be a server or client socket. -# -# @example -# nsock = Rex::Socket::Tcp.create( -# 'PeerHost' => opts['RHOST'] || rhost, -# 'PeerPort' => (opts['RPORT'] || rport).to_i, -# 'LocalHost' => opts['CHOST'] || chost || "0.0.0.0", -# 'LocalPort' => (opts['CPORT'] || cport || 0).to_i, -# 'SSL' => dossl, -# 'SSLVersion'=> opts['SSLVersion'] || ssl_version, -# 'Proxies' => proxies, -# 'Timeout' => (opts['ConnectTimeout'] || connect_timeout || 10).to_i, -# 'Context' => -# { -# 'Msf' => framework, -# 'MsfExploit' => self, -# }) -# -### -class Rex::Socket::Parameters - - ## - # - # Factory - # - ## - - # - # Creates an instance of the Parameters class using the supplied hash. - # - def self.from_hash(hash) - return self.new(hash) - end - - ## - # - # Constructor - # - ## - - # - # Initializes the attributes from the supplied hash. The following hash - # keys can be specified. - # - # @option hash [String] 'PeerHost' The remote host to connect to - # @option hash [String] 'PeerAddr' (alias for 'PeerHost') - # @option hash [Fixnum] 'PeerPort' The remote port to connect to - # @option hash [String] 'LocalHost' The local host to communicate from, if any - # @option hash [String] 'LocalPort' The local port to communicate from, if any - # @option hash [Bool] 'Bool' Create a bare socket - # @option hash [Bool] 'Server' Whether or not this should be a server - # @option hash [Bool] 'SSL' Whether or not SSL should be used - # @option hash [OpenSSL::SSL::SSLContext] 'SSLContext' Use a pregenerated SSL Context - # @option hash [String] 'SSLVersion' Specify Auto, SSL2, SSL3, or TLS1 (Auto is - # default) - # @option hash [String] 'SSLCert' A file containing an SSL certificate (for - # server sockets) - # @option hash [String] 'SSLCipher' see {#ssl_cipher} - # @option hash [Bool] 'SSLCompression' enable SSL-level compression where available - # @option hash [String] 'SSLVerifyMode' SSL certificate verification - # mechanism. One of 'NONE' (default), 'CLIENT_ONCE', 'FAIL_IF_NO_PEER_CERT ', 'PEER' - # @option hash [String] 'Proxies' List of proxies to use. - # @option hash [String] 'Proto' The underlying protocol to use. - # @option hash [String] 'IPv6' Force the use of IPv6. - # @option hash [String] 'Comm' The underlying {Comm} object to use to create - # the socket for this parameter set. - # @option hash [Hash] 'Context' A context hash that can allow users of - # this parameter class instance to determine who is responsible for - # requesting that a socket be created. - # @option hash [String] 'Retries' The number of times a connection should be - # retried. - # @option hash [Fixnum] 'Timeout' The number of seconds before a connection - # should time out - def initialize(hash) - if (hash['PeerHost']) - self.peerhost = hash['PeerHost'] - elsif (hash['PeerAddr']) - self.peerhost = hash['PeerAddr'] - else - self.peerhost = nil - end - - if (hash['LocalHost']) - self.localhost = hash['LocalHost'] - elsif (hash['LocalAddr']) - self.localhost = hash['LocalAddr'] - else - self.localhost = '0.0.0.0' - end - - if (hash['PeerPort']) - self.peerport = hash['PeerPort'].to_i - else - self.peerport = 0 - end - - if (hash['LocalPort']) - self.localport = hash['LocalPort'].to_i - else - self.localport = 0 - end - - if (hash['Bare']) - self.bare = hash['Bare'] - else - self.bare = false - end - - if (hash['SSL'] and hash['SSL'].to_s =~ /^(t|y|1)/i) - self.ssl = true - else - self.ssl = false - end - - if hash['SSLContext'] - self.sslctx = hash['SSLContext'] - end - - supported_ssl_versions = ['Auto', 'SSL2', 'SSL23', 'TLS1', 'SSL3', :Auto, :SSLv2, :SSLv3, :SSLv23, :TLSv1] - if (hash['SSLVersion'] and supported_ssl_versions.include? hash['SSLVersion']) - self.ssl_version = hash['SSLVersion'] - end - - supported_ssl_verifiers = %W{CLIENT_ONCE FAIL_IF_NO_PEER_CERT NONE PEER} - if (hash['SSLVerifyMode'] and supported_ssl_verifiers.include? hash['SSLVerifyMode']) - self.ssl_verify_mode = hash['SSLVerifyMode'] - end - - if hash['SSLCompression'] - self.ssl_compression = hash['SSLCompression'] - end - - if (hash['SSLCipher']) - self.ssl_cipher = hash['SSLCipher'] - end - - if (hash['SSLCert'] and ::File.file?(hash['SSLCert'])) - begin - self.ssl_cert = ::File.read(hash['SSLCert']) - rescue ::Exception => e - elog("Failed to read cert: #{e.class}: #{e}", LogSource) - end - end - - if hash['Proxies'] - self.proxies = hash['Proxies'].split('-').map{|a| a.strip}.map{|a| a.split(':').map{|b| b.strip}} - end - - # The protocol this socket will be using - if (hash['Proto']) - self.proto = hash['Proto'].downcase - else - self.proto = 'tcp' - end - - # Whether or not the socket should be a server - self.server = hash['Server'] || false - - # The communication subsystem to use to create the socket - self.comm = hash['Comm'] - - # The context that was passed in, if any. - self.context = hash['Context'] || {} - - # If no comm was supplied, try to use the comm that is best fit to - # handle the provided host based on the current routing table. - if( self.server ) - if (self.comm == nil and self.localhost) - self.comm = Rex::Socket::SwitchBoard.best_comm(self.localhost) - end - else - if (self.comm == nil and self.peerhost) - self.comm = Rex::Socket::SwitchBoard.best_comm(self.peerhost) - end - end - - # If we still haven't found a comm, we default to the local comm. - self.comm = Rex::Socket::Comm::Local if (self.comm == nil) - - # If we are a UDP server, turn off the server flag as it was only set when - # creating the UDP socket in order to avail of the switch board above. - if( self.server and self.proto == 'udp' ) - self.server = false - end - - # The number of connection retries to make (client only) - if hash['Retries'] - self.retries = hash['Retries'].to_i - else - self.retries = 0 - end - - # The number of seconds before a connect attempt times out (client only) - if hash['Timeout'] - self.timeout = hash['Timeout'].to_i - else - self.timeout = 5 - end - - # Whether to force IPv6 addressing - self.v6 = hash['IPv6'] || false - end - - ## - # - # Conditionals - # - ## - - # - # Returns true if this represents parameters for a server. - # - def server? - return (server == true) - end - - # - # Returns true if this represents parameters for a client. - # - def client? - return (server == false) - end - - # - # Returns true if the protocol for the parameters is TCP. - # - def tcp? - return (proto == 'tcp') - end - - # - # Returns true if the protocol for the parameters is UDP. - # - def udp? - return (proto == 'udp') - end - - # - # Returns true if the protocol for the parameters is IP. - # - def ip? - return (proto == 'ip') - end - - # - # Returns true if the socket is a bare socket that does not inherit from - # any extended Rex classes. - # - def bare? - return (bare == true) - end - - # - # Returns true if SSL has been requested. - # - def ssl? - return ssl - end - - # - # Returns true if IPv6 has been enabled - # - def v6? - return v6 - end - - - ## - # - # Attributes - # - ## - - # The remote host information, equivalent to the PeerHost parameter hash - # key. - # @return [String] - attr_accessor :peerhost - - # The remote port. Equivalent to the PeerPort parameter hash key. - # @return [Fixnum] - attr_accessor :peerport - - # The local host. Equivalent to the LocalHost parameter hash key. - # @return [String] - attr_accessor :localhost - - # The local port. Equivalent to the LocalPort parameter hash key. - # @return [Fixnum] - attr_accessor :localport - - # The protocol to to use, such as TCP. Equivalent to the Proto parameter - # hash key. - # @return [String] - attr_accessor :proto - - # Whether or not this is a server. Equivalent to the Server parameter - # hash key. - # @return [Bool] - attr_accessor :server - - # The {Comm} instance that should be used to create the underlying socket. - # @return [Comm] - attr_accessor :comm - - # The context hash that was passed in to the structure. (default: {}) - # @return [Hash] - attr_accessor :context - - # The number of attempts that should be made. - # @return [Fixnum] - attr_accessor :retries - - # The number of seconds before a connection attempt should time out. - # @return [Fixnum] - attr_accessor :timeout - - # Whether or not this is a bare (non-extended) socket instance that should - # be created. - # @return [Bool] - attr_accessor :bare - - # Whether or not SSL should be used to wrap the connection. - # @return [Bool] - attr_accessor :ssl - - # Pre configured SSL Context to use - # @return [OpenSSL::SSL::SSLContext] - attr_accessor :sslctx - - # What version of SSL to use (Auto, SSL2, SSL3, SSL23, TLS1) - # @return [String,Symbol] - attr_accessor :ssl_version - - # What specific SSL Cipher(s) to use, may be a string containing the cipher - # name or an array of strings containing cipher names e.g. - # ["DHE-RSA-AES256-SHA", "DHE-DSS-AES256-SHA"] - # @return [String,Array] - attr_accessor :ssl_cipher - - # The SSL certificate, in pem format, stored as a string. See - # {Rex::Socket::SslTcpServer#makessl} - # @return [String] - attr_accessor :ssl_cert - - # Enables SSL/TLS-level compression - # @return [Bool] - attr_accessor :ssl_compression - - # - # The SSL context verification mechanism - # - attr_accessor :ssl_verify_mode - - # - # Whether we should use IPv6 - # @return [Bool] - attr_accessor :v6 - - - # List of proxies to use - # @return [String] - attr_accessor :proxies - - alias peeraddr peerhost - alias localaddr localhost -end diff --git a/lib/rex/socket/range_walker.rb b/lib/rex/socket/range_walker.rb deleted file mode 100644 index 7d64e5d639..0000000000 --- a/lib/rex/socket/range_walker.rb +++ /dev/null @@ -1,470 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' - -module Rex -module Socket - -### -# -# This class provides an interface to enumerating an IP range -# -# This class uses start,stop pairs to represent ranges of addresses. This -# is very efficient for large numbers of consecutive addresses, and not -# show-stoppingly inefficient when storing a bunch of non-consecutive -# addresses, which should be a somewhat unusual case. -# -# @example -# r = RangeWalker.new("10.1,3.1-7.1-255") -# r.include?("10.3.7.255") #=> true -# r.length #=> 3570 -# r.each do |addr| -# # do something with the address -# end -### -class RangeWalker - - # The total number of IPs within the range - # - # @return [Fixnum] - attr_reader :length - - # for backwards compatibility - alias :num_ips :length - - # A list of the {Range ranges} held in this RangeWalker - # @return [Array] - attr_reader :ranges - - # Initializes a walker instance using the supplied range - # - # @param parseme [RangeWalker,String] - def initialize(parseme) - if parseme.is_a? RangeWalker - @ranges = parseme.ranges.dup - else - @ranges = parse(parseme) - end - reset - end - - # - # Calls the instance method - # - # This is basically only useful for determining if a range can be parsed - # - # @return (see #parse) - def self.parse(parseme) - self.new.parse(parseme) - end - - # - # Turn a human-readable range string into ranges we can step through one address at a time. - # - # Allow the following formats: - # "a.b.c.d e.f.g.h" - # "a.b.c.d, e.f.g.h" - # where each chunk is CIDR notation, (e.g. '10.1.1.0/24') or a range in nmap format (see {#expand_nmap}) - # - # OR this format - # "a.b.c.d-e.f.g.h" - # where a.b.c.d and e.f.g.h are single IPs and the second must be - # bigger than the first. - # - # @param parseme [String] - # @return [self] - # @return [false] if +parseme+ cannot be parsed - def parse(parseme) - return nil if not parseme - ranges = [] - parseme.split(', ').map{ |a| a.split(' ') }.flatten.each do |arg| - opts = {} - - # Handle IPv6 first (support ranges, but not CIDR) - if arg.include?(":") - addrs = arg.split('-', 2) - - # Handle a single address - if addrs.length == 1 - addr, scope_id = addrs[0].split('%') - opts[:scope_id] = scope_id if scope_id - opts[:ipv6] = true - - return false unless Rex::Socket.is_ipv6?(addr) - addr = Rex::Socket.addr_atoi(addr) - ranges.push(Range.new(addr, addr, opts)) - next - end - - addr1, scope_id = addrs[0].split('%') - opts[:scope_id] = scope_id if scope_id - - addr2, scope_id = addrs[0].split('%') - ( opts[:scope_id] ||= scope_id ) if scope_id - - # Both have to be IPv6 for this to work - return false unless (Rex::Socket.is_ipv6?(addr1) && Rex::Socket.is_ipv6?(addr2)) - - # Handle IPv6 ranges in the form of 2001::1-2001::10 - addr1 = Rex::Socket.addr_atoi(addr1) - addr2 = Rex::Socket.addr_atoi(addr2) - - ranges.push(Range.new(addr1, addr2, opts)) - next - - # Handle IPv4 CIDR - elsif arg.include?("/") - # Then it's CIDR notation and needs special case - return false if arg =~ /[,-]/ # Improper CIDR notation (can't mix with 1,3 or 1-3 style IP ranges) - return false if arg.scan("/").size > 1 # ..but there are too many slashes - ip_part,mask_part = arg.split("/") - return false if ip_part.nil? or ip_part.empty? or mask_part.nil? or mask_part.empty? - return false if mask_part !~ /^[0-9]{1,2}$/ # Illegal mask -- numerals only - return false if mask_part.to_i > 32 # This too -- between 0 and 32. - if ip_part =~ /^\d{1,3}(\.\d{1,3}){1,3}$/ - return false unless ip_part =~ Rex::Socket::MATCH_IPV4 - end - begin - Rex::Socket.getaddress(ip_part) # This allows for "www.metasploit.com/24" which is fun. - rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT - return false # Can't resolve the ip_part, so bail. - end - - expanded = expand_cidr(arg) - if expanded - ranges.push(expanded) - else - return false - end - - # Handle hostnames - elsif arg =~ /[^-0-9,.*]/ - # Then it's a domain name and we should send it on to addr_atoi - # unmolested to force a DNS lookup. - begin - ranges += Rex::Socket.addr_atoi_list(arg).map { |a| Range.new(a, a, opts) } - rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT - return false - end - - # Handle IPv4 ranges - elsif arg =~ /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})-([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/ - - # Then it's in the format of 1.2.3.4-5.6.7.8 - # Note, this will /not/ deal with DNS names, or the fancy/obscure 10...1-10...2 - begin - start, stop = Rex::Socket.addr_atoi($1), Rex::Socket.addr_atoi($2) - return false if start > stop # The end is greater than the beginning. - ranges.push(Range.new(start, stop, opts)) - rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT - return false - end - else - # Returns an array of ranges - expanded = expand_nmap(arg) - if expanded - expanded.each { |r| ranges.push(r) } - end - end - end - - # Remove any duplicate ranges - ranges = ranges.uniq - - return ranges - end - - # - # Resets the subnet walker back to its original state. - # - # @return [self] - def reset - return false if not valid? - @curr_range_index = 0 - @curr_addr = @ranges.first.start - @length = 0 - @ranges.each { |r| @length += r.length } - - self - end - - # Returns the next IP address. - # - # @return [String] The next address in the range - def next_ip - return false if not valid? - if (@curr_addr > @ranges[@curr_range_index].stop) - # Then we are at the end of this range. Grab the next one. - - # Bail if there are no more ranges - return nil if (@ranges[@curr_range_index+1].nil?) - - @curr_range_index += 1 - - @curr_addr = @ranges[@curr_range_index].start - end - addr = Rex::Socket.addr_itoa(@curr_addr, @ranges[@curr_range_index].ipv6?) - - if @ranges[@curr_range_index].options[:scope_id] - addr = addr + '%' + @ranges[@curr_range_index].options[:scope_id] - end - - @curr_addr += 1 - return addr - end - - alias :next :next_ip - - # Whether this RangeWalker's ranges are valid - def valid? - (@ranges && !@ranges.empty?) - end - - # Returns true if the argument is an ip address that falls within any of - # the stored ranges. - # - # @return [true] if this RangeWalker contains +addr+ - # @return [false] if not - def include?(addr) - return false if not @ranges - if (addr.is_a? String) - addr = Rex::Socket.addr_atoi(addr) - end - @ranges.map { |r| - if addr.between?(r.start, r.stop) - return true - end - } - return false - end - - # - # Returns true if this RangeWalker includes *all* of the addresses in the - # given RangeWalker - # - # @param other [RangeWalker] - def include_range?(other) - return false if (!@ranges || @ranges.empty?) - return false if !other.ranges || other.ranges.empty? - - # Check that all the ranges in +other+ fall within at least one of - # our ranges. - other.ranges.all? do |other_range| - ranges.any? do |range| - other_range.start.between?(range.start, range.stop) && other_range.stop.between?(range.start, range.stop) - end - end - end - - # - # Calls the given block with each address. This is basically a wrapper for - # {#next_ip} - # - # @return [self] - def each(&block) - while (ip = next_ip) - block.call(ip) - end - reset - - self - end - - # - # Returns an Array with one element, a {Range} defined by the given CIDR - # block. - # - # @see Rex::Socket.cidr_crack - # @param arg [String] A CIDR range - # @return [Range] - # @return [false] if +arg+ is not valid CIDR notation - def expand_cidr(arg) - start,stop = Rex::Socket.cidr_crack(arg) - if !start or !stop - return false - end - range = Range.new - range.start = Rex::Socket.addr_atoi(start) - range.stop = Rex::Socket.addr_atoi(stop) - range.options = { :ipv6 => (arg.include?(":")) } - - return range - end - - # - # Expands an nmap-style host range x.x.x.x where x can be simply "*" which - # means 0-255 or any combination and repitition of: - # i,n - # n-m - # i,n-m - # n-m,i - # ensuring that n is never greater than m. - # - # non-unique elements will be removed - # e.g.: - # 10.1.1.1-3,2-2,2 => ["10.1.1.1", "10.1.1.2", "10.1.1.3"] - # 10.1.1.1-3,7 => ["10.1.1.1", "10.1.1.2", "10.1.1.3", "10.1.1.7"] - # - # Returns an array of Ranges - # - def expand_nmap(arg) - # Can't really do anything with IPv6 - return false if arg.include?(":") - - # nmap calls these errors, but it's hard to catch them with our - # splitting below, so short-cut them here - return false if arg.include?(",-") or arg.include?("-,") - - bytes = [] - sections = arg.split('.') - if sections.length != 4 - # Too many or not enough dots - return false - end - sections.each { |section| - if section.empty? - # pretty sure this is an unintentional artifact of the C - # functions that turn strings into ints, but it sort of makes - # sense, so why not - # "10...1" => "10.0.0.1" - section = "0" - end - - if section == "*" - # I think this ought to be 1-254, but this is how nmap does it. - section = "0-255" - elsif section.include?("*") - return false - end - - # Break down the sections into ranges like so - # "1-3,5-7" => ["1-3", "5-7"] - ranges = section.split(',', -1) - sets = [] - ranges.each { |r| - bounds = [] - if r.include?('-') - # Then it's an actual range, break it down into start,stop - # pairs: - # "1-3" => [ 1, 3 ] - # if the lower bound is empty, start at 0 - # if the upper bound is empty, stop at 255 - # - bounds = r.split('-', -1) - return false if (bounds.length > 2) - - bounds[0] = 0 if bounds[0].nil? or bounds[0].empty? - bounds[1] = 255 if bounds[1].nil? or bounds[1].empty? - bounds.map!{|b| b.to_i} - return false if bounds[0] > bounds[1] - else - # Then it's a single value - bounds[0] = r.to_i - end - return false if bounds[0] > 255 or (bounds[1] and bounds[1] > 255) - return false if bounds[1] and bounds[0] > bounds[1] - if bounds[1] - bounds[0].upto(bounds[1]) do |i| - sets.push(i) - end - elsif bounds[0] - sets.push(bounds[0]) - end - } - bytes.push(sets.sort.uniq) - } - - # - # Combinitorically squish all of the quads together into a big list of - # ip addresses, stored as ints - # - # e.g.: - # [[1],[1],[1,2],[1,2]] - # => - # [atoi("1.1.1.1"),atoi("1.1.1.2"),atoi("1.1.2.1"),atoi("1.1.2.2")] - addrs = [] - for a in bytes[0] - for b in bytes[1] - for c in bytes[2] - for d in bytes[3] - ip = (a << 24) + (b << 16) + (c << 8) + d - addrs.push ip - end - end - end - end - - addrs.sort! - addrs.uniq! - - rng = Range.new - rng.options = { :ipv6 => false } - rng.start = addrs[0] - - ranges = [] - 1.upto(addrs.length - 1) do |idx| - if addrs[idx - 1] + 1 == addrs[idx] - # Then this address is contained in the current range - next - else - # Then this address is the upper bound for the current range - rng.stop = addrs[idx - 1] - ranges.push(rng.dup) - rng.start = addrs[idx] - end - end - rng.stop = addrs[addrs.length - 1] - ranges.push(rng.dup) - return ranges - end - -end - -# A range of IP addresses -class Range - - #@!attribute start - # The first address in this range, as a number - # @return [Fixnum] - attr_accessor :start - #@!attribute stop - # The last address in this range, as a number - # @return [Fixnum] - attr_accessor :stop - #@!attribute options - # @return [Hash] - attr_accessor :options - - # @param start [Fixnum] - # @param stop [Fixnum] - # @param options [Hash] Recognized keys are: - # * +:ipv6+ - # * +:scope_id+ - def initialize(start=nil, stop=nil, options=nil) - @start = start - @stop = stop - @options = options - end - - # Compare attributes with +other+ - # @param other [Range] - # @return [Boolean] - def ==(other) - (other.start == start && other.stop == stop && other.ipv6? == ipv6? && other.options == options) - end - - # The number of addresses in this Range - # @return [Fixnum] - def length - stop - start + 1 - end - alias :count :length - - # Whether this Range contains IPv6 or IPv4 addresses - # @return [Boolean] - def ipv6? - options[:ipv6] - end -end - -end -end diff --git a/lib/rex/socket/ssh_factory.rb b/lib/rex/socket/ssh_factory.rb deleted file mode 100644 index 872354da47..0000000000 --- a/lib/rex/socket/ssh_factory.rb +++ /dev/null @@ -1,46 +0,0 @@ -module Rex - module Socket - - # This class exists to abuse the Proxy capabilities in the Net::SSH library to allow the use of Rex::Sockets - # for the transport layer in Net::SSH. The SSHFactory object will respond to the #open method and create the - # {Rex::Socket::Tcp} - class SSHFactory - - # @!attribute msfraemwork - # @return [Object] The framework instance object - attr_accessor :framework - # @!attribute msfmodule - # @return [Object] The metasploit module this socket belongs to - attr_accessor :msfmodule - # @!attribute proxies - # @return [String] Any proxies to use for the connection - attr_accessor :proxies - - def initialize(framework, msfmodule, proxies) - @framework = framework - @msfmodule = msfmodule - @proxies = proxies - end - - # Responds to the proxy setup routine Net::SSH will call when - # initialising the Transport Layer. This will instead create our - # {Rex::Socket::Tcp} and tie the socket back to the calling module - # @param host [String] The host to open the connection to - # @param port [Fixnum] the port to open the connection on - # @param options [Hash] the options hash - def open(host, port, options={}) - socket = Rex::Socket::Tcp.create( - 'PeerHost' => host, - 'PeerPort' => port, - 'Proxies' => proxies, - 'Context' => { - 'Msf' => framework, - 'MsfExploit' => msfmodule - } - ) - msfmodule.add_socket(socket) if msfmodule - socket - end - end - end -end \ No newline at end of file diff --git a/lib/rex/socket/ssl_tcp.rb b/lib/rex/socket/ssl_tcp.rb deleted file mode 100644 index f37ee3f4c8..0000000000 --- a/lib/rex/socket/ssl_tcp.rb +++ /dev/null @@ -1,374 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' -### -# -# This class provides methods for interacting with an SSL TCP client -# connection. -# -### -module Rex::Socket::SslTcp - -begin - @@loaded_openssl = false - - begin - require 'openssl' - @@loaded_openssl = true - require 'openssl/nonblock' - rescue ::Exception - end - - - include Rex::Socket::Tcp - - ## - # - # Factory - # - ## - - # - # Creates an SSL TCP instance. - # - def self.create(hash = {}) - raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl - hash['SSL'] = true - self.create_param(Rex::Socket::Parameters.from_hash(hash)) - end - - # - # Set the SSL flag to true and call the base class's create_param routine. - # - def self.create_param(param) - param.ssl = true - Rex::Socket::Tcp.create_param(param) - end - - ## - # - # Class initialization - # - ## - - # - # Initializes the SSL socket. - # - def initsock(params = nil) - super - - # Default to SSLv23 (automatically negotiate) - version = :SSLv23 - - # Let the caller specify a particular SSL/TLS version - if params - case params.ssl_version - when 'SSL2', :SSLv2 - version = :SSLv2 - # 'TLS' will be the new name for autonegotation with newer versions of OpenSSL - when 'SSL23', :SSLv23, 'TLS', 'Auto' - version = :SSLv23 - when 'SSL3', :SSLv3 - version = :SSLv3 - when 'TLS1','TLS1.0', :TLSv1 - version = :TLSv1 - when 'TLS1.1', :TLSv1_1 - version = :TLSv1_1 - when 'TLS1.2', :TLSv1_2 - version = :TLSv1_2 - end - end - - # Raise an error if no selected versions are supported - if ! OpenSSL::SSL::SSLContext::METHODS.include? version - raise ArgumentError, 'The system OpenSSL does not support the requested SSL/TLS version' - end - - # Try intializing the socket with this SSL/TLS version - # This will throw an exception if it fails - initsock_with_ssl_version(params, version) - - # Track the SSL version - self.ssl_negotiated_version = version - end - - def initsock_with_ssl_version(params, version) - # Build the SSL connection - self.sslctx = OpenSSL::SSL::SSLContext.new(version) - - # Configure the SSL context - # TODO: Allow the user to specify the verify mode callback - # Valid modes: - # VERIFY_CLIENT_ONCE - # VERIFY_FAIL_IF_NO_PEER_CERT - # VERIFY_NONE - # VERIFY_PEER - if params.ssl_verify_mode - self.sslctx.verify_mode = OpenSSL::SSL.const_get("VERIFY_#{params.ssl_verify_mode}".intern) - else - # Could also do this as graceful faildown in case a passed verify_mode is not supported - self.sslctx.verify_mode = OpenSSL::SSL::VERIFY_PEER - end - - self.sslctx.options = OpenSSL::SSL::OP_ALL - - if params.ssl_cipher - self.sslctx.ciphers = params.ssl_cipher - end - - # Set the verification callback - self.sslctx.verify_callback = Proc.new do |valid, store| - self.peer_verified = valid - true - end - - # Tie the context to a socket - self.sslsock = OpenSSL::SSL::SSLSocket.new(self, self.sslctx) - - # If peerhost looks like a hostname, set the undocumented 'hostname' - # attribute on sslsock, which enables the Server Name Indication (SNI) - # extension - self.sslsock.hostname = self.peerhost if !Rex::Socket.dotted_ip?(self.peerhost) - - # Force a negotiation timeout - begin - Timeout.timeout(params.timeout) do - if not allow_nonblock? - self.sslsock.connect - else - begin - self.sslsock.connect_nonblock - # Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno - rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK - IO::select(nil, nil, nil, 0.10) - retry - - # Ruby 1.9.2+ uses IO::WaitReadable/IO::WaitWritable - rescue ::Exception => e - if ::IO.const_defined?('WaitReadable') and e.kind_of?(::IO::WaitReadable) - IO::select( [ self.sslsock ], nil, nil, 0.10 ) - retry - end - - if ::IO.const_defined?('WaitWritable') and e.kind_of?(::IO::WaitWritable) - IO::select( nil, [ self.sslsock ], nil, 0.10 ) - retry - end - - raise e - end - end - end - - rescue ::Timeout::Error - raise Rex::ConnectionTimeout.new(params.peerhost, params.peerport) - end - end - - ## - # - # Stream mixin implementations - # - ## - - # - # Writes data over the SSL socket. - # - def write(buf, opts = {}) - return sslsock.write(buf) if not allow_nonblock? - - total_sent = 0 - total_length = buf.length - block_size = 16384 - retry_time = 0.5 - - begin - while( total_sent < total_length ) - s = Rex::ThreadSafe.select( nil, [ self.sslsock ], nil, 0.25 ) - if( s == nil || s[0] == nil ) - next - end - data = buf[total_sent, block_size] - sent = sslsock.write_nonblock( data ) - if sent > 0 - total_sent += sent - end - end - - rescue ::IOError, ::Errno::EPIPE - return nil - - # Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno - rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK - # Sleep for a half a second, or until we can write again - Rex::ThreadSafe.select( nil, [ self.sslsock ], nil, retry_time ) - # Decrement the block size to handle full sendQs better - block_size = 1024 - # Try to write the data again - retry - - # Ruby 1.9.2+ uses IO::WaitReadable/IO::WaitWritable - rescue ::Exception => e - if ::IO.const_defined?('WaitReadable') and e.kind_of?(::IO::WaitReadable) - IO::select( [ self.sslsock ], nil, nil, retry_time ) - retry - end - - if ::IO.const_defined?('WaitWritable') and e.kind_of?(::IO::WaitWritable) - IO::select( nil, [ self.sslsock ], nil, retry_time ) - retry - end - - # Another form of SSL error, this is always fatal - if e.kind_of?(::OpenSSL::SSL::SSLError) - return nil - end - - # Bubble the event up to the caller otherwise - raise e - end - - total_sent - end - - # - # Reads data from the SSL socket. - # - def read(length = nil, opts = {}) - if not allow_nonblock? - length = 16384 unless length - begin - return sslsock.sysread(length) - rescue ::IOError, ::Errno::EPIPE, ::OpenSSL::SSL::SSLError - return nil - end - return - end - - - begin - while true - s = Rex::ThreadSafe.select( [ self.sslsock ], nil, nil, 0.10 ) - if( s == nil || s[0] == nil ) - next - end - return sslsock.read_nonblock( length ) - end - - rescue ::IOError, ::Errno::EPIPE - return nil - - # Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno - rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK - # Sleep for a tenth a second, or until we can read again - Rex::ThreadSafe.select( [ self.sslsock ], nil, nil, 0.10 ) - # Decrement the block size to handle full sendQs better - block_size = 1024 - # Try to write the data again - retry - - # Ruby 1.9.2+ uses IO::WaitReadable/IO::WaitWritable - rescue ::Exception => e - if ::IO.const_defined?('WaitReadable') and e.kind_of?(::IO::WaitReadable) - IO::select( [ self.sslsock ], nil, nil, 0.5 ) - retry - end - - if ::IO.const_defined?('WaitWritable') and e.kind_of?(::IO::WaitWritable) - IO::select( nil, [ self.sslsock ], nil, 0.5 ) - retry - end - - # Another form of SSL error, this is always fatal - if e.kind_of?(::OpenSSL::SSL::SSLError) - return nil - end - - raise e - end - - end - - - # - # Closes the SSL socket. - # - def close - sslsock.close rescue nil - super - end - - # - # Ignore shutdown requests - # - def shutdown(how=0) - # Calling shutdown() on an SSL socket can lead to bad things - # Cause of http://metasploit.com/dev/trac/ticket/102 - end - - # - # Access to peer cert - # - def peer_cert - sslsock.peer_cert if sslsock - end - - # - # Access to peer cert chain - # - def peer_cert_chain - sslsock.peer_cert_chain if sslsock - end - - # - # Access to the current cipher - # - def cipher - sslsock.cipher if sslsock - end - - # - # Prevent a sysread from the bare socket - # - def sysread(*args) - raise RuntimeError, "Invalid sysread() call on SSL socket" - end - - # - # Prevent a sysread from the bare socket - # - def syswrite(*args) - raise RuntimeError, "Invalid syswrite() call on SSL socket" - end - - # - # This flag determines whether to use the non-blocking openssl - # API calls when they are available. This is still buggy on - # Linux/Mac OS X, but is required on Windows - # - def allow_nonblock? - avail = self.sslsock.respond_to?(:accept_nonblock) - if avail and Rex::Compat.is_windows - return true - end - false - end - - attr_reader :peer_verified # :nodoc: - attr_reader :ssl_negotiated_version # :nodoc: - attr_accessor :sslsock, :sslctx, :sslhash # :nodoc: - - def type? - return 'tcp-ssl' - end - -protected - - attr_writer :peer_verified # :nodoc: - attr_writer :ssl_negotiated_version # :nodoc: - - -rescue LoadError -end - -end - diff --git a/lib/rex/socket/ssl_tcp_server.rb b/lib/rex/socket/ssl_tcp_server.rb deleted file mode 100644 index 3a25a188f8..0000000000 --- a/lib/rex/socket/ssl_tcp_server.rb +++ /dev/null @@ -1,220 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' -require 'rex/socket/tcp_server' -require 'rex/io/stream_server' -require 'rex/parser/x509_certificate' - -### -# -# This class provides methods for interacting with an SSL wrapped TCP server. It -# implements the StreamServer IO interface. -# -### -module Rex::Socket::SslTcpServer - - @@loaded_openssl = false - - begin - require 'openssl' - @@loaded_openssl = true - require 'openssl/nonblock' - rescue ::Exception - end - - include Rex::Socket::TcpServer - - ## - # - # Factory - # - ## - - def self.create(hash = {}) - hash['Proto'] = 'tcp' - hash['Server'] = true - hash['SSL'] = true - self.create_param(Rex::Socket::Parameters.from_hash(hash)) - end - - # - # Wrapper around the base class' creation method that automatically sets - # the parameter's protocol to TCP and sets the server flag to true. - # - def self.create_param(param) - param.proto = 'tcp' - param.server = true - param.ssl = true - Rex::Socket.create_param(param) - end - - def initsock(params = nil) - raise RuntimeError, 'No OpenSSL support' unless @@loaded_openssl - - if params && params.sslctx && params.sslctx.kind_of?(OpenSSL::SSL::SSLContext) - self.sslctx = params.sslctx - else - self.sslctx = makessl(params) - end - - super - end - - # (see TcpServer#accept) - def accept(opts = {}) - sock = super() - return if not sock - - begin - ssl = OpenSSL::SSL::SSLSocket.new(sock, self.sslctx) - - if not allow_nonblock?(ssl) - ssl.accept - else - begin - ssl.accept_nonblock - - # Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno - rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK - IO::select(nil, nil, nil, 0.10) - retry - - # Ruby 1.9.2+ uses IO::WaitReadable/IO::WaitWritable - rescue ::Exception => e - if ::IO.const_defined?('WaitReadable') and e.kind_of?(::IO::WaitReadable) - IO::select( [ ssl ], nil, nil, 0.10 ) - retry - end - - if ::IO.const_defined?('WaitWritable') and e.kind_of?(::IO::WaitWritable) - IO::select( nil, [ ssl ], nil, 0.10 ) - retry - end - - raise e - end - end - - sock.extend(Rex::Socket::SslTcp) - sock.sslsock = ssl - sock.sslctx = self.sslctx - - return sock - - rescue ::OpenSSL::SSL::SSLError - sock.close - nil - end - end - - # - # Parse a certificate in unified PEM format that contains a private key and - # one or more certificates. The first certificate is the primary, while any - # additional certificates are treated as intermediary certificates. This emulates - # the behavior of web servers like nginx. - # - # @param [String] ssl_cert - # @return [String, String, Array] - def self.ssl_parse_pem(ssl_cert) - Rex::Parser::X509Certificate.parse_pem(ssl_cert) - end - - # - # Shim for the ssl_parse_pem module method - # - def ssl_parse_pem(ssl_cert) - Rex::Socket::SslTcpServer.ssl_parse_pem(ssl_cert) - end - - # - # Generate a realistic-looking but obstensibly fake SSL - # certificate. This matches a typical "snakeoil" cert. - # - # @return [String, String, Array] - def self.ssl_generate_certificate - yr = 24*3600*365 - vf = Time.at(Time.now.to_i - rand(yr * 3) - yr) - vt = Time.at(vf.to_i + (10 * yr)) - cn = Rex::Text.rand_text_alpha_lower(rand(8)+2) - key = OpenSSL::PKey::RSA.new(2048){ } - cert = OpenSSL::X509::Certificate.new - cert.version = 2 - cert.serial = (rand(0xFFFFFFFF) << 32) + rand(0xFFFFFFFF) - cert.subject = OpenSSL::X509::Name.new([["CN", cn]]) - cert.issuer = OpenSSL::X509::Name.new([["CN", cn]]) - cert.not_before = vf - cert.not_after = vt - cert.public_key = key.public_key - - ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) - cert.extensions = [ - ef.create_extension("basicConstraints","CA:FALSE") - ] - ef.issuer_certificate = cert - - cert.sign(key, OpenSSL::Digest::SHA256.new) - - [key, cert, nil] - end - - # - # Shim for the ssl_generate_certificate module method - # - def ssl_generate_certificate - Rex::Socket::SslTcpServer.ssl_generate_certificate - end - - # - # Create a new ssl context. If +ssl_cert+ is not given, generates a new - # key and a leaf certificate with random values. - # - # @param [Rex::Socket::Parameters] params - # @return [::OpenSSL::SSL::SSLContext] - def makessl(params) - - if params.ssl_cert - key, cert, chain = ssl_parse_pem(params.ssl_cert) - else - key, cert, chain = ssl_generate_certificate - end - - ctx = OpenSSL::SSL::SSLContext.new() - ctx.key = key - ctx.cert = cert - ctx.extra_chain_cert = chain - ctx.options = 0 - - if params.ssl_cipher - ctx.ciphers = params.ssl_cipher - end - - # Older versions of OpenSSL do not export the OP_NO_COMPRESSION symbol - if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) - # enable/disable the SSL/TLS-level compression - if params.ssl_compression - ctx.options &= ~OpenSSL::SSL::OP_NO_COMPRESSION - else - ctx.options |= OpenSSL::SSL::OP_NO_COMPRESSION - end - end - - ctx.session_id_context = Rex::Text.rand_text(16) - - return ctx - end - - # - # This flag determines whether to use the non-blocking openssl - # API calls when they are available. This is still buggy on - # Linux/Mac OS X, but is required on Windows - # - def allow_nonblock?(sock=self.sock) - avail = sock.respond_to?(:accept_nonblock) - if avail and Rex::Compat.is_windows - return true - end - false - end - - attr_accessor :sslctx -end - diff --git a/lib/rex/socket/subnet_walker.rb b/lib/rex/socket/subnet_walker.rb deleted file mode 100644 index 2c560e8869..0000000000 --- a/lib/rex/socket/subnet_walker.rb +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' - -module Rex -module Socket - -### -# -# This class provides an interface to enumerating a subnet with a supplied -# netmask. -# -### -class SubnetWalker - - # - # Initializes a subnet walker instance using the supplied subnet - # information. - # - def initialize(subnet, netmask) - self.subnet = Socket.resolv_to_dotted(subnet) - self.netmask = Socket.resolv_to_dotted(netmask) - - reset - end - - # - # Resets the subnet walker back to its original state. - # - def reset - self.curr_ip = self.subnet.split('.') - self.num_ips = (1 << (32 - Socket.net2bitmask(self.netmask).to_i)) - self.curr_ip_idx = 0 - end - - # - # Returns the next IP address. - # - def next_ip - if (curr_ip_idx >= num_ips) - return nil - end - - if (curr_ip_idx > 0) - self.curr_ip[3] = (curr_ip[3].to_i + 1) % 256 - self.curr_ip[2] = (curr_ip[2].to_i + 1) % 256 if (curr_ip[3] == 0) - self.curr_ip[1] = (curr_ip[1].to_i + 1) % 256 if (curr_ip[2] == 0) - self.curr_ip[0] = (curr_ip[0].to_i + 1) % 256 if (curr_ip[1] == 0) - end - - self.curr_ip_idx += 1 - - self.curr_ip.join('.') - end - - # - # The subnet that is being enumerated. - # - attr_reader :subnet - # - # The netmask of the subnet. - # - attr_reader :netmask - # - # The total number of IPs within the subnet. - # - attr_reader :num_ips - -protected - - attr_writer :subnet, :netmask, :num_ips # :nodoc: - attr_accessor :curr_ip, :curr_ip_idx # :nodoc: - -end - -end -end diff --git a/lib/rex/socket/switch_board.rb b/lib/rex/socket/switch_board.rb deleted file mode 100644 index d431aa765a..0000000000 --- a/lib/rex/socket/switch_board.rb +++ /dev/null @@ -1,289 +0,0 @@ -# -*- coding: binary -*- -require 'singleton' -require 'thread' -require 'rex/socket' - -module Rex -module Socket - -### -# -# This class provides a global routing table that associates subnets with Comm -# classes. Comm classes are used to instantiate objects that are tied to -# remote network entities. For example, the Local Comm class is used to -# building network connections directly from the local machine whereas, for -# instance, a Meterpreter Comm would build a local socket pair that is -# associated with a connection established by a remote entity. This can be -# seen as a uniform way of communicating with hosts through arbitrary -# channels. -# -### -class SwitchBoard - - include Singleton - include Enumerable - - def initialize - @_initialized = false - end - - ### - # - # This class represents a logical switch board route. - # TODO: Enable this to work with IPv6 addresses - # - ### - class Route - def initialize(subnet, netmask, comm) - self.subnet = subnet - self.netmask = netmask - self.comm = comm - self.subnet_nbo = Socket.resolv_nbo_i(subnet) - self.netmask_nbo = Socket.resolv_nbo_i(netmask) - end - - # - # Sort according to bitmask - # - def <=>(other) - self.bitmask <=> other.bitmask - end - - # - # Convert the netmask to a bitmask and cache it. - # - def bitmask - @_bitmask = Socket.net2bitmask(self.netmask) if (@_bitmask == nil) - @_bitmask - end - - attr_reader :subnet, :netmask, :comm - attr_reader :subnet_nbo, :netmask_nbo - protected - attr_writer :subnet, :netmask, :comm - attr_writer :subnet_nbo, :netmask_nbo - end - - ## - # - # Class method wrappers - # - ## - - # - # Adds a route to the switch board routing table using the supplied Comm - # instance. - # - def self.add_route(subnet, mask, comm) - ret = self.instance.add_route(subnet, mask, comm) - if ret && comm.respond_to?(:routes) && comm.routes.kind_of?(Array) - comm.routes << "#{subnet}/#{mask}" - end - ret - end - - # - # Removes a route from the switch board routing table for the supplied - # subnet routing through the supplied Comm instance. - # - def self.remove_route(subnet, mask, comm) - ret = self.instance.remove_route(subnet, mask, comm) - if ret && comm.respond_to?(:routes) && comm.routes.kind_of?(Array) - comm.routes.delete "#{subnet}/#{mask}" - end - ret - end - - # - # Flush all the routes from the switch board routing table. - # - def self.flush_routes - ret = self.instance.flush_routes - end - - # - # Enumerate each route in the routing table. - # - def self.each(&block) - self.instance.each(&block) - end - - # - # Returns the array of routes. - # - def self.routes - self.instance.routes - end - - def self.route_exists?(subnet, mask) - self.instance.route_exists?(subnet, mask) - end - - # - # Returns the Comm instance that should be used for the supplied address. - # If no comm can be found, the default Local Comm is returned. - # - def self.best_comm(addr) - self.instance.best_comm(addr) - end - - # - # Removes all routes that go through the supplied Comm. - # - def self.remove_by_comm(comm) - self.instance.remove_by_comm(comm) - end - - ## - # - # Instance methods - # - ## - - # - # Adds a route for a given subnet and netmask destined through a given comm - # instance. - # - def add_route(subnet, mask, comm) - # If a bitmask was supplied, convert it. - netmask = (mask.to_s =~ /^\d+$/) ? Rex::Socket.bit2netmask(mask.to_i) : mask - rv = true - - _init - - mutex.synchronize { - # If the route already exists, return false to the caller. - if (route_exists?(subnet, netmask) == false) - self.routes << Route.new(subnet, netmask, comm) - else - rv = false - end - } - - rv - end - - # - # Removes a route for a given subnet and netmask destined through a given - # comm instance. - # - def remove_route(subnet, mask, comm) - # If a bitmask was supplied, convert it. - netmask = (mask.to_s =~ /^\d+$/) ? Rex::Socket.bit2netmask(mask.to_i) : mask - rv = false - - _init - - mutex.synchronize { - self.routes.delete_if { |route| - if (route.subnet == subnet and route.netmask == netmask and route.comm == comm) - rv = true - else - false - end - } - } - - rv - end - - # - # Flushes all established routes. - # - def flush_routes - _init - - # Remove each of the individual routes so the comms don't think they're - # still routing after a flush. - self.routes.each { |r| - if r.comm.respond_to? :routes - r.comm.routes.delete("#{r.subnet}/#{r.netmask}") - end - } - # Re-initialize to an empty array - self.routes = Array.new - end - - # - # Checks to see if a route already exists for the supplied subnet and - # netmask. - # - def route_exists?(subnet, netmask) - each { |route| - return true if (route.subnet == subnet and route.netmask == netmask) - } - - false - end - - # - # Enumerates each entry in the routing table. - # - def each(&block) - _init - - routes.each(&block) - end - - # - # Finds the best possible comm for the supplied target address. - # - def best_comm(addr) - - addr_nbo = Socket.resolv_nbo_i(addr) - comm = nil - msb = 0 - - each { |route| - if ((route.subnet_nbo & route.netmask_nbo) == - (addr_nbo & route.netmask_nbo)) - if (route.bitmask >= msb) - comm = route.comm - msb = route.bitmask - end - end - } - - comm - end - - # - # Remove all routes that go through the supplied comm. - # - def remove_by_comm(comm) - _init - mutex.synchronize { - routes.delete_if { |route| - route.comm == comm - } - } - end - - # - # The routes array. - # - attr_reader :routes - # - # The mutex protecting the routes array. - # - attr_reader :mutex - -protected - - attr_writer :routes, :mutex # :nodoc: - - # - # Initializes the underlying stuff. - # - def _init - if (@_initialized != true) - @_initialized = true - self.routes = Array.new - self.mutex = Mutex.new - end - end - -end - -end -end diff --git a/lib/rex/socket/tcp.rb b/lib/rex/socket/tcp.rb deleted file mode 100644 index 762afe8a99..0000000000 --- a/lib/rex/socket/tcp.rb +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' -require 'rex/io/stream' - -### -# -# This class provides methods for interacting with a TCP client connection. -# -### -module Rex::Socket::Tcp - - include Rex::Socket - include Rex::IO::Stream - - ## - # - # Factory - # - ## - - # - # Creates the client using the supplied hash. - # - # @see create_param - # @see Rex::Socket::Parameters.from_hash - def self.create(hash = {}) - hash['Proto'] = 'tcp' - self.create_param(Rex::Socket::Parameters.from_hash(hash)) - end - - # - # Wrapper around the base socket class' creation method that automatically - # sets the parameter's protocol to TCP. - # - def self.create_param(param) - param.proto = 'tcp' - Rex::Socket.create_param(param) - end - - ## - # - # Stream mixin implementations - # - ## - - # - # Calls shutdown on the TCP connection. - # - def shutdown(how = ::Socket::SHUT_RDWR) - begin - return (super(how) == 0) - rescue ::Exception - end - end - - # - # Returns peer information (host + port) in host:port format. - # - def peerinfo - if (pi = getpeername_as_array) - return pi[1] + ':' + pi[2].to_s - end - end - - # - # Returns local information (host + port) in host:port format. - # - def localinfo - if (pi = getlocalname) - return pi[1] + ':' + pi[2].to_s - end - end - - # returns socket type - def type? - return 'tcp' - end - -end diff --git a/lib/rex/socket/tcp_server.rb b/lib/rex/socket/tcp_server.rb deleted file mode 100644 index b0107d3e68..0000000000 --- a/lib/rex/socket/tcp_server.rb +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' -require 'rex/socket/tcp' -require 'rex/io/stream_server' - -### -# -# This class provides methods for interacting with a TCP server. It -# implements the Rex::IO::StreamServer interface. -# -### -module Rex::Socket::TcpServer - - include Rex::Socket - include Rex::IO::StreamServer - - ## - # - # Factory - # - ## - - # - # Creates the server using the supplied hash. - # - def self.create(hash = {}) - hash['Proto'] = 'tcp' - hash['Server'] = true - self.create_param(Rex::Socket::Parameters.from_hash(hash)) - end - - # - # Wrapper around the base class' creation method that automatically sets - # the parameter's protocol to TCP and sets the server flag to true. - # - def self.create_param(param) - param.proto = 'tcp' - param.server = true - Rex::Socket.create_param(param) - end - - # - # Accepts a child connection. - # - def accept(opts = {}) - t = super() - - # jRuby compatibility - if t.respond_to?('[]') - t = t[0] - end - - if (t) - t.extend(Rex::Socket::Tcp) - t.context = self.context - - pn = t.getpeername_as_array - - # We hit a "getpeername(2)" from Ruby - return nil unless pn - - t.peerhost = pn[1] - t.peerport = pn[2] - end - - t - end - -end - diff --git a/lib/rex/socket/udp.rb b/lib/rex/socket/udp.rb deleted file mode 100644 index 58ba2c0411..0000000000 --- a/lib/rex/socket/udp.rb +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' - -### -# -# This class provides methods for interacting with a UDP socket. -# -### -module Rex::Socket::Udp - - include Rex::Socket - - ## - # - # Factory - # - ## - - # - # Creates the client using the supplied hash. - # - def self.create(hash = {}) - hash['Proto'] = 'udp' - # If we have are to bind to a LocalHost we must be a Server to avail of pivoting. - # Rex::Socket::Parameters will subsequently turn off the sever flag after the correct - # comm has been chosen. - if( hash['LocalHost'] ) - hash['Server'] = true - end - self.create_param(Rex::Socket::Parameters.from_hash(hash)) - end - - # - # Wrapper around the base socket class' creation method that automatically - # sets the parameter's protocol to UDP. - # - def self.create_param(param) - param.proto = 'udp' - Rex::Socket.create_param(param) - end - - ## - # - # UDP connected state methods - # - ## - - # - # Write the supplied datagram to the connected UDP socket. - # - def write(gram) - begin - return syswrite(gram) - rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL,::Errno::EADDRNOTAVAIL - return nil - end - end - - alias put write - - # - # Read a datagram from the UDP socket. - # - def read(length = 65535) - if length < 0 - length = 65535 - end - return sysread(length) - end - - # - # Read a datagram from the UDP socket with a timeout - # - def timed_read(length = 65535, timeout=def_read_timeout) - begin - if ((rv = ::IO.select([ fd ], nil, nil, timeout)) and - (rv[0]) and (rv[0][0] == fd) - ) - return read(length) - else - return '' - end - rescue Exception - return '' - end - end - - #alias send write - #alias recv read - - ## - # - # UDP non-connected state methods - # - ## - - # - # Sends a datagram to the supplied host:port with optional flags. - # - def sendto(gram, peerhost, peerport, flags = 0) - - # Catch unconnected IPv6 sockets talking to IPv4 addresses - peer = Rex::Socket.resolv_nbo(peerhost) - if (peer.length == 4 and self.ipv == 6) - peerhost = Rex::Socket.getaddress(peerhost, true) - if peerhost[0,7].downcase != '::ffff:' - peerhost = '::ffff:' + peerhost - end - end - - begin - send(gram, flags, Rex::Socket.to_sockaddr(peerhost, peerport)) - rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL,::Errno::EADDRNOTAVAIL - return nil - end - - end - - # - # Receives a datagram and returns the data and host:port of the requestor - # as [ data, host, port ]. - # - def recvfrom(length = 65535, timeout=def_read_timeout) - - begin - if ((rv = ::IO.select([ fd ], nil, nil, timeout)) and - (rv[0]) and (rv[0][0] == fd) - ) - data, saddr = recvfrom_nonblock(length) - af, host, port = Rex::Socket.from_sockaddr(saddr) - - return [ data, host, port ] - else - return [ '', nil, nil ] - end - rescue ::Timeout::Error - return [ '', nil, nil ] - rescue ::Interrupt - raise $! - rescue ::Exception - return [ '', nil, nil ] - end - end - - # - # Calls recvfrom and only returns the data - # - def get(timeout=nil) - data, saddr, sport = recvfrom(65535, timeout) - return data - end - - # - # The default number of seconds to wait for a read operation to timeout. - # - def def_read_timeout - 10 - end - - def type? - return 'udp' - end - -end - diff --git a/lib/rex/sync.rb b/lib/rex/sync.rb deleted file mode 100644 index e2d6748787..0000000000 --- a/lib/rex/sync.rb +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: binary -*- - -require 'rex/sync/thread_safe' -require 'rex/sync/ref' -require 'rex/sync/read_write_lock' -require 'rex/sync/event' diff --git a/lib/rex/sync/event.rb b/lib/rex/sync/event.rb deleted file mode 100644 index c71aed36a0..0000000000 --- a/lib/rex/sync/event.rb +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: binary -*- -require 'thread' - -module Rex -module Sync - -### -# -# This class wraps the logical ConditionVariable class to make it an easier to -# work with interface that is similar to Windows' synchronization events. -# -### -class Event - - Infinite = 10000 - - # - # Initializes a waitable event. The state parameter initializes the - # default state of the event. If auto_reset is true, any calls to set() - # will automatically reset the event back to an unset state. - # - def initialize(state = false, auto_reset = true, param = nil) - self.state = state - self.auto_reset = auto_reset - self.param = param - self.mutex = Mutex.new - self.cond = ConditionVariable.new - end - - # - # Sets the event and wakes up anyone who was waiting. - # - def set(param = nil) - self.param = param - - self.mutex.synchronize { - # If this event does not automatically reset its state, - # set the state to true - if (auto_reset == false) - self.state = true - end - - self.cond.broadcast - } - end - - # - # Resets the signaled state to false. - # - def reset - self.param = nil - self.state = false - end - - # - # Alias notify with set. - # - alias notify set - - # - # Waits for the event to become signaled. Timeout is measured in - # seconds. Raises TimeoutError if the condition does not become signaled. - # - def wait(t = Infinite) - self.mutex.synchronize { - break if (self.state == true) - - Timeout.timeout(t) { - self.cond.wait(self.mutex) - } - } - - return self.param - end - -protected - - attr_accessor :state, :auto_reset # :nodoc: - attr_accessor :param, :mutex, :cond # :nodoc: - -end - -end -end - diff --git a/lib/rex/sync/read_write_lock.rb b/lib/rex/sync/read_write_lock.rb deleted file mode 100644 index 7d1612c106..0000000000 --- a/lib/rex/sync/read_write_lock.rb +++ /dev/null @@ -1,177 +0,0 @@ -# -*- coding: binary -*- -require 'thread' - -module Rex - -### -# -# This class implements a read/write lock synchronization -# primitive. It is meant to allow for more efficient access to -# resources that are more often read from than written to and many -# times can have concurrent reader threads. By allowing the reader -# threads to lock the resource concurrently rather than serially, -# a large performance boost can be seen. Acquiring a write lock -# results in exclusive access to the resource and thereby prevents -# any read operations during the time that a write lock is acquired. -# Only one write lock may be acquired at a time. -# -### -class ReadWriteLock - - # - # Initializes a reader/writer lock instance. - # - def initialize - @read_sync_mutex = Mutex.new - @write_sync_mutex = Mutex.new - @exclusive_mutex = Mutex.new - @readers = 0 - @writer = false - end - - # - # Acquires the read lock for the calling thread. - # - def lock_read - read_sync_mutex.lock - - begin - # If there are a non-zero number of readers and a - # writer is waiting to acquire the exclusive lock, - # free up the sync mutex temporarily and lock/unlock - # the exclusive lock. This is to give the writer - # thread a chance to acquire the lock and prevents - # it from being constantly starved. - if ((@readers > 0) and - (@writer)) - read_sync_mutex.unlock - exclusive_mutex.lock - exclusive_mutex.unlock - read_sync_mutex.lock - end - - # Increment the active reader count - @readers += 1 - - # If we now have just one reader, acquire the exclusive - # lock. Track the thread owner so that we release the - # lock from within the same thread context later on. - if (@readers == 1) - exclusive_mutex.lock - - @owner = Thread.current - end - ensure - read_sync_mutex.unlock - end - end - - # - # Releases the read lock for the calling thread. - # - def unlock_read - read_sync_mutex.lock - - begin - unlocked = false - - # Keep looping until we've lost this thread's reader - # lock - while (!unlocked) - # If there are no more readers left after this one - if (@readers - 1 == 0) - # If the calling thread is the owner of the exclusive - # reader lock, then let's release it - if (Thread.current == @owner) - @owner = nil - - exclusive_mutex.unlock - end - # If there is more than one reader left and this thread is - # the owner of the exclusive lock, then keep looping so that - # we can eventually unlock the exclusive mutex in this thread's - # context - elsif (Thread.current == @owner) - read_sync_mutex.unlock - - next - end - - # Unlocked! - unlocked = true - - # Decrement the active reader count - @readers -= 1 - end - ensure - read_sync_mutex.unlock - end - end - - # - # Acquire the exclusive write lock. - # - def lock_write - write_sync_mutex.lock - - begin - @writer = true - - exclusive_mutex.lock - - @owner = Thread.current - ensure - write_sync_mutex.unlock - end - end - - # - # Release the exclusive write lock. - # - def unlock_write - # If the caller is not the owner of the write lock, then someone is - # doing something broken, let's let them know. - if (Thread.current != @owner) - raise RuntimeError, "Non-owner calling thread attempted to release write lock", caller - end - - # Otherwise, release the exclusive write lock - @writer = false - - exclusive_mutex.unlock - end - - # - # Synchronize a block for read access. - # - def synchronize_read - lock_read - begin - yield - ensure - unlock_read - end - end - - # - # Synchronize a block for write access. - # - def synchronize_write - lock_write - begin - yield - ensure - unlock_write - end - end - -protected - - attr_accessor :read_sync_mutex # :nodoc: - attr_accessor :write_sync_mutex # :nodoc: - attr_accessor :exclusive_mutex # :nodoc: - -end - -end - diff --git a/lib/rex/sync/ref.rb b/lib/rex/sync/ref.rb deleted file mode 100644 index 364eafdf95..0000000000 --- a/lib/rex/sync/ref.rb +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: binary -*- -require 'thread' - -module Rex - -### -# -# This module provides a uniform reference counted interface for classes to -# use. -# -### -module Ref - - # - # Initializes the reference count to one. - # - def refinit - @_references = 1 - @_references_mutex = Mutex.new - - self - end - - # - # Increments the total number of references. - # - def ref - @_references_mutex.synchronize { - @_references += 1 - } - - self - end - - # - # Decrements the total number of references. If the reference count - # reaches zero, true is returned. Otherwise, false is returned. - # - def deref - @_references_mutex.synchronize { - if ((@_references -= 1) == 0) - cleanup - - true - else - false - end - } - end - - # - # Called to clean up resources once the ref count drops to zero. - # - def cleanup - end - -end -end diff --git a/lib/rex/sync/thread_safe.rb b/lib/rex/sync/thread_safe.rb deleted file mode 100644 index ec57e3cc47..0000000000 --- a/lib/rex/sync/thread_safe.rb +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: binary -*- -require 'timeout' - -module Rex - -### -# -# This module provides a set of methods for performing various blocking -# operations in a manner that is compatible with ruby style threads. -# -### -module ThreadSafe - - DefaultCycle = 0.2 - - # - # Wraps calls to select with a lower timeout period and does the - # calculations to walk down to zero timeout. This has a little room for - # improvement in that it should probably check how much time actually - # elapsed during the select call considering ruby threading wont be exactly - # accurate perhaps. - # - def self.select(rfd = nil, wfd = nil, efd = nil, t = nil) - left = t - - # Immediately raise a StreamClosedError if the socket was closed. This - # prevents a bad fd from being passed downstream and solves an issue - # with Ruby on Windows. - rfd.each { |fd| raise StreamClosedError.new(fd) if (fd.closed?) } if rfd - - begin - orig_size = rfd.length if (rfd) - - # Poll the set supplied to us at least once. - begin - rv = ::IO.select(rfd, wfd, efd, DefaultCycle) - rescue ::IOError, ::Errno::EBADF, ::Errno::ENOTSOCK - # If a stream was detected as being closed, re-raise the error as - # a StreamClosedError with the specific file descriptor that was - # detected as being closed. This is to better handle the case of - # a closed socket being detected so that it can be cleaned up and - # removed. - rfd.each { |fd| raise StreamClosedError.new(fd) if (fd.closed?) } if rfd - - # If the original rfd length is not the same as the current - # length, then the list may have been altered and as such may not - # contain the socket that caused the IOError. This is a bad way - # to do this since it's possible that the array length could be - # back to the size that it was originally and yet have had the - # socket that caused the IOError to be removed. - return nil if (rfd and rfd.length != orig_size) - - # Re-raise the exception since we didn't handle it here. - raise $! -# rescue ::Exception => e -# $stderr.puts "SELECT(#{t}) #{[rfd,wfd,efd].inspect} #{e.class} #{e} #{e.backtrace}" - end - - return rv if (rv) - - # Decrement the amount of time left by the polling cycle - left -= DefaultCycle if (left) - - # Keep chugging until we run out of time, if time was supplied. - end while ((left == nil) or (left > 0)) - - # Nothin. - nil - end - - # - # Simulates a sleep operation by selecting on nil until a timeout period - # expires. - # - def self.sleep(seconds=nil) - self.select(nil, nil, nil, seconds) - - seconds - end - -end - -end diff --git a/lib/rex/ui.rb b/lib/rex/ui.rb index a0c9b03148..fa85649e4a 100644 --- a/lib/rex/ui.rb +++ b/lib/rex/ui.rb @@ -13,6 +13,7 @@ require 'rex/ui/text/input' require 'rex/ui/text/shell' require 'rex/ui/text/dispatcher_shell' require 'rex/ui/text/irb_shell' +require 'rex/ui/text/bidirectional_pipe' require 'rex/text/color' require 'rex/text/table' diff --git a/lib/rex/io/bidirectional_pipe.rb b/lib/rex/ui/text/bidirectional_pipe.rb similarity index 98% rename from lib/rex/io/bidirectional_pipe.rb rename to lib/rex/ui/text/bidirectional_pipe.rb index 0c553c0c9c..491bca9552 100644 --- a/lib/rex/io/bidirectional_pipe.rb +++ b/lib/rex/ui/text/bidirectional_pipe.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- module Rex -module IO +module Ui +module Text require 'rex/ui/text/output' require 'rex/ui/text/output/buffer' @@ -155,3 +156,4 @@ end end end +end diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index aa7ff94a3c..2cddae0d8e 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -109,6 +109,8 @@ Gem::Specification.new do |spec| # # REX Libraries # + # Core of the Ruby Exploitation Library + spec.add_runtime_dependency 'rex-core' # Text manipulation library for things like generating random string spec.add_runtime_dependency 'rex-text' # Library for Generating Randomized strings valid as Identifiers such as variable names @@ -132,6 +134,8 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'rex-mime' # Library for parsing and manipulating executable binaries spec.add_runtime_dependency 'rex-bin_tools' + # Rex Socket Abstraction Layer + spec.add_runtime_dependency 'rex-socket' # rb-readline doesn't work with Ruby Installer due to error with Fiddle: # NoMethodError undefined method `dlopen' for Fiddle:Module diff --git a/spec/lib/rex/socket/range_walker_spec.rb b/spec/lib/rex/socket/range_walker_spec.rb deleted file mode 100644 index f5e4ae8047..0000000000 --- a/spec/lib/rex/socket/range_walker_spec.rb +++ /dev/null @@ -1,191 +0,0 @@ -# -*- coding:binary -*- -require 'rex/socket/range_walker' - -RSpec.describe Rex::Socket::RangeWalker do - - let(:args) { "::1" } - subject(:walker) { described_class.new(args) } - - it { is_expected.to respond_to(:length) } - it { is_expected.to respond_to(:valid?) } - it { is_expected.to respond_to(:each) } - - describe '.new' do - - context "with a hostname" do - let(:args) { "localhost" } - it { is_expected.to be_valid } - it { expect(subject.length).to be >= 1 } - end - - context "with a hostname and CIDR" do - let(:args) { "localhost/24" } - it { is_expected.to be_valid } - it { expect(subject.length).to eq(256) } - end - - context "with an invalid hostname" do - let(:args) { "@!*^&.invalid-hostname-really." } - it { is_expected.not_to be_valid } - end - - context "with an invalid hostname and CIDR" do - let(:args) { "@!*^&.invalid-hostname-really./24" } - it { is_expected.not_to be_valid } - end - - context "with an IPv6 address range containing a scope" do - let(:args) { "fe80::1%lo-fe80::100%lo" } - it { is_expected.to be_valid } - end - - it "should handle single ipv6 addresses" do - walker = Rex::Socket::RangeWalker.new("::1") - expect(walker).to be_valid - expect(walker.length).to eq 1 - end - - it "should handle longform ranges" do - walker = Rex::Socket::RangeWalker.new("10.1.1.1-10.1.1.2") - expect(walker).to be_valid - expect(walker.length).to eq 2 - expect(walker.next).to eq "10.1.1.1" - end - - context "with mulitple ranges" do - let(:args) { "1.1.1.1-2 2.1-2.2.2 3.1-2.1-2.1 " } - it { is_expected.to be_valid } - it { expect(subject.length).to eq(8) } - it { is_expected.to include("1.1.1.1") } - end - - it "should handle ranges" do - walker = Rex::Socket::RangeWalker.new("10.1.1.1-2") - expect(walker).to be_valid - expect(walker.length).to eq 2 - expect(walker.next).to eq "10.1.1.1" - walker = Rex::Socket::RangeWalker.new("10.1-2.1.1-2") - expect(walker).to be_valid - expect(walker.length).to eq 4 - walker = Rex::Socket::RangeWalker.new("10.1-2.3-4.5-6") - expect(walker).to be_valid - expect(walker.length).to eq 8 - expect(walker).to include("10.1.3.5") - end - - it 'should reject CIDR ranges with missing octets' do - walker = Rex::Socket::RangeWalker.new('192.168/24') - expect(walker).not_to be_valid - end - - it 'should reject a CIDR range with too many octets' do - walker = Rex::Socket::RangeWalker.new('192.168.1.2.0/24') - expect(walker).not_to be_valid - end - - it "should default the lower bound of a range to 0" do - walker = Rex::Socket::RangeWalker.new("10.1.3.-17") - expect(walker).to be_valid - expect(walker.length).to eq 18 - walker = Rex::Socket::RangeWalker.new("10.1.3.-255") - expect(walker).to be_valid - expect(walker.length).to eq 256 - end - - it "should default the upper bound of a range to 255" do - walker = Rex::Socket::RangeWalker.new("10.1.3.254-") - expect(walker).to be_valid - expect(walker.length).to eq 2 - end - - it "should take * to mean 0-255" do - walker = Rex::Socket::RangeWalker.new("10.1.3.*") - expect(walker).to be_valid - expect(walker.length).to eq 256 - expect(walker.next).to eq "10.1.3.0" - expect(walker).to include("10.1.3.255") - walker = Rex::Socket::RangeWalker.new("10.1.*.3") - expect(walker).to be_valid - expect(walker.length).to eq 256 - expect(walker.next).to eq "10.1.0.3" - expect(walker).to include("10.1.255.3") - end - - it "should handle lists" do - walker = Rex::Socket::RangeWalker.new("10.1.1.1") - expect(walker).to be_valid - expect(walker.length).to eq 1 - walker = Rex::Socket::RangeWalker.new("10.1.1.1,3") - expect(walker).to be_valid - expect(walker.length).to eq 2 - expect(walker).not_to include("10.1.1.2") - end - - it "should produce the same ranges with * and 0-255" do - a = Rex::Socket::RangeWalker.new("10.1.3.*") - b = Rex::Socket::RangeWalker.new("10.1.3.0-255") - expect(a.ranges).to eq(b.ranges) - end - - it "should handle ranges and lists together" do - walker = Rex::Socket::RangeWalker.new("10.1.1.1-2,3") - expect(walker).to be_valid - expect(walker.length).to eq 3 - walker = Rex::Socket::RangeWalker.new("10.1-2.1.1,2") - expect(walker).to be_valid - expect(walker.length).to eq 4 - walker = Rex::Socket::RangeWalker.new("10.1,2.3,4.5,6") - expect(walker.length).to eq 8 - end - - it "should handle cidr" do - 31.downto 16 do |bits| - walker = Rex::Socket::RangeWalker.new("10.1.1.1/#{bits}") - expect(walker).to be_valid - expect(walker.length).to eq (2**(32-bits)) - end - end - end - - describe '#each' do - let(:args) { "10.1.1.1-2,2,3 10.2.2.2" } - - it "should yield all ips" do - got = [] - walker.each { |ip| - got.push ip - } - expect(got).to eq ["10.1.1.1", "10.1.1.2", "10.1.1.3", "10.2.2.2"] - end - - end - - describe '#include_range?' do - let(:args) { "10.1.1.*" } - - it "returns true for a sub-range" do - other = described_class.new("10.1.1.1-255") - expect(walker).to be_include_range(other) - end - - end - - describe '#next' do - let(:args) { "10.1.1.1-5" } - it "should return all addresses" do - all = [] - while ip = walker.next - all << ip - end - expect(all).to eq [ "10.1.1.1", "10.1.1.2", "10.1.1.3", "10.1.1.4", "10.1.1.5", ] - end - - it "should not raise if called again after empty" do - expect { - (walker.length + 5).times { walker.next } - }.not_to raise_error - end - - end - -end diff --git a/spec/lib/rex/socket_spec.rb b/spec/lib/rex/socket_spec.rb deleted file mode 100644 index b71d8bf2e6..0000000000 --- a/spec/lib/rex/socket_spec.rb +++ /dev/null @@ -1,198 +0,0 @@ -# -*- coding:binary -*- -require 'rex/socket/range_walker' -require 'spec_helper' - -RSpec.describe Rex::Socket do - - describe '.addr_itoa' do - - context 'with explicit v6' do - it "should convert a number to a human-readable IPv6 address" do - expect(described_class.addr_itoa(1, true)).to eq "::1" - end - end - - context 'with explicit v4' do - it "should convert a number to a human-readable IPv4 address" do - expect(described_class.addr_itoa(1, false)).to eq "0.0.0.1" - end - end - - context 'without explicit version' do - it "should convert a number within the range of possible v4 addresses to a human-readable IPv4 address" do - expect(described_class.addr_itoa(0)).to eq "0.0.0.0" - expect(described_class.addr_itoa(1)).to eq "0.0.0.1" - expect(described_class.addr_itoa(0xffff_ffff)).to eq "255.255.255.255" - end - it "should convert a number larger than possible v4 addresses to a human-readable IPv6 address" do - expect(described_class.addr_itoa(0xfe80_0000_0000_0000_0000_0000_0000_0001)).to eq "fe80::1" - expect(described_class.addr_itoa(0x1_0000_0001)).to eq "::1:0:1" - end - end - - end - - describe '.addr_aton' do - subject(:nbo) do - described_class.addr_aton(try) - end - - context 'with ipv6' do - let(:try) { "fe80::1" } - it { is_expected.to be_an(String) } - it { expect(subject.bytes.count).to eq(16) } - it "should be in the right order" do - expect(nbo).to eq "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" - end - end - - context 'with ipv4' do - let(:try) { "127.0.0.1" } - it { is_expected.to be_an(String) } - it { expect(subject.bytes.count).to eq(4) } - it "should be in the right order" do - expect(nbo).to eq "\x7f\x00\x00\x01" - end - end - - context 'with a hostname' do - let(:try) { "localhost" } - it "should resolve" do - expect(nbo).to be_a(String) - expect(nbo.encoding).to eq Encoding.find('binary') - expect([ 4, 16 ]).to include(nbo.length) - end - end - - end - - describe '.compress_address' do - - subject(:compressed) do - described_class.compress_address(try) - end - - context 'with lots of single 0s' do - let(:try) { "fe80:0:0:0:0:0:0:1" } - it { is_expected.to eq "fe80::1" } - end - - end - - describe '.getaddress' do - - subject { described_class.getaddress('whatever') } - - before(:example) do - expect(Socket).to receive(:gethostbyname).and_return(['name', ['aliases'], response_afamily, *response_addresses]) - end - - context 'when ::Socket.gethostbyname returns IPv4 responses' do - let(:response_afamily) { Socket::AF_INET } - let(:response_addresses) { ["\x01\x01\x01\x01", "\x02\x02\x02\x02"] } - - it { is_expected.to be_a(String) } - it "should return the first ASCII address" do - expect(subject).to eq "1.1.1.1" - end - end - - context 'when ::Socket.gethostbyname returns IPv6 responses' do - let(:response_afamily) { Socket::AF_INET6 } - let(:response_addresses) { ["\xfe\x80"+("\x00"*13)+"\x01", "\xfe\x80"+("\x00"*13)+"\x02"] } - - it { is_expected.to be_a(String) } - it "should return the first ASCII address" do - expect(subject).to eq "fe80::1" - end - end - - context "with rubinius' bug returning ASCII addresses" do - let(:response_afamily) { Socket::AF_INET } - let(:response_addresses) { ["1.1.1.1", "2.2.2.2"] } - - it { is_expected.to be_a(String) } - it "should return the first ASCII address" do - expect(subject).to eq "1.1.1.1" - end - - end - end - - describe '.getaddresses' do - - subject { described_class.getaddresses('whatever') } - - before(:example) do - expect(Socket).to receive(:gethostbyname).and_return(['name', ['aliases'], response_afamily, *response_addresses]) - end - - context 'when ::Socket.gethostbyname returns IPv4 responses' do - let(:response_afamily) { Socket::AF_INET } - let(:response_addresses) { ["\x01\x01\x01\x01", "\x02\x02\x02\x02"] } - - it { is_expected.to be_an(Array) } - it { expect(subject.size).to eq(2) } - it "should return the ASCII addresses" do - expect(subject).to include("1.1.1.1") - expect(subject).to include("2.2.2.2") - end - end - - context 'when ::Socket.gethostbyname returns IPv6 responses' do - let(:response_afamily) { Socket::AF_INET6 } - let(:response_addresses) { ["\xfe\x80"+("\x00"*13)+"\x01", "\xfe\x80"+("\x00"*13)+"\x02"] } - - it { is_expected.to be_an(Array) } - it { expect(subject.size).to eq(2) } - it "should return the ASCII addresses" do - expect(subject).to include("fe80::1") - expect(subject).to include("fe80::2") - end - end - - context "with rubinius' bug returning ASCII addresses" do - let(:response_afamily) { Socket::AF_INET } - let(:response_addresses) { ["1.1.1.1", "2.2.2.2"] } - - it { is_expected.to be_an(Array) } - it { expect(subject.size).to eq(2) } - it "should return the ASCII addresses" do - expect(subject).to include("1.1.1.1") - expect(subject).to include("2.2.2.2") - end - - end - end - - describe '.portspec_to_portlist' do - - subject(:portlist) { described_class.portspec_to_portlist portspec_string} - let(:portspec_string) { '-1,0-10,!2-5,!7,65530-,65536' } - - it 'does not include negative numbers' do - expect(portlist).to_not include '-1' - end - - it 'does not include 0' do - expect(portlist).to_not include '0' - end - - it 'does not include negated numbers' do - ['2', '3', '4', '5', '7'].each do |port| - expect(portlist).to_not include port - end - end - - it 'does not include any numbers above 65535' do - expect(portlist).to_not include '65536' - end - - it 'expands open ended ranges' do - (65530..65535).each do |port| - expect(portlist).to include port - end - end - end - -end