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 <[email protected]>
|
---|
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 11708 2007-02-12 23:01:19Z shyouhei $
|
---|
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 | @ssl = true
|
---|
138 | end
|
---|
139 | ''
|
---|
140 | else
|
---|
141 | ''
|
---|
142 | end
|
---|
143 | end
|
---|
144 | end # preprocess
|
---|
145 |
|
---|
146 | alias waitfor_org waitfor
|
---|
147 |
|
---|
148 | def waitfor(options)
|
---|
149 | time_out = @options["Timeout"]
|
---|
150 | waittime = @options["Waittime"]
|
---|
151 |
|
---|
152 | if options.kind_of?(Hash)
|
---|
153 | prompt = if options.has_key?("Match")
|
---|
154 | options["Match"]
|
---|
155 | elsif options.has_key?("Prompt")
|
---|
156 | options["Prompt"]
|
---|
157 | elsif options.has_key?("String")
|
---|
158 | Regexp.new( Regexp.quote(options["String"]) )
|
---|
159 | end
|
---|
160 | time_out = options["Timeout"] if options.has_key?("Timeout")
|
---|
161 | waittime = options["Waittime"] if options.has_key?("Waittime")
|
---|
162 | else
|
---|
163 | prompt = options
|
---|
164 | end
|
---|
165 |
|
---|
166 | if time_out == false
|
---|
167 | time_out = nil
|
---|
168 | end
|
---|
169 |
|
---|
170 | line = ''
|
---|
171 | buf = ''
|
---|
172 | @rest = '' unless @rest
|
---|
173 |
|
---|
174 | until(prompt === line and not IO::select([@sock], nil, nil, waittime))
|
---|
175 | unless IO::select([@sock], nil, nil, time_out)
|
---|
176 | raise TimeoutError, "timed-out; wait for the next data"
|
---|
177 | end
|
---|
178 | begin
|
---|
179 | c = @rest + @sock.sysread(1024 * 1024)
|
---|
180 | @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
|
---|
181 | if @options["Telnetmode"]
|
---|
182 | pos = 0
|
---|
183 | catch(:next){
|
---|
184 | while true
|
---|
185 | case c[pos]
|
---|
186 | when IAC[0]
|
---|
187 | case c[pos+1]
|
---|
188 | when DO[0], DONT[0], WILL[0], WONT[0]
|
---|
189 | throw :next unless c[pos+2]
|
---|
190 | pos += 3
|
---|
191 | when SB[0]
|
---|
192 | ret = detect_sub_negotiation(c, pos)
|
---|
193 | throw :next unless ret
|
---|
194 | pos = ret
|
---|
195 | when nil
|
---|
196 | throw :next
|
---|
197 | else
|
---|
198 | pos += 2
|
---|
199 | end
|
---|
200 | when nil
|
---|
201 | throw :next
|
---|
202 | else
|
---|
203 | pos += 1
|
---|
204 | end
|
---|
205 | end
|
---|
206 | }
|
---|
207 |
|
---|
208 | buf = preprocess(c[0...pos])
|
---|
209 | @rest = c[pos..-1]
|
---|
210 | end
|
---|
211 | @log.print(buf) if @options.has_key?("Output_log")
|
---|
212 | line.concat(buf)
|
---|
213 | yield buf if block_given?
|
---|
214 | rescue EOFError # End of file reached
|
---|
215 | if line == ''
|
---|
216 | line = nil
|
---|
217 | yield nil if block_given?
|
---|
218 | end
|
---|
219 | break
|
---|
220 | end
|
---|
221 | end
|
---|
222 | line
|
---|
223 | end
|
---|
224 |
|
---|
225 | private
|
---|
226 |
|
---|
227 | def detect_sub_negotiation(data, pos)
|
---|
228 | return nil if data.length < pos+6 # IAC SB x param IAC SE
|
---|
229 | pos += 3
|
---|
230 | while true
|
---|
231 | case data[pos]
|
---|
232 | when IAC[0]
|
---|
233 | if data[pos+1] == SE[0]
|
---|
234 | pos += 2
|
---|
235 | return pos
|
---|
236 | else
|
---|
237 | pos += 2
|
---|
238 | end
|
---|
239 | when nil
|
---|
240 | return nil
|
---|
241 | else
|
---|
242 | pos += 1
|
---|
243 | end
|
---|
244 | end
|
---|
245 | end
|
---|
246 |
|
---|
247 | end
|
---|
248 | end
|
---|