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