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