summaryrefslogtreecommitdiffstats
path: root/Demo/cwilib/telnetlib.py
blob: 3a48a79b75bd5ecbaae0dc3f73b19b2e4befe980 (plain)
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# A TELNET client class.  Based on RFC 854: TELNET Protocol
# Specification, by J. Postel and J. Reynolds


# Example:
#
# >>> from telnetlib import Telnet
# >>> tn = Telnet('voorn.cwi.nl', 79) # connect to finger port
# >>> tn.write('guido\r\n')
# >>> print tn.read_all()
# Login name: guido                       In real life: Guido van Rossum
# Office: M353,  x4127                    Home phone: 020-6225521
# Directory: /ufs/guido                   Shell: /usr/local/bin/esh
# On since Oct 28 11:02:16 on ttyq1   
# Project: Multimedia Kernel Systems
# No Plan.
# >>>
#
# Note that read() won't read until eof -- it just reads some data
# (but it guarantees to read at least one byte unless EOF is hit).
#
# It is possible to pass a Telnet object to select.select() in order
# to wait until more data is available.  Note that in this case,
# read_eager() may return '' even if there was data on the socket,
# because the protocol negotiation may have eaten the data.
# This is why EOFError is needed to distinguish between "no data"
# and "connection closed" (since the socket also appears ready for
# reading when it is closed).
#
# Bugs:
# - may hang when connection is slow in the middle of an IAC sequence
#
# To do:
# - option negotiation


# Imported modules
import socket
import select
import string
import regsub

# Tunable parameters
DEBUGLEVEL = 0

# Telnet protocol defaults
TELNET_PORT = 23

# Telnet protocol characters (don't change)
IAC  = chr(255)	# "Interpret As Command"
DONT = chr(254)
DO   = chr(253)
WONT = chr(252)
WILL = chr(251)


# Telnet interface class

