require 'custodian/testfactory' require 'socket' require 'timeout' # # The TCP-protocol test. # # This object is instantiated if the parser sees a line such as: # ### ### foo.vm.bytemark.co.uk must run tcp on 22 with banner 'ssh' otherwise 'ssh fail'. ### # # The specification of the port is mandatory, the banner is optional. # module Custodian module ProtocolTest class TCPTest < TestFactory # # The input line from which we were constructed. # attr_reader :line # # The host to test against. # attr_reader :host # # The port to connect to. # attr_reader :port # # Is this test inverted? # attr_reader :inverted # # The banner to look for, may be nil. # attr_reader :banner # # Constructor # # Ensure we received a port to run the TCP-test against. # def initialize( line ) # # Save the line # @line = line # # Save the host # @host = line.split( /\s+/)[0] # # Is this test inverted? # if ( line =~ /must\s+not\s+run\s+/ ) @inverted = true else @inverted = false end # # Save the port # if ( line =~ /on\s+([0-9]+)/ ) @port = $1.dup else @port = nil end # # Save the optional banner. # if ( line =~ /with\s+banner\s+'([^']+)'/ ) @banner = $1.dup else @banner = nil end @error = nil if ( @port.nil? ) raise ArgumentError, "Missing port to test against" end end # # Allow this test to be serialized. # def to_s return( @line ) end # # Run the test. # def run_test # reset the error, in case we were previously executed. @error = nil return( run_test_internal( @host, @port, @banner ) ) end # # Run the connection test - optionally matching against the banner. # # If the banner is nil then we're merely testing we can connect and # send the string "quit". # # def run_test_internal( host, port, banner = nil, do_read = true ) begin timeout(30) do begin socket = TCPSocket.new( host, port ) # read a banner from the remote server, if we're supposed to. read = nil read = socket.gets(1024) if ( do_read ) # trim to a sane length & strip newlines. read = read[0,255] unless ( read.nil? ) read.gsub!(/[\n\r]/, "") unless ( read.nil? ) socket.close() if ( banner.nil? ) @error = nil return true else # test for banner if ( ( !read.nil? ) && ( read =~ /#{banner}/i ) ) return true end @error = "We expected a banner matching '#{banner}' but we got '#{read}'" return false end rescue @error = "Exception connecting to host #{host}:#{port} - #{$!}" return false end end rescue Timeout::Error => e @error = "TIMEOUT: #{e}" return false end @error = "Misc failure" return false end # # If the test fails then report the error. # def error @error end register_test_type "tcp" end end end