ssl_scan 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +17 -0
 - data/Gemfile +4 -0
 - data/LICENSE.txt +22 -0
 - data/README.md +51 -0
 - data/Rakefile +1 -0
 - data/bin/ssl_scan +4 -0
 - data/lib/ssl_scan/client.rb +0 -0
 - data/lib/ssl_scan/compat.rb +388 -0
 - data/lib/ssl_scan/exceptions.rb +274 -0
 - data/lib/ssl_scan/io/bidirectional_pipe.rb +161 -0
 - data/lib/ssl_scan/io/datagram_abstraction.rb +35 -0
 - data/lib/ssl_scan/io/ring_buffer.rb +369 -0
 - data/lib/ssl_scan/io/stream.rb +312 -0
 - data/lib/ssl_scan/io/stream_abstraction.rb +209 -0
 - data/lib/ssl_scan/io/stream_server.rb +221 -0
 - data/lib/ssl_scan/result.rb +165 -0
 - data/lib/ssl_scan/scanner.rb +241 -0
 - data/lib/ssl_scan/socket/comm/local.rb +526 -0
 - data/lib/ssl_scan/socket/comm.rb +120 -0
 - data/lib/ssl_scan/socket/ip.rb +131 -0
 - data/lib/ssl_scan/socket/parameters.rb +363 -0
 - data/lib/ssl_scan/socket/range_walker.rb +470 -0
 - data/lib/ssl_scan/socket/ssl_tcp.rb +345 -0
 - data/lib/ssl_scan/socket/ssl_tcp_server.rb +188 -0
 - data/lib/ssl_scan/socket/subnet_walker.rb +76 -0
 - data/lib/ssl_scan/socket/switch_board.rb +289 -0
 - data/lib/ssl_scan/socket/tcp.rb +79 -0
 - data/lib/ssl_scan/socket/tcp_server.rb +67 -0
 - data/lib/ssl_scan/socket/udp.rb +165 -0
 - data/lib/ssl_scan/socket.rb +773 -0
 - data/lib/ssl_scan/sync/thread_safe.rb +83 -0
 - data/lib/ssl_scan/version.rb +9 -0
 - data/lib/ssl_scan.rb +11 -0
 - data/sslscan.gemspec +23 -0
 - metadata +107 -0
 
| 
         @@ -0,0 +1,241 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'ssl_scan/socket'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'ssl_scan/result'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module SSLScan
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class Scanner
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              attr_accessor :context
         
     | 
| 
      
 9 
     | 
    
         
            +
              attr_accessor :host
         
     | 
| 
      
 10 
     | 
    
         
            +
              attr_accessor :port
         
     | 
| 
      
 11 
     | 
    
         
            +
              attr_accessor :timeout
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              attr_reader :supported_versions
         
     | 
| 
      
 14 
     | 
    
         
            +
              attr_reader :peer_supported_versions
         
     | 
| 
      
 15 
     | 
    
         
            +
              attr_reader :sslv2
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              # Initializes the scanner object
         
     | 
| 
      
 18 
     | 
    
         
            +
              # @param host [String] IP address or hostname to scan
         
     | 
| 
      
 19 
     | 
    
         
            +
              # @param port [Fixnum] Port number to scan, default: 443
         
     | 
| 
      
 20 
     | 
    
         
            +
              # @param timeout [Fixnum] Timeout for connections, in seconds. default: 5
         
     | 
| 
      
 21 
     | 
    
         
            +
              # @raise [StandardError] Raised when the configuration is invalid
         
     | 
| 
      
 22 
     | 
    
         
            +
              def initialize(host,port = 443,context = {},timeout=5)
         
     | 
| 
      
 23 
     | 
    
         
            +
                @host       = host
         
     | 
| 
      
 24 
     | 
    
         
            +
                @port       = port
         
     | 
| 
      
 25 
     | 
    
         
            +
                @timeout    = timeout
         
     | 
| 
      
 26 
     | 
    
         
            +
                @context    = context
         
     | 
