File: net/telnets.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: Net#59
  class: Telnet#60
inherits from
  SimpleDelegator   
has properties
attribute: ssl [R] #61
constant: OPT_STARTTLS #63
constant: TLS_FOLLOWS #64
alias: preprocess_orig preprocess #66
method: ssl? #68
method: preprocess (2/E) / 1 #70
alias: waitfor_org waitfor #149
method: waitfor (2/E) / 1 #151
method: detect_sub_negotiation / 2 #230

Class Hierarchy

Object ( Builtin-Module )
SimpleDelegator
  Telnet ( Net ) #60

Code

   1  =begin
   2  = $RCSfile$ -- SSL/TLS enhancement for Net::Telnet.
   3 
   4  = Info
   5    'OpenSSL for Ruby 2' project
   6    Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
   7    All rights reserved.
   8 
   9  = Licence
  10    This program is licenced under the same licence as Ruby.
  11    (See the file 'LICENCE'.)
  12 
  13  = Version
  14    $Id: telnets.rb 13657 2007-10-08 11:16:54Z gotoyuzo $
  15    
  16    2001/11/06: Contiributed to Ruby/OpenSSL project.
  17 
  18  == class Net::Telnet
  19 
  20  This class will initiate SSL/TLS session automaticaly if the server
  21  sent OPT_STARTTLS. Some options are added for SSL/TLS.
  22 
  23    host = Net::Telnet::new({
  24             "Host"       => "localhost",
  25             "Port"       => "telnets",
  26             ## follows are new options.
  27             'CertFile'   => "user.crt",
  28             'KeyFile'    => "user.key",
  29             'CAFile'     => "/some/where/certs/casert.pem",
  30             'CAPath'     => "/some/where/caserts",
  31             'VerifyMode' => SSL::VERIFY_PEER,
  32             'VerifyCallback' => verify_proc
  33           })
  34 
  35  Or, the new options ('Cert', 'Key' and 'CACert') are available from
  36  Michal Rokos's OpenSSL module.
  37 
  38    cert_data = File.open("user.crt"){|io| io.read }
  39    pkey_data = File.open("user.key"){|io| io.read }
  40    cacert_data = File.open("your_ca.pem"){|io| io.read }
  41    host = Net::Telnet::new({
  42             "Host"       => "localhost",
  43             "Port"       => "telnets",
  44             'Cert'       => OpenSSL::X509::Certificate.new(cert_data)
  45             'Key'        => OpenSSL::PKey::RSA.new(pkey_data)
  46             'CACert'     => OpenSSL::X509::Certificate.new(cacert_data)
  47             'CAFile'     => "/some/where/certs/casert.pem",
  48             'CAPath'     => "/some/where/caserts",
  49             'VerifyMode' => SSL::VERIFY_PEER,
  50             'VerifyCallback' => verify_proc
  51           })
  52 
  53  This class is expected to be a superset of usual Net::Telnet.
  54  =end
  55 
  56  require "net/telnet"
  57  require "openssl"
  58 
  59  module Net
  60    class Telnet
  61      attr_reader :ssl
  62 
  63      OPT_STARTTLS       =  46.chr # "\056" # "\x2e" # Start TLS
  64      TLS_FOLLOWS        =   1.chr # "\001" # "\x01" # FOLLOWS (for STARTTLS)
  65 
  66      alias preprocess_orig preprocess
  67 
  68      def ssl?; @ssl; end
  69 
  70      def preprocess(string)
  71        # combine CR+NULL into CR
  72        string = string.gsub(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"]
  73 
  74        # combine EOL into "\n"
  75        string = string.gsub(/#{EOL}/no, "\n") unless @options["Binmode"]
  76 
  77        string.gsub(/#{IAC}(
  78                     [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
  79                     [#{DO}#{DONT}#{WILL}#{WONT}][#{OPT_BINARY}-#{OPT_EXOPL}]|
  80                     #{SB}[#{OPT_BINARY}-#{OPT_EXOPL}]
  81                       (#{IAC}#{IAC}|[^#{IAC}])+#{IAC}#{SE}
  82                   )/xno) do
  83          if    IAC == $1  # handle escaped IAC characters
  84            IAC
  85          elsif AYT == $1  # respond to "IAC AYT" (are you there)
  86            self.write("nobody here but us pigeons" + EOL)
  87            ''
  88          elsif DO[0] == $1[0]  # respond to "IAC DO x"
  89            if    OPT_BINARY[0] == $1[1]
  90              @telnet_option["BINARY"] = true
  91              self.write(IAC + WILL + OPT_BINARY)
  92            elsif OPT_STARTTLS[0] == $1[1]
  93              self.write(IAC + WILL + OPT_STARTTLS)
  94              self.write(IAC + SB + OPT_STARTTLS + TLS_FOLLOWS + IAC + SE)
  95            else
  96              self.write(IAC + WONT + $1[1..1])
  97            end
  98            ''
  99          elsif DONT[0] == $1[0]  # respond to "IAC DON'T x" with "IAC WON'T x"
 100            self.write(IAC + WONT + $1[1..1])
 101            ''
 102          elsif WILL[0] == $1[0]  # respond to "IAC WILL x"
 103            if    OPT_BINARY[0] == $1[1]
 104              self.write(IAC + DO + OPT_BINARY)
 105            elsif OPT_ECHO[0] == $1[1]
 106              self.write(IAC + DO + OPT_ECHO)
 107            elsif OPT_SGA[0]  == $1[1]
 108              @telnet_option["SGA"] = true
 109              self.write(IAC + DO + OPT_SGA)
 110            else
 111              self.write(IAC + DONT + $1[1..1])
 112            end
 113            ''
 114          elsif WONT[0] == $1[0]  # respond to "IAC WON'T x"
 115            if    OPT_ECHO[0] == $1[1]
 116              self.write(IAC + DONT + OPT_ECHO)
 117            elsif OPT_SGA[0]  == $1[1]
 118              @telnet_option["SGA"] = false
 119              self.write(IAC + DONT + OPT_SGA)
 120            else
 121              self.write(IAC + DONT + $1[1..1])
 122            end
 123            ''
 124          elsif SB[0] == $1[0]    # respond to "IAC SB xxx IAC SE"
 125            if    OPT_STARTTLS[0] == $1[1] && TLS_FOLLOWS[0] == $2[0]
 126              @sock = OpenSSL::SSL::SSLSocket.new(@sock)
 127              @sock.cert            = @options['Cert'] unless @sock.cert
 128              @sock.key             = @options['Key'] unless @sock.key
 129              @sock.ca_cert         = @options['CACert']
 130              @sock.ca_file         = @options['CAFile']
 131              @sock.ca_path         = @options['CAPath']
 132              @sock.timeout         = @options['Timeout']
 133              @sock.verify_mode     = @options['VerifyMode']
 134              @sock.verify_callback = @options['VerifyCallback']
 135              @sock.verify_depth    = @options['VerifyDepth']
 136              @sock.connect
 137              if @options['VerifyMode'] != OpenSSL::SSL::VERIFY_NONE
 138                @sock.post_connection_check(@options['Host'])
 139              end
 140              @ssl = true
 141            end
 142            ''
 143          else
 144            ''
 145          end
 146        end
 147      end # preprocess
 148      
 149      alias waitfor_org waitfor
 150 
 151      def waitfor(options)
 152        time_out = @options["Timeout"]
 153        waittime = @options["Waittime"]
 154 
 155        if options.kind_of?(Hash)
 156          prompt   = if options.has_key?("Match")
 157                       options["Match"]
 158                     elsif options.has_key?("Prompt")
 159                       options["Prompt"]
 160                     elsif options.has_key?("String")
 161                       Regexp.new( Regexp.quote(options["String"]) )
 162                     end
 163          time_out = options["Timeout"]  if options.has_key?("Timeout")
 164          waittime = options["Waittime"] if options.has_key?("Waittime")
 165        else
 166          prompt = options
 167        end
 168 
 169        if time_out == false
 170          time_out = nil
 171        end
 172 
 173        line = ''
 174        buf = ''
 175        @rest = '' unless @rest
 176 
 177        until(prompt === line and not IO::select([@sock], nil, nil, waittime))
 178          unless IO::select([@sock], nil, nil, time_out)
 179            raise TimeoutError, "timed-out; wait for the next data"
 180          end
 181          begin
 182            c = @rest + @sock.sysread(1024 * 1024)
 183            @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
 184            if @options["Telnetmode"]   
 185              pos = 0
 186              catch(:next){
 187                while true
 188                  case c[pos]
 189                  when IAC[0]
 190                    case c[pos+1]
 191                    when DO[0], DONT[0], WILL[0], WONT[0]
 192                      throw :next unless c[pos+2]
 193                      pos += 3
 194                    when SB[0]
 195                      ret = detect_sub_negotiation(c, pos)
 196                      throw :next unless ret
 197                      pos = ret
 198                    when nil
 199                      throw :next
 200                    else
 201                      pos += 2
 202                    end
 203                  when nil
 204                    throw :next
 205                  else
 206                    pos += 1
 207                  end
 208                end
 209              }
 210 
 211              buf = preprocess(c[0...pos])
 212              @rest = c[pos..-1]
 213            end
 214            @log.print(buf) if @options.has_key?("Output_log")
 215            line.concat(buf)
 216            yield buf if block_given?   
 217          rescue EOFError # End of file reached
 218            if line == ''
 219              line = nil
 220              yield nil if block_given? 
 221            end
 222            break
 223          end
 224        end
 225        line
 226      end
 227 
 228      private
 229 
 230      def detect_sub_negotiation(data, pos)
 231        return nil if data.length < pos+6  # IAC SB x param IAC SE
 232        pos += 3
 233        while true
 234          case data[pos]
 235          when IAC[0]
 236            if data[pos+1] == SE[0]
 237              pos += 2
 238              return pos
 239            else
 240              pos += 2
 241            end
 242          when nil
 243            return nil
 244          else
 245            pos += 1
 246          end
 247        end
 248      end
 249 
 250    end
 251  end