class Telnet:

    # Constructor
    def __init__(self, host, *args):
	if not args:
	    port = TELNET_PORT
	else:
	    if len(args) > 1: raise TypeError, 'too many args'
	    port = args[0]
	    if not port: port = TELNET_PORT
	self.debuglevel = DEBUGLEVEL
	self.host = host
	self.port = port
	self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	self.sock.connect((self.host, self.port))
	self.rawq = ''
	self.irawq = 0
	self.cookedq = ''
	self.eof = 0

    # Destructor
    def __del__(self):
	self.close()

    # Debug message
    def msg(self, msg, *args):
	if self.debuglevel > 0:
	    print 'Telnet(%s,%d):' % (self.host, self.port), msg % args

    # Set debug level
    def set_debuglevel(self, debuglevel):
	self.debuglevel = debuglevel

    # Explicit close
    def close(self):
	if self.sock:
	    self.sock.close()
	self.sock = None
	self.eof = 1

    # Return socket (e.g. for select)
    def get_socket(self):
	return self.sock

    # Return socket's fileno (e.g. for select)
    def fileno(self):
	return self.sock.fileno()

    # Write a string to the socket, doubling any IAC characters
    # Might block if the connection is blocked
    # May raise socket.error if the connection is closed
    def write(self, buffer):
	if IAC in buffer:
	    buffer = regsub.gsub(IAC, IAC+IAC, buffer)
	self.sock.send(buffer)

    # The following read_* methods exist:
    # Special case:
    # - read_until() reads until a string is encountered or a timeout is hit
    # These may block:
    # - read_all() reads all data until EOF
    # - read_some() reads at least one byte until EOF
    # These may do I/O but won't block doing it:
    # - read_very_eager() reads all data available on the socket
    # - read_eager() reads either data already queued or some data
    #                available on the socket
    # These don't do I/O:
    # - read_lazy() reads all data in the raw queue (processing it first)
    # - read_very_lazy() reads all data in the cooked queue

    # Read until a given string is encountered or until timeout
    # Raise EOFError if connection closed and no cooked data available
    # Return '' if no cooked data available otherwise
    def read_until(self, match, *args):
	if not args:
	    timeout = None
	else:
	    if len(args) > 1: raise TypeError, 'too many args'
	    timeout = args[0]
	n = len(match)
	self.process_rawq()
	i = string.find(self.cookedq, match)
	if i >= 0:
	    i = i+n
	    buf = self.cookedq[:i]
	    self.cookedq = self.cookedq[i:]
	    return buf
	s_reply = ([self], [], [])
	s_args = s_reply
	if timeout is not None:
	    s_args = s_args + (timeout,)
	while not self.eof and apply(select.select, s_args) == s_reply:
	    i = max(0, len(self.cookedq)-n)
	    self.fill_rawq()
	    self.process_rawq()
	    i = string.find(self.cookedq, match, i)
	    if i >= 0:
		i = i+n
		buf = self.cookedq[:i]
		self.cookedq = self.cookedq[i:]
		return buf
	return self.read_very_lazy()

    # Read all data until EOF
    # Block until connection closed
    def read_all(self):
	self.process_rawq()
	while not self.eof:
	    self.fill_rawq()
	    self.process_rawq()
	buf = self.cookedq
	self.cookedq = ''
	return buf

    # Read at least one byte of cooked data unless EOF is hit
    # Return '' if EOF is hit
    # Block if no data is immediately available
    def read_some(self):
	self.process_rawq()
	while not self.cookedq and not self.eof:
	    self.fill_rawq()
	    self.process_rawq()
	buf = self.cookedq
	self.cookedq = ''
	return buf

    # Read everything that's possible without blocking in I/O (eager)
    # Raise EOFError if connection closed and no cooked data available
    # Return '' if no cooked data available otherwise
    # Don't block unless in the midst of an IAC sequence
    def read_very_eager(self):
	self.process_rawq()
	while not self.eof and self.sock_avail():
	    self.fill_rawq()
	    self.process_rawq()
	return self.read_very_lazy()

    # Read readily available data
    # Raise EOFError if connection closed and no cooked data available
    # Return '' if no cooked data available otherwise
    # Don't block unless in the midst of an IAC sequence
    def read_eager(self):
	self.process_rawq()
	while not self.cookedq and not self.eof and self.sock_avail():
	    self.fill_rawq()
	    self.process_rawq()
	return self.read_very_lazy()

    # Process and return data that's already in the queues (lazy)
    # Raise EOFError if connection closed and no data available
    # Return '' if no cooked data available otherwise
    # Don't block unless in the midst of an IAC sequence
    def read_lazy(self):
	self.process_rawq()
	return self.read_very_lazy()

    # Return any data available in the cooked queue (very lazy)
    # Raise EOFError if connection closed and no data available
    # Return '' if no cooked data available otherwise
    # Don't block
    def read_very_lazy(self):
	buf = self.cookedq
	self.cookedq = ''
	if not buf and self.eof and not self.rawq:
	    raise EOFError, 'telnet connection closed'
	return buf

    # Transfer from raw queue to cooked queue
    # Set self.eof when connection is closed
    # Don't block unless in the midst of an IAC sequence
    def process_rawq(self):
	buf = ''
	try:
	    while self.rawq:
		c = self.rawq_getchar()
		if c != IAC:
		    buf = buf + c
		    continue
		c = self.rawq_getchar()
		if c == IAC:
		    buf = buf + c
		elif c in (DO, DONT):
		    opt = self.rawq_getchar()
		    self.msg('IAC %s %d', c == DO and 'DO' or 'DONT', ord(c))
		    self.sock.send(IAC + WONT + opt)
		elif c in (WILL, WONT):
		    opt = self.rawq_getchar()
		    self.msg('IAC %s %d',
			  c == WILL and 'WILL' or 'WONT', ord(c))
		else:
		    self.msg('IAC %s not recognized' % `c`)
	except EOFError: # raised by self.rawq_getchar()
	    pass
	self.cookedq = self.cookedq + buf

    # Get next char from raw queue
    # Block if no data is immediately available
    # Raise EOFError when connection is closed
    def rawq_getchar(self):
	if not self.rawq:
	    self.fill_rawq()
	    if self.eof:
		raise EOFError
	c = self.rawq[self.irawq]
	self.irawq = self.irawq + 1
	if self.irawq >= len(self.rawq):
	    self.rawq = ''
	    self.irawq = 0
	return c

    # Fill raw queue from exactly one recv() system call
    # Block if no data is immediately available
    # Set self.eof when connection is closed
    def fill_rawq(self):
	if self.irawq >= len(self.rawq):
	    self.rawq = ''
	    self.irawq = 0
	# The buffer size should be fairly small so as to avoid quadratic
	# behavior in process_rawq() above
	buf = self.sock.recv(50)
	self.eof = (not buf)
	self.rawq = self.rawq + buf

    # Test whether data is available on the socket
    def sock_avail(self):
	return select.select([self], [], [], 0) == ([self], [], [])


# Test program
# Usage: test [-d] ... [host [port]]
def test():
    import sys, string, socket, select
    debuglevel = 0
    while sys.argv[1:] and sys.argv[1] == '-d':
	debuglevel = debuglevel+1
	del sys.argv[1]
    host = 'localhost'
    if sys.argv[1:]:
	host = sys.argv[1]
    port = 0
    if sys.argv[2:]:
	portstr = sys.argv[2]
	try:
	    port = string.atoi(portstr)
	except string.atoi_error:
	    port = socket.getservbyname(portstr, 'tcp')
    tn = Telnet(host, port)
    tn.set_debuglevel(debuglevel)
    while 1:
	rfd, wfd, xfd = select.select([tn, sys.stdin], [], [])
	if sys.stdin in rfd:
	    line = sys.stdin.readline()
	    tn.write(line)
	if tn in rfd:
	    try:
		text = tn.read_eager()
	    except EOFError:
		print '*** Connection closed by remote host ***'
		break
	    if text:
		sys.stdout.write(text)
		sys.stdout.flush()
    tn.close()