| 
      
 27 
     | 
    
         
            +
                if check_opensslv2 == true
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @supported_versions = [:SSLv2, :SSLv3, :TLSv1]
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @sslv2 = true
         
     | 
| 
      
 30 
     | 
    
         
            +
                else
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @supported_versions = [:SSLv3, :TLSv1]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @sslv2 = false
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
                @peer_supported_versions = []
         
     | 
| 
      
 35 
     | 
    
         
            +
                raise StandardError, "The scanner configuration is invalid" unless valid?
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              # Checks whether the scanner option has a valid configuration
         
     | 
| 
      
 39 
     | 
    
         
            +
              # @return [Boolean] True or False, the configuration is valid.
         
     | 
| 
      
 40 
     | 
    
         
            +
              def valid?
         
     | 
| 
      
 41 
     | 
    
         
            +
                begin
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @host = SSLScan::Socket.getaddress(@host, true)
         
     | 
| 
      
 43 
     | 
    
         
            +
                rescue
         
     | 
| 
      
 44 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
                return false unless @port.kind_of? Fixnum
         
     | 
| 
      
 47 
     | 
    
         
            +
                return false unless @port >= 0 and @port <= 65535
         
     | 
| 
      
 48 
     | 
    
         
            +
                return false unless @timeout.kind_of? Fixnum
         
     | 
| 
      
 49 
     | 
    
         
            +
                return true
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              # Initiate the Scan against the target. Will test each cipher one at a time.
         
     | 
| 
      
 53 
     | 
    
         
            +
              # @return [Result] object containing the details of the scan
         
     | 
| 
      
 54 
     | 
    
         
            +
              def scan(&block)
         
     | 
| 
      
 55 
     | 
    
         
            +
                scan_result = SSLScan::Result.new
         
     | 
| 
      
 56 
     | 
    
         
            +
                scan_result.openssl_sslv2 = sslv2
         
     | 
| 
      
 57 
     | 
    
         
            +
                # If we can't get any SSL connection, then don't bother testing
         
     | 
| 
      
 58 
     | 
    
         
            +
                # individual ciphers.
         
     | 
| 
      
 59 
     | 
    
         
            +
                if test_ssl == :rejected and test_tls == :rejected
         
     | 
| 
      
 60 
     | 
    
         
            +
                  return scan_result
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                @supported_versions.each do |ssl_version|
         
     | 
| 
      
 64 
     | 
    
         
            +
                  sslctx = OpenSSL::SSL::SSLContext.new(ssl_version)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  sslctx.ciphers.each do |cipher_name, ssl_ver, key_length, alg_length|
         
     | 
| 
      
 66 
     | 
    
         
            +
                    status = test_cipher(ssl_version, cipher_name)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    scan_result.add_cipher(ssl_version, cipher_name, key_length, status)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    if status == :accepted and scan_result.cert.nil?
         
     | 
| 
      
 69 
     | 
    
         
            +
                      scan_result.cert = get_cert(ssl_version, cipher_name)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    if block_given?
         
     | 
| 
      
 73 
     | 
    
         
            +
                      yield(ssl_version, cipher_name, key_length, status, scan_result.cert)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
                    
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
                @peer_supported_versions = [].tap do |psv|
         
     | 
| 
      
 79 
     | 
    
         
            +
                  psv << :SSLv2 if scan_result.supports_sslv2?
         
     | 
| 
      
 80 
     | 
    
         
            +
                  psv << :SSLv3 if scan_result.supports_sslv3?
         
     | 
| 
      
 81 
     | 
    
         
            +
                  psv << :TLSv1 if scan_result.supports_tlsv1?
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
                scan_result
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
              
         
     | 
| 
      
 87 
     | 
    
         
            +
              def get_preferred_ciphers
         
     | 
| 
      
 88 
     | 
    
         
            +
                ssl_versions = {}.tap do |v|
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @supported_versions.each { |sv| v[sv] = [] } 
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                ssl_versions.keys.each do |ssl_version|
         
     | 
| 
      
 93 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 94 
     | 
    
         
            +
                    scan_client = SSLScan::Socket::Tcp.create(
         
     | 
| 
      
 95 
     | 
    
         
            +
                      'Context'    => @context,
         
     | 
| 
      
 96 
     | 
    
         
            +
                      'PeerHost'   => @host,
         
     | 
| 
      
 97 
     | 
    
         
            +
                      'PeerPort'   => @port,
         
     | 
| 
      
 98 
     | 
    
         
            +
                      'SSL'        => true,
         
     | 
| 
      
 99 
     | 
    
         
            +
                      'SSLVersion' => ssl_version,
         
     | 
| 
      
 100 
     | 
    
         
            +
                      'Timeout'    => @timeout
         
     | 
| 
      
 101 
     | 
    
         
            +
                    )
         
     | 
| 
      
 102 
     | 
    
         
            +
                    ssl_versions[ssl_version] = scan_client.cipher
         
     | 
| 
      
 103 
     | 
    
         
            +
                  rescue => ex
         
     | 
| 
      
 104 
     | 
    
         
            +
                    ssl_versions.delete(ssl_version)
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
                ssl_versions
         
     | 
| 
      
 108 
     | 
    
         
            +
              end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
              def test_ssl
         
     | 
| 
      
 111 
     | 
    
         
            +
                begin
         
     | 
| 
      
 112 
     | 
    
         
            +
                  scan_client = SSLScan::Socket::Tcp.create(
         
     | 
| 
      
 113 
     | 
    
         
            +
                    'Context'    => @context,
         
     | 
| 
      
 114 
     | 
    
         
            +
                    'PeerHost'   => @host,
         
     | 
| 
      
 115 
     | 
    
         
            +
                    'PeerPort'   => @port,
         
     | 
| 
      
 116 
     | 
    
         
            +
                    'SSL'        => true,
         
     | 
| 
      
 117 
     | 
    
         
            +
                    'SSLVersion' => :SSLv23,
         
     | 
| 
      
 118 
     | 
    
         
            +
                    'Timeout'    => @timeout
         
     | 
| 
      
 119 
     | 
    
         
            +
                  )
         
     | 
| 
      
 120 
     | 
    
         
            +
                rescue ::Exception => e
         
     | 
| 
      
 121 
     | 
    
         
            +
                  return :rejected
         
     | 
| 
      
 122 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 123 
     | 
    
         
            +
                  if scan_client
         
     | 
| 
      
 124 
     | 
    
         
            +
                    scan_client.close
         
     | 
| 
      
 125 
     | 
    
         
            +
                  end
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
                return :accepted
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
              def test_tls
         
     | 
| 
      
 131 
     | 
    
         
            +
                begin
         
     | 
| 
      
 132 
     | 
    
         
            +
                  scan_client = SSLScan::Socket::Tcp.create(
         
     | 
| 
      
 133 
     | 
    
         
            +
                    'Context'    => @context,
         
     | 
| 
      
 134 
     | 
    
         
            +
                    'PeerHost'   => @host,
         
     | 
| 
      
 135 
     | 
    
         
            +
                    'PeerPort'   => @port,
         
     | 
| 
      
 136 
     | 
    
         
            +
                    'SSL'        => true,
         
     | 
| 
      
 137 
     | 
    
         
            +
                    'SSLVersion' => :TLSv1,
         
     | 
| 
      
 138 
     | 
    
         
            +
                    'Timeout'    => @timeout
         
     | 
| 
      
 139 
     | 
    
         
            +
                  )
         
     | 
| 
      
 140 
     | 
    
         
            +
                rescue ::Exception => e
         
     | 
| 
      
 141 
     | 
    
         
            +
                  return :rejected
         
     | 
| 
      
 142 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 143 
     | 
    
         
            +
                  if scan_client
         
     | 
| 
      
 144 
     | 
    
         
            +
                    scan_client.close
         
     | 
| 
      
 145 
     | 
    
         
            +
                  end
         
     | 
| 
      
 146 
     | 
    
         
            +
                end
         
     | 
| 
      
 147 
     | 
    
         
            +
                return :accepted
         
     | 
| 
      
 148 
     | 
    
         
            +
              end
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
              # Tests the specified SSL Version and Cipher against the configured target
         
     | 
| 
      
 151 
     | 
    
         
            +
              # @param ssl_version [Symbol] The SSL version to use (:SSLv2,  :SSLv3, :TLSv1)
         
     | 
| 
      
 152 
     | 
    
         
            +
              # @param cipher [String] The SSL Cipher to use
         
     | 
| 
      
 153 
     | 
    
         
            +
              # @return [Symbol] Either :accepted or :rejected
         
     | 
| 
      
 154 
     | 
    
         
            +
              def test_cipher(ssl_version, cipher)
         
     | 
| 
      
 155 
     | 
    
         
            +
                validate_params(ssl_version,cipher)
         
     | 
| 
      
 156 
     | 
    
         
            +
                begin
         
     | 
| 
      
 157 
     | 
    
         
            +
                  scan_client = SSLScan::Socket::Tcp.create(
         
     | 
| 
      
 158 
     | 
    
         
            +
                    'Context'    => @context,
         
     | 
| 
      
 159 
     | 
    
         
            +
                    'PeerHost'   => @host,
         
     | 
| 
      
 160 
     | 
    
         
            +
                    'PeerPort'   => @port,
         
     | 
| 
      
 161 
     | 
    
         
            +
                    'SSL'        => true,
         
     | 
| 
      
 162 
     | 
    
         
            +
                    'SSLVersion' => ssl_version,
         
     | 
| 
      
 163 
     | 
    
         
            +
                    'SSLCipher'  => cipher,
         
     | 
| 
      
 164 
     | 
    
         
            +
                    'Timeout'    => @timeout
         
     | 
| 
      
 165 
     | 
    
         
            +
                  )
         
     | 
| 
      
 166 
     | 
    
         
            +
                rescue ::Exception => e
         
     | 
| 
      
 167 
     | 
    
         
            +
                  return :rejected
         
     | 
| 
      
 168 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 169 
     | 
    
         
            +
                  if scan_client
         
     | 
| 
      
 170 
     | 
    
         
            +
                    scan_client.close
         
     | 
| 
      
 171 
     | 
    
         
            +
                  end
         
     | 
| 
      
 172 
     | 
    
         
            +
                end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                return :accepted
         
     | 
| 
      
 175 
     | 
    
         
            +
              end
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
              # Retrieve the X509 Cert from the target service,
         
     | 
| 
      
 178 
     | 
    
         
            +
              # @param ssl_version [Symbol] The SSL version to use (:SSLv2,  :SSLv3, :TLSv1)
         
     | 
| 
      
 179 
     | 
    
         
            +
              # @param cipher [String] The SSL Cipher to use
         
     | 
| 
      
 180 
     | 
    
         
            +
              # @return [OpenSSL::X509::Certificate] if the certificate was retrieved
         
     | 
| 
      
 181 
     | 
    
         
            +
              # @return [Nil] if the cert couldn't be retrieved
         
     | 
| 
      
 182 
     | 
    
         
            +
              def get_cert(ssl_version, cipher)
         
     | 
| 
      
 183 
     | 
    
         
            +
                validate_params(ssl_version, cipher)
         
     | 
| 
      
 184 
     | 
    
         
            +
                begin
         
     | 
| 
      
 185 
     | 
    
         
            +
                  scan_client = SSLScan::Socket::Tcp.create(
         
     | 
| 
      
 186 
     | 
    
         
            +
                    'PeerHost'   => @host,
         
     | 
| 
      
 187 
     | 
    
         
            +
                    'PeerPort'   => @port,
         
     | 
| 
      
 188 
     | 
    
         
            +
                    'SSL'        => true,
         
     | 
| 
      
 189 
     | 
    
         
            +
                    'SSLVersion' => ssl_version,
         
     | 
| 
      
 190 
     | 
    
         
            +
                    'SSLCipher'  => cipher,
         
     | 
| 
      
 191 
     | 
    
         
            +
                    'Timeout'    => @timeout
         
     | 
| 
      
 192 
     | 
    
         
            +
                  )
         
     | 
| 
      
 193 
     | 
    
         
            +
                  cert = scan_client.peer_cert
         
     | 
| 
      
 194 
     | 
    
         
            +
                  if cert.kind_of? OpenSSL::X509::Certificate
         
     | 
| 
      
 195 
     | 
    
         
            +
                    return cert
         
     | 
| 
      
 196 
     | 
    
         
            +
                  else
         
     | 
| 
      
 197 
     | 
    
         
            +
                    return nil
         
     | 
| 
      
 198 
     | 
    
         
            +
                  end
         
     | 
| 
      
 199 
     | 
    
         
            +
                rescue ::Exception => e
         
     | 
| 
      
 200 
     | 
    
         
            +
                  return nil
         
     | 
| 
      
 201 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 202 
     | 
    
         
            +
                  if scan_client
         
     | 
| 
      
 203 
     | 
    
         
            +
                    scan_client.close
         
     | 
| 
      
 204 
     | 
    
         
            +
                  end
         
     | 
| 
      
 205 
     | 
    
         
            +
                end
         
     | 
| 
      
 206 
     | 
    
         
            +
              end
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
              protected
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
              # Validates that the SSL Version and Cipher are valid both seperately and
         
     | 
| 
      
 212 
     | 
    
         
            +
              # together as part of an SSL Context.
         
     | 
| 
      
 213 
     | 
    
         
            +
              # @param ssl_version [Symbol] The SSL version to use (:SSLv2,  :SSLv3, :TLSv1)
         
     | 
| 
      
 214 
     | 
    
         
            +
              # @param cipher [String] The SSL Cipher to use
         
     | 
| 
      
 215 
     | 
    
         
            +
              # @raise [StandardError] If an invalid or unsupported SSL Version was supplied
         
     | 
| 
      
 216 
     | 
    
         
            +
              # @raise [StandardError] If the cipher is not valid for that version of SSL
         
     | 
| 
      
 217 
     | 
    
         
            +
              def validate_params(ssl_version, cipher)
         
     | 
| 
      
 218 
     | 
    
         
            +
                raise StandardError, "The scanner configuration is invalid" unless valid?
         
     | 
| 
      
 219 
     | 
    
         
            +
                unless @supported_versions.include? ssl_version
         
     | 
| 
      
 220 
     | 
    
         
            +
                  raise StandardError, "SSL Version must be one of: #{@supported_versions.to_s}"
         
     | 
| 
      
 221 
     | 
    
         
            +
                end
         
     | 
| 
      
 222 
     | 
    
         
            +
                if ssl_version == :SSLv2 and sslv2 == false
         
     | 
| 
      
 223 
     | 
    
         
            +
                  raise StandardError, "Your OS hates freedom! Your OpenSSL libs are compiled without SSLv2 support!"
         
     | 
| 
      
 224 
     | 
    
         
            +
                else
         
     | 
| 
      
 225 
     | 
    
         
            +
                  unless OpenSSL::SSL::SSLContext.new(ssl_version).ciphers.flatten.include? cipher
         
     | 
| 
      
 226 
     | 
    
         
            +
                    raise StandardError, "Must be a valid SSL Cipher for #{ssl_version}!"
         
     | 
| 
      
 227 
     | 
    
         
            +
                  end
         
     | 
| 
      
 228 
     | 
    
         
            +
                end
         
     | 
| 
      
 229 
     | 
    
         
            +
              end
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
              def check_opensslv2
         
     | 
| 
      
 232 
     | 
    
         
            +
                begin
         
     | 
| 
      
 233 
     | 
    
         
            +
                  OpenSSL::SSL::SSLContext.new(:SSLv2)
         
     | 
| 
      
 234 
     | 
    
         
            +
                rescue
         
     | 
| 
      
 235 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 236 
     | 
    
         
            +
                end
         
     | 
| 
      
 237 
     | 
    
         
            +
                return true
         
     | 
| 
      
 238 
     | 
    
         
            +
              end
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
            end
         
     | 
| 
      
 241 
     | 
    
         
            +
            end
         
     |