OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.conch.test.test_telnet -*- | |
2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 """ | |
6 Telnet protocol implementation. | |
7 | |
8 @author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>} | |
9 """ | |
10 | |
11 import struct | |
12 | |
13 from zope.interface import implements | |
14 | |
15 from twisted.internet import protocol, interfaces as iinternet, defer | |
16 from twisted.python import log | |
17 | |
18 MODE = chr(1) | |
19 EDIT = 1 | |
20 TRAPSIG = 2 | |
21 MODE_ACK = 4 | |
22 SOFT_TAB = 8 | |
23 LIT_ECHO = 16 | |
24 | |
25 # Characters gleaned from the various (and conflicting) RFCs. Not all of these
are correct. | |
26 | |
27 NULL = chr(0) # No operation. | |
28 BEL = chr(7) # Produces an audible or | |
29 # visible signal (which does | |
30 # NOT move the print head). | |
31 BS = chr(8) # Moves the print head one | |
32 # character position towards | |
33 # the left margin. | |
34 HT = chr(9) # Moves the printer to the | |
35 # next horizontal tab stop. | |
36 # It remains unspecified how | |
37 # either party determines or | |
38 # establishes where such tab | |
39 # stops are located. | |
40 LF = chr(10) # Moves the printer to the | |
41 # next print line, keeping the | |
42 # same horizontal position. | |
43 VT = chr(11) # Moves the printer to the | |
44 # next vertical tab stop. It | |
45 # remains unspecified how | |
46 # either party determines or | |
47 # establishes where such tab | |
48 # stops are located. | |
49 FF = chr(12) # Moves the printer to the top | |
50 # of the next page, keeping | |
51 # the same horizontal position. | |
52 CR = chr(13) # Moves the printer to the left | |
53 # margin of the current line. | |
54 | |
55 ECHO = chr(1) # User-to-Server: Asks the server to send | |
56 # Echos of the transmitted data. | |
57 SGA = chr(3) # Suppress Go Ahead. Go Ahead is silly | |
58 # and most modern servers should suppress | |
59 # it. | |
60 NAWS = chr(31) # Negotiate About Window Size. Indicate that | |
61 # information about the size of the terminal | |
62 # can be communicated. | |
63 LINEMODE = chr(34) # Allow line buffering to be | |
64 # negotiated about. | |
65 | |
66 SE = chr(240) # End of subnegotiation parameters. | |
67 NOP = chr(241) # No operation. | |
68 DM = chr(242) # "Data Mark": The data stream portion | |
69 # of a Synch. This should always be | |
70 # accompanied by a TCP Urgent | |
71 # notification. | |
72 BRK = chr(243) # NVT character Break. | |
73 IP = chr(244) # The function Interrupt Process. | |
74 AO = chr(245) # The function Abort Output | |
75 AYT = chr(246) # The function Are You There. | |
76 EC = chr(247) # The function Erase Character. | |
77 EL = chr(248) # The function Erase Line | |
78 GA = chr(249) # The Go Ahead signal. | |
79 SB = chr(250) # Indicates that what follows is | |
80 # subnegotiation of the indicated | |
81 # option. | |
82 WILL = chr(251) # Indicates the desire to begin | |
83 # performing, or confirmation that | |
84 # you are now performing, the | |
85 # indicated option. | |
86 WONT = chr(252) # Indicates the refusal to perform, | |
87 # or continue performing, the | |
88 # indicated option. | |
89 DO = chr(253) # Indicates the request that the | |
90 # other party perform, or | |
91 # confirmation that you are expecting | |
92 # the other party to perform, the | |
93 # indicated option. | |
94 DONT = chr(254) # Indicates the demand that the | |
95 # other party stop performing, | |
96 # or confirmation that you are no | |
97 # longer expecting the other party | |
98 # to perform, the indicated option. | |
99 IAC = chr(255) # Data Byte 255. Introduces a | |
100 # telnet command. | |
101 | |
102 LINEMODE_MODE = chr(1) | |
103 LINEMODE_EDIT = chr(1) | |
104 LINEMODE_TRAPSIG = chr(2) | |
105 LINEMODE_MODE_ACK = chr(4) | |
106 LINEMODE_SOFT_TAB = chr(8) | |
107 LINEMODE_LIT_ECHO = chr(16) | |
108 LINEMODE_FORWARDMASK = chr(2) | |
109 LINEMODE_SLC = chr(3) | |
110 LINEMODE_SLC_SYNCH = chr(1) | |
111 LINEMODE_SLC_BRK = chr(2) | |
112 LINEMODE_SLC_IP = chr(3) | |
113 LINEMODE_SLC_AO = chr(4) | |
114 LINEMODE_SLC_AYT = chr(5) | |
115 LINEMODE_SLC_EOR = chr(6) | |
116 LINEMODE_SLC_ABORT = chr(7) | |
117 LINEMODE_SLC_EOF = chr(8) | |
118 LINEMODE_SLC_SUSP = chr(9) | |
119 LINEMODE_SLC_EC = chr(10) | |
120 LINEMODE_SLC_EL = chr(11) | |
121 | |
122 LINEMODE_SLC_EW = chr(12) | |
123 LINEMODE_SLC_RP = chr(13) | |
124 LINEMODE_SLC_LNEXT = chr(14) | |
125 LINEMODE_SLC_XON = chr(15) | |
126 LINEMODE_SLC_XOFF = chr(16) | |
127 LINEMODE_SLC_FORW1 = chr(17) | |
128 LINEMODE_SLC_FORW2 = chr(18) | |
129 LINEMODE_SLC_MCL = chr(19) | |
130 LINEMODE_SLC_MCR = chr(20) | |
131 LINEMODE_SLC_MCWL = chr(21) | |
132 LINEMODE_SLC_MCWR = chr(22) | |
133 LINEMODE_SLC_MCBOL = chr(23) | |
134 LINEMODE_SLC_MCEOL = chr(24) | |
135 LINEMODE_SLC_INSRT = chr(25) | |
136 LINEMODE_SLC_OVER = chr(26) | |
137 LINEMODE_SLC_ECR = chr(27) | |
138 LINEMODE_SLC_EWR = chr(28) | |
139 LINEMODE_SLC_EBOL = chr(29) | |
140 LINEMODE_SLC_EEOL = chr(30) | |
141 | |
142 LINEMODE_SLC_DEFAULT = chr(3) | |
143 LINEMODE_SLC_VALUE = chr(2) | |
144 LINEMODE_SLC_CANTCHANGE = chr(1) | |
145 LINEMODE_SLC_NOSUPPORT = chr(0) | |
146 LINEMODE_SLC_LEVELBITS = chr(3) | |
147 | |
148 LINEMODE_SLC_ACK = chr(128) | |
149 LINEMODE_SLC_FLUSHIN = chr(64) | |
150 LINEMODE_SLC_FLUSHOUT = chr(32) | |
151 LINEMODE_EOF = chr(236) | |
152 LINEMODE_SUSP = chr(237) | |
153 LINEMODE_ABORT = chr(238) | |
154 | |
155 class ITelnetProtocol(iinternet.IProtocol): | |
156 def unhandledCommand(command, argument): | |
157 """A command was received but not understood. | |
158 """ | |
159 | |
160 def unhandledSubnegotiation(bytes): | |
161 """A subnegotiation command was received but not understood. | |
162 """ | |
163 | |
164 def enableLocal(option): | |
165 """Enable the given option locally. | |
166 | |
167 This should enable the given option on this side of the | |
168 telnet connection and return True. If False is returned, | |
169 the option will be treated as still disabled and the peer | |
170 will be notified. | |
171 """ | |
172 | |
173 def enableRemote(option): | |
174 """Indicate whether the peer should be allowed to enable this option. | |
175 | |
176 Returns True if the peer should be allowed to enable this option, | |
177 False otherwise. | |
178 """ | |
179 | |
180 def disableLocal(option): | |
181 """Disable the given option locally. | |
182 | |
183 Unlike enableLocal, this method cannot fail. The option must be | |
184 disabled. | |
185 """ | |
186 | |
187 def disableRemote(option): | |
188 """Indicate that the peer has disabled this option. | |
189 """ | |
190 | |
191 class ITelnetTransport(iinternet.ITransport): | |
192 def do(option): | |
193 """Indicate a desire for the peer to begin performing the given option. | |
194 | |
195 Returns a Deferred that fires with True when the peer begins performing | |
196 the option, or False when the peer refuses to perform it. If the peer | |
197 is already performing the given option, the Deferred will fail with | |
198 L{AlreadyEnabled}. If a negotiation regarding this option is already | |
199 in progress, the Deferred will fail with L{AlreadyNegotiating}. | |
200 | |
201 Note: It is currently possible that this Deferred will never fire, | |
202 if the peer never responds, or if the peer believes the option to | |
203 already be enabled. | |
204 """ | |
205 | |
206 def dont(option): | |
207 """Indicate a desire for the peer to cease performing the given option. | |
208 | |
209 Returns a Deferred that fires with True when the peer ceases performing | |
210 the option. If the peer is not performing the given option, the | |
211 Deferred will fail with L{AlreadyDisabled}. If negotiation regarding | |
212 this option is already in progress, the Deferred will fail with | |
213 L{AlreadyNegotiating}. | |
214 | |
215 Note: It is currently possible that this Deferred will never fire, | |
216 if the peer never responds, or if the peer believes the option to | |
217 already be disabled. | |
218 """ | |
219 | |
220 def will(option): | |
221 """Indicate our willingness to begin performing this option locally. | |
222 | |
223 Returns a Deferred that fires with True when the peer agrees to allow | |
224 us to begin performing this option, or False if the peer refuses to | |
225 allow us to begin performing it. If the option is already enabled | |
226 locally, the Deferred will fail with L{AlreadyEnabled}. If negotiation | |
227 regarding this option is already in progress, the Deferred will fail wit
h | |
228 L{AlreadyNegotiating}. | |
229 | |
230 Note: It is currently possible that this Deferred will never fire, | |
231 if the peer never responds, or if the peer believes the option to | |
232 already be enabled. | |
233 """ | |
234 | |
235 def wont(option): | |
236 """Indicate that we will stop performing the given option. | |
237 | |
238 Returns a Deferred that fires with True when the peer acknowledges | |
239 we have stopped performing this option. If the option is already | |
240 disabled locally, the Deferred will fail with L{AlreadyDisabled}. | |
241 If negotiation regarding this option is already in progress, | |
242 the Deferred will fail with L{AlreadyNegotiating}. | |
243 | |
244 Note: It is currently possible that this Deferred will never fire, | |
245 if the peer never responds, or if the peer believes the option to | |
246 already be disabled. | |
247 """ | |
248 | |
249 def requestNegotiation(about, bytes): | |
250 """Send a subnegotiation request. | |
251 | |
252 @param about: A byte indicating the feature being negotiated. | |
253 @param bytes: Any number of bytes containing specific information | |
254 about the negotiation being requested. No values in this string | |
255 need to be escaped, as this function will escape any value which | |
256 requires it. | |
257 """ | |
258 | |
259 class TelnetError(Exception): | |
260 pass | |
261 | |
262 class NegotiationError(TelnetError): | |
263 def __str__(self): | |
264 return self.__class__.__module__ + '.' + self.__class__.__name__ + ':' +
repr(self.args[0]) | |
265 | |
266 class OptionRefused(NegotiationError): | |
267 pass | |
268 | |
269 class AlreadyEnabled(NegotiationError): | |
270 pass | |
271 | |
272 class AlreadyDisabled(NegotiationError): | |
273 pass | |
274 | |
275 class AlreadyNegotiating(NegotiationError): | |
276 pass | |
277 | |
278 class TelnetProtocol(protocol.Protocol): | |
279 implements(ITelnetProtocol) | |
280 | |
281 def unhandledCommand(self, command, argument): | |
282 pass | |
283 | |
284 def unhandledSubnegotiation(self, command, bytes): | |
285 pass | |
286 | |
287 def enableLocal(self, option): | |
288 pass | |
289 | |
290 def enableRemote(self, option): | |
291 pass | |
292 | |
293 def disableLocal(self, option): | |
294 pass | |
295 | |
296 def disableRemote(self, option): | |
297 pass | |
298 | |
299 | |
300 class Telnet(protocol.Protocol): | |
301 """ | |
302 @ivar commandMap: A mapping of bytes to callables. When a | |
303 telnet command is received, the command byte (the first byte | |
304 after IAC) is looked up in this dictionary. If a callable is | |
305 found, it is invoked with the argument of the command, or None | |
306 if the command takes no argument. Values should be added to | |
307 this dictionary if commands wish to be handled. By default, | |
308 only WILL, WONT, DO, and DONT are handled. These should not | |
309 be overridden, as this class handles them correctly and | |
310 provides an API for interacting with them. | |
311 | |
312 @ivar negotiationMap: A mapping of bytes to callables. When | |
313 a subnegotiation command is received, the command byte (the | |
314 first byte after SB) is looked up in this dictionary. If | |
315 a callable is found, it is invoked with the argument of the | |
316 subnegotiation. Values should be added to this dictionary if | |
317 subnegotiations are to be handled. By default, no values are | |
318 handled. | |
319 | |
320 @ivar options: A mapping of option bytes to their current | |
321 state. This state is likely of little use to user code. | |
322 Changes should not be made to it. | |
323 | |
324 @ivar state: A string indicating the current parse state. It | |
325 can take on the values "data", "escaped", "command", "newline", | |
326 "subnegotiation", and "subnegotiation-escaped". Changes | |
327 should not be made to it. | |
328 | |
329 @ivar transport: This protocol's transport object. | |
330 """ | |
331 | |
332 # One of a lot of things | |
333 state = 'data' | |
334 | |
335 def __init__(self): | |
336 self.options = {} | |
337 self.negotiationMap = {} | |
338 self.commandMap = { | |
339 WILL: self.telnet_WILL, | |
340 WONT: self.telnet_WONT, | |
341 DO: self.telnet_DO, | |
342 DONT: self.telnet_DONT} | |
343 | |
344 def _write(self, bytes): | |
345 self.transport.write(bytes) | |
346 | |
347 class _OptionState: | |
348 class _Perspective: | |
349 state = 'no' | |
350 negotiating = False | |
351 onResult = None | |
352 | |
353 def __str__(self): | |
354 return self.state + ('*' * self.negotiating) | |
355 | |
356 def __init__(self): | |
357 self.us = self._Perspective() | |
358 self.him = self._Perspective() | |
359 | |
360 def __repr__(self): | |
361 return '<_OptionState us=%s him=%s>' % (self.us, self.him) | |
362 | |
363 def getOptionState(self, opt): | |
364 return self.options.setdefault(opt, self._OptionState()) | |
365 | |
366 def _do(self, option): | |
367 self._write(IAC + DO + option) | |
368 | |
369 def _dont(self, option): | |
370 self._write(IAC + DONT + option) | |
371 | |
372 def _will(self, option): | |
373 self._write(IAC + WILL + option) | |
374 | |
375 def _wont(self, option): | |
376 self._write(IAC + WONT + option) | |
377 | |
378 def will(self, option): | |
379 """Indicate our willingness to enable an option. | |
380 """ | |
381 s = self.getOptionState(option) | |
382 if s.us.negotiating or s.him.negotiating: | |
383 return defer.fail(AlreadyNegotiating(option)) | |
384 elif s.us.state == 'yes': | |
385 return defer.fail(AlreadyEnabled(option)) | |
386 else: | |
387 s.us.negotiating = True | |
388 s.us.onResult = d = defer.Deferred() | |
389 self._will(option) | |
390 return d | |
391 | |
392 def wont(self, option): | |
393 """Indicate we are not willing to enable an option. | |
394 """ | |
395 s = self.getOptionState(option) | |
396 if s.us.negotiating or s.him.negotiating: | |
397 return defer.fail(AlreadyNegotiating(option)) | |
398 elif s.us.state == 'no': | |
399 return defer.fail(AlreadyDisabled(option)) | |
400 else: | |
401 s.us.negotiating = True | |
402 s.us.onResult = d = defer.Deferred() | |
403 self._wont(option) | |
404 return d | |
405 | |
406 def do(self, option): | |
407 s = self.getOptionState(option) | |
408 if s.us.negotiating or s.him.negotiating: | |
409 return defer.fail(AlreadyNegotiating(option)) | |
410 elif s.him.state == 'yes': | |
411 return defer.fail(AlreadyEnabled(option)) | |
412 else: | |
413 s.him.negotiating = True | |
414 s.him.onResult = d = defer.Deferred() | |
415 self._do(option) | |
416 return d | |
417 | |
418 def dont(self, option): | |
419 s = self.getOptionState(option) | |
420 if s.us.negotiating or s.him.negotiating: | |
421 return defer.fail(AlreadyNegotiating(option)) | |
422 elif s.him.state == 'no': | |
423 return defer.fail(AlreadyDisabled(option)) | |
424 else: | |
425 s.him.negotiating = True | |
426 s.him.onResult = d = defer.Deferred() | |
427 self._dont(option) | |
428 return d | |
429 | |
430 def requestNegotiation(self, about, bytes): | |
431 bytes = bytes.replace(IAC, IAC * 2) | |
432 self._write(IAC + SB + bytes + IAC + SE) | |
433 | |
434 def dataReceived(self, data): | |
435 appDataBuffer = [] | |
436 | |
437 for b in data: | |
438 if self.state == 'data': | |
439 if b == IAC: | |
440 self.state = 'escaped' | |
441 elif b == '\r': | |
442 self.state = 'newline' | |
443 else: | |
444 appDataBuffer.append(b) | |
445 elif self.state == 'escaped': | |
446 if b == IAC: | |
447 appDataBuffer.append(b) | |
448 self.state = 'data' | |
449 elif b == SB: | |
450 self.state = 'subnegotiation' | |
451 self.commands = [] | |
452 elif b in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA): | |
453 self.state = 'data' | |
454 if appDataBuffer: | |
455 self.applicationDataReceived(''.join(appDataBuffer)) | |
456 del appDataBuffer[:] | |
457 self.commandReceived(b, None) | |
458 elif b in (WILL, WONT, DO, DONT): | |
459 self.state = 'command' | |
460 self.command = b | |
461 else: | |
462 raise ValueError("Stumped", b) | |
463 elif self.state == 'command': | |
464 self.state = 'data' | |
465 command = self.command | |
466 del self.command | |
467 if appDataBuffer: | |
468 self.applicationDataReceived(''.join(appDataBuffer)) | |
469 del appDataBuffer[:] | |
470 self.commandReceived(command, b) | |
471 elif self.state == 'newline': | |
472 if b == '\n': | |
473 appDataBuffer.append('\n') | |
474 elif b == '\0': | |
475 appDataBuffer.append('\r') | |
476 else: | |
477 appDataBuffer.append('\r' + b) | |
478 self.state = 'data' | |
479 elif self.state == 'subnegotiation': | |
480 if b == IAC: | |
481 self.state = 'subnegotiation-escaped' | |
482 else: | |
483 self.commands.append(b) | |
484 elif self.state == 'subnegotiation-escaped': | |
485 if b == SE: | |
486 self.state = 'data' | |
487 commands = self.commands | |
488 del self.commands | |
489 if appDataBuffer: | |
490 self.applicationDataReceived(''.join(appDataBuffer)) | |
491 del appDataBuffer[:] | |
492 self.negotiate(commands) | |
493 else: | |
494 self.state = 'subnegotiation' | |
495 self.commands.append(b) | |
496 else: | |
497 raise ValueError("How'd you do this?") | |
498 | |
499 if appDataBuffer: | |
500 self.applicationDataReceived(''.join(appDataBuffer)) | |
501 | |
502 | |
503 def connectionLost(self, reason): | |
504 for state in self.options.values(): | |
505 if state.us.onResult is not None: | |
506 d = state.us.onResult | |
507 state.us.onResult = None | |
508 d.errback(reason) | |
509 if state.him.onResult is not None: | |
510 d = state.him.onResult | |
511 state.him.onResult = None | |
512 d.errback(reason) | |
513 | |
514 def applicationDataReceived(self, bytes): | |
515 """Called with application-level data. | |
516 """ | |
517 | |
518 def unhandledCommand(self, command, argument): | |
519 """Called for commands for which no handler is installed. | |
520 """ | |
521 | |
522 def commandReceived(self, command, argument): | |
523 cmdFunc = self.commandMap.get(command) | |
524 if cmdFunc is None: | |
525 self.unhandledCommand(command, argument) | |
526 else: | |
527 cmdFunc(argument) | |
528 | |
529 def unhandledSubnegotiation(self, command, bytes): | |
530 """Called for subnegotiations for which no handler is installed. | |
531 """ | |
532 | |
533 def negotiate(self, bytes): | |
534 command, bytes = bytes[0], bytes[1:] | |
535 cmdFunc = self.negotiationMap.get(command) | |
536 if cmdFunc is None: | |
537 self.unhandledSubnegotiation(command, bytes) | |
538 else: | |
539 cmdFunc(bytes) | |
540 | |
541 def telnet_WILL(self, option): | |
542 s = self.getOptionState(option) | |
543 self.willMap[s.him.state, s.him.negotiating](self, s, option) | |
544 | |
545 def will_no_false(self, state, option): | |
546 # He is unilaterally offering to enable an option. | |
547 if self.enableRemote(option): | |
548 state.him.state = 'yes' | |
549 self._do(option) | |
550 else: | |
551 self._dont(option) | |
552 | |
553 def will_no_true(self, state, option): | |
554 # Peer agreed to enable an option in response to our request. | |
555 state.him.state = 'yes' | |
556 state.him.negotiating = False | |
557 d = state.him.onResult | |
558 state.him.onResult = None | |
559 d.callback(True) | |
560 assert self.enableRemote(option), "enableRemote must return True in this
context (for option %r)" % (option,) | |
561 | |
562 def will_yes_false(self, state, option): | |
563 # He is unilaterally offering to enable an already-enabled option. | |
564 # Ignore this. | |
565 pass | |
566 | |
567 def will_yes_true(self, state, option): | |
568 # This is a bogus state. It is here for completeness. It will | |
569 # never be entered. | |
570 assert False, "will_yes_true can never be entered, but was called with %
r, %r" % (state, option) | |
571 | |
572 willMap = {('no', False): will_no_false, ('no', True): will_no_true, | |
573 ('yes', False): will_yes_false, ('yes', True): will_yes_true} | |
574 | |
575 def telnet_WONT(self, option): | |
576 s = self.getOptionState(option) | |
577 self.wontMap[s.him.state, s.him.negotiating](self, s, option) | |
578 | |
579 def wont_no_false(self, state, option): | |
580 # He is unilaterally demanding that an already-disabled option be/remain
disabled. | |
581 # Ignore this (although we could record it and refuse subsequent enable
attempts | |
582 # from our side - he can always refuse them again though, so we won't) | |
583 pass | |
584 | |
585 def wont_no_true(self, state, option): | |
586 # Peer refused to enable an option in response to our request. | |
587 state.him.negotiating = False | |
588 d = state.him.onResult | |
589 state.him.onResult = None | |
590 d.errback(OptionRefused(option)) | |
591 | |
592 def wont_yes_false(self, state, option): | |
593 # Peer is unilaterally demanding that an option be disabled. | |
594 state.him.state = 'no' | |
595 self.disableRemote(option) | |
596 self._dont(option) | |
597 | |
598 def wont_yes_true(self, state, option): | |
599 # Peer agreed to disable an option at our request. | |
600 state.him.state = 'no' | |
601 state.him.negotiating = False | |
602 d = state.him.onResult | |
603 state.him.onResult = None | |
604 d.callback(True) | |
605 self.disableRemote(option) | |
606 | |
607 wontMap = {('no', False): wont_no_false, ('no', True): wont_no_true, | |
608 ('yes', False): wont_yes_false, ('yes', True): wont_yes_true} | |
609 | |
610 def telnet_DO(self, option): | |
611 s = self.getOptionState(option) | |
612 self.doMap[s.us.state, s.us.negotiating](self, s, option) | |
613 | |
614 def do_no_false(self, state, option): | |
615 # Peer is unilaterally requesting that we enable an option. | |
616 if self.enableLocal(option): | |
617 state.us.state = 'yes' | |
618 self._will(option) | |
619 else: | |
620 self._wont(option) | |
621 | |
622 def do_no_true(self, state, option): | |
623 # Peer agreed to allow us to enable an option at our request. | |
624 state.us.state = 'yes' | |
625 state.us.negotiating = False | |
626 d = state.us.onResult | |
627 state.us.onResult = None | |
628 d.callback(True) | |
629 self.enableLocal(option) | |
630 | |
631 def do_yes_false(self, state, option): | |
632 # Peer is unilaterally requesting us to enable an already-enabled option
. | |
633 # Ignore this. | |
634 pass | |
635 | |
636 def do_yes_true(self, state, option): | |
637 # This is a bogus state. It is here for completeness. It will never be | |
638 # entered. | |
639 assert False, "do_yes_true can never be entered, but was called with %r,
%r" % (state, option) | |
640 | |
641 doMap = {('no', False): do_no_false, ('no', True): do_no_true, | |
642 ('yes', False): do_yes_false, ('yes', True): do_yes_true} | |
643 | |
644 def telnet_DONT(self, option): | |
645 s = self.getOptionState(option) | |
646 self.dontMap[s.us.state, s.us.negotiating](self, s, option) | |
647 | |
648 def dont_no_false(self, state, option): | |
649 # Peer is unilaterally demanding us to disable an already-disabled optio
n. | |
650 # Ignore this. | |
651 pass | |
652 | |
653 def dont_no_true(self, state, option): | |
654 # This is a bogus state. It is here for completeness. It will never be | |
655 # entered. | |
656 assert False, "dont_no_true can never be entered, but was called with %r
, %r" % (state, option) | |
657 | |
658 | |
659 def dont_yes_false(self, state, option): | |
660 # Peer is unilaterally demanding we disable an option. | |
661 state.us.state = 'no' | |
662 self.disableLocal(option) | |
663 self._wont(option) | |
664 | |
665 def dont_yes_true(self, state, option): | |
666 # Peer acknowledged our notice that we will disable an option. | |
667 state.us.state = 'no' | |
668 state.us.negotiating = False | |
669 d = state.us.onResult | |
670 state.us.onResult = None | |
671 d.callback(True) | |
672 self.disableLocal(option) | |
673 | |
674 dontMap = {('no', False): dont_no_false, ('no', True): dont_no_true, | |
675 ('yes', False): dont_yes_false, ('yes', True): dont_yes_true} | |
676 | |
677 def enableLocal(self, option): | |
678 """ | |
679 Reject all attempts to enable options. | |
680 """ | |
681 return False | |
682 | |
683 | |
684 def enableRemote(self, option): | |
685 """ | |
686 Reject all attempts to enable options. | |
687 """ | |
688 return False | |
689 | |
690 | |
691 def disableLocal(self, option): | |
692 """ | |
693 Signal a programming error by raising an exception. | |
694 | |
695 L{enableLocal} must return true for the given value of C{option} in | |
696 order for this method to be called. If a subclass of L{Telnet} | |
697 overrides enableLocal to allow certain options to be enabled, it must | |
698 also override disableLocal to disable those options. | |
699 | |
700 @raise NotImplementedError: Always raised. | |
701 """ | |
702 raise NotImplementedError( | |
703 "Don't know how to disable local telnet option %r" % (option,)) | |
704 | |
705 | |
706 def disableRemote(self, option): | |
707 """ | |
708 Signal a programming error by raising an exception. | |
709 | |
710 L{enableRemote} must return true for the given value of C{option} in | |
711 order for this method to be called. If a subclass of L{Telnet} | |
712 overrides enableRemote to allow certain options to be enabled, it must | |
713 also override disableRemote tto disable those options. | |
714 | |
715 @raise NotImplementedError: Always raised. | |
716 """ | |
717 raise NotImplementedError( | |
718 "Don't know how to disable remote telnet option %r" % (option,)) | |
719 | |
720 | |
721 | |
722 class ProtocolTransportMixin: | |
723 def write(self, bytes): | |
724 self.transport.write(bytes.replace('\n', '\r\n')) | |
725 | |
726 def writeSequence(self, seq): | |
727 self.transport.writeSequence(seq) | |
728 | |
729 def loseConnection(self): | |
730 self.transport.loseConnection() | |
731 | |
732 def getHost(self): | |
733 return self.transport.getHost() | |
734 | |
735 def getPeer(self): | |
736 return self.transport.getPeer() | |
737 | |
738 class TelnetTransport(Telnet, ProtocolTransportMixin): | |
739 """ | |
740 @ivar protocol: An instance of the protocol to which this | |
741 transport is connected, or None before the connection is | |
742 established and after it is lost. | |
743 | |
744 @ivar protocolFactory: A callable which returns protocol instances | |
745 which provide L{ITelnetProtocol}. This will be invoked when a | |
746 connection is established. It is passed *protocolArgs and | |
747 **protocolKwArgs. | |
748 | |
749 @ivar protocolArgs: A tuple of additional arguments to | |
750 pass to protocolFactory. | |
751 | |
752 @ivar protocolKwArgs: A dictionary of additional arguments | |
753 to pass to protocolFactory. | |
754 """ | |
755 | |
756 disconnecting = False | |
757 | |
758 protocolFactory = None | |
759 protocol = None | |
760 | |
761 def __init__(self, protocolFactory=None, *a, **kw): | |
762 Telnet.__init__(self) | |
763 if protocolFactory is not None: | |
764 self.protocolFactory = protocolFactory | |
765 self.protocolArgs = a | |
766 self.protocolKwArgs = kw | |
767 | |
768 def connectionMade(self): | |
769 if self.protocolFactory is not None: | |
770 self.protocol = self.protocolFactory(*self.protocolArgs, **self.prot
ocolKwArgs) | |
771 assert ITelnetProtocol.providedBy(self.protocol) | |
772 try: | |
773 factory = self.factory | |
774 except AttributeError: | |
775 pass | |
776 else: | |
777 self.protocol.factory = factory | |
778 self.protocol.makeConnection(self) | |
779 | |
780 def connectionLost(self, reason): | |
781 Telnet.connectionLost(self, reason) | |
782 if self.protocol is not None: | |
783 try: | |
784 self.protocol.connectionLost(reason) | |
785 finally: | |
786 del self.protocol | |
787 | |
788 def enableLocal(self, option): | |
789 return self.protocol.enableLocal(option) | |
790 | |
791 def enableRemote(self, option): | |
792 return self.protocol.enableRemote(option) | |
793 | |
794 def disableLocal(self, option): | |
795 return self.protocol.disableLocal(option) | |
796 | |
797 def disableRemote(self, option): | |
798 return self.protocol.disableRemote(option) | |
799 | |
800 def unhandledSubnegotiation(self, command, bytes): | |
801 self.protocol.unhandledSubnegotiation(command, bytes) | |
802 | |
803 def unhandledCommand(self, command, argument): | |
804 self.protocol.unhandledCommand(command, argument) | |
805 | |
806 def applicationDataReceived(self, bytes): | |
807 self.protocol.dataReceived(bytes) | |
808 | |
809 def write(self, data): | |
810 ProtocolTransportMixin.write(self, data.replace('\xff','\xff\xff')) | |
811 | |
812 | |
813 class TelnetBootstrapProtocol(TelnetProtocol, ProtocolTransportMixin): | |
814 implements() | |
815 | |
816 protocol = None | |
817 | |
818 def __init__(self, protocolFactory, *args, **kw): | |
819 self.protocolFactory = protocolFactory | |
820 self.protocolArgs = args | |
821 self.protocolKwArgs = kw | |
822 | |
823 def connectionMade(self): | |
824 self.transport.negotiationMap[NAWS] = self.telnet_NAWS | |
825 self.transport.negotiationMap[LINEMODE] = self.telnet_LINEMODE | |
826 | |
827 for opt in (LINEMODE, NAWS, SGA): | |
828 self.transport.do(opt).addErrback(log.err) | |
829 for opt in (ECHO,): | |
830 self.transport.will(opt).addErrback(log.err) | |
831 | |
832 self.protocol = self.protocolFactory(*self.protocolArgs, **self.protocol
KwArgs) | |
833 | |
834 try: | |
835 factory = self.factory | |
836 except AttributeError: | |
837 pass | |
838 else: | |
839 self.protocol.factory = factory | |
840 | |
841 self.protocol.makeConnection(self) | |
842 | |
843 def connectionLost(self, reason): | |
844 if self.protocol is not None: | |
845 try: | |
846 self.protocol.connectionLost(reason) | |
847 finally: | |
848 del self.protocol | |
849 | |
850 def dataReceived(self, data): | |
851 self.protocol.dataReceived(data) | |
852 | |
853 def enableLocal(self, opt): | |
854 if opt == ECHO: | |
855 return True | |
856 elif opt == SGA: | |
857 return True | |
858 else: | |
859 return False | |
860 | |
861 def enableRemote(self, opt): | |
862 if opt == LINEMODE: | |
863 self.transport.requestNegotiation(LINEMODE, MODE + chr(TRAPSIG)) | |
864 return True | |
865 elif opt == NAWS: | |
866 return True | |
867 elif opt == SGA: | |
868 return True | |
869 else: | |
870 return False | |
871 | |
872 def telnet_NAWS(self, bytes): | |
873 # NAWS is client -> server *only*. self.protocol will | |
874 # therefore be an ITerminalTransport, the `.protocol' | |
875 # attribute of which will be an ITerminalProtocol. Maybe. | |
876 # You know what, XXX TODO clean this up. | |
877 if len(bytes) == 4: | |
878 width, height = struct.unpack('!HH', ''.join(bytes)) | |
879 self.protocol.terminalProtocol.terminalSize(width, height) | |
880 else: | |
881 log.msg("Wrong number of NAWS bytes") | |
882 | |
883 | |
884 linemodeSubcommands = { | |
885 LINEMODE_SLC: 'SLC'} | |
886 def telnet_LINEMODE(self, bytes): | |
887 revmap = {} | |
888 linemodeSubcommand = bytes[0] | |
889 if 0: | |
890 # XXX TODO: This should be enabled to parse linemode subnegotiation. | |
891 getattr(self, 'linemode_' + self.linemodeSubcommands[linemodeSubcomm
and])(bytes[1:]) | |
892 | |
893 def linemode_SLC(self, bytes): | |
894 chunks = zip(*[iter(bytes)]*3) | |
895 for slcFunction, slcValue, slcWhat in chunks: | |
896 # Later, we should parse stuff. | |
897 'SLC', ord(slcFunction), ord(slcValue), ord(slcWhat) | |
898 | |
899 from twisted.protocols import basic | |
900 | |
901 class StatefulTelnetProtocol(basic.LineReceiver, TelnetProtocol): | |
902 delimiter = '\n' | |
903 | |
904 state = 'Discard' | |
905 | |
906 def connectionLost(self, reason): | |
907 basic.LineReceiver.connectionLost(self, reason) | |
908 TelnetProtocol.connectionLost(self, reason) | |
909 | |
910 def lineReceived(self, line): | |
911 oldState = self.state | |
912 newState = getattr(self, "telnet_" + oldState)(line) | |
913 if newState is not None: | |
914 if self.state == oldState: | |
915 self.state = newState | |
916 else: | |
917 log.msg("Warning: state changed and new state returned") | |
918 | |
919 def telnet_Discard(self, line): | |
920 pass | |
921 | |
922 from twisted.cred import credentials | |
923 | |
924 class AuthenticatingTelnetProtocol(StatefulTelnetProtocol): | |
925 """A protocol which prompts for credentials and attempts to authenticate the
m. | |
926 | |
927 Username and password prompts are given (the password is obscured). When th
e | |
928 information is collected, it is passed to a portal and an avatar implementin
g | |
929 L{ITelnetProtocol} is requested. If an avatar is returned, it connected to
this | |
930 protocol's transport, and this protocol's transport is connected to it. | |
931 Otherwise, the user is re-prompted for credentials. | |
932 """ | |
933 | |
934 state = "User" | |
935 protocol = None | |
936 | |
937 def __init__(self, portal): | |
938 self.portal = portal | |
939 | |
940 def connectionMade(self): | |
941 self.transport.write("Username: ") | |
942 | |
943 def connectionLost(self, reason): | |
944 StatefulTelnetProtocol.connectionLost(self, reason) | |
945 if self.protocol is not None: | |
946 try: | |
947 self.protocol.connectionLost(reason) | |
948 self.logout() | |
949 finally: | |
950 del self.protocol, self.logout | |
951 | |
952 def telnet_User(self, line): | |
953 self.username = line | |
954 self.transport.will(ECHO) | |
955 self.transport.write("Password: ") | |
956 return 'Password' | |
957 | |
958 def telnet_Password(self, line): | |
959 username, password = self.username, line | |
960 del self.username | |
961 def login(ignored): | |
962 creds = credentials.UsernamePassword(username, password) | |
963 d = self.portal.login(creds, None, ITelnetProtocol) | |
964 d.addCallback(self._cbLogin) | |
965 d.addErrback(self._ebLogin) | |
966 self.transport.wont(ECHO).addCallback(login) | |
967 return 'Discard' | |
968 | |
969 def _cbLogin(self, ial): | |
970 interface, protocol, logout = ial | |
971 assert interface is ITelnetProtocol | |
972 self.protocol = protocol | |
973 self.logout = logout | |
974 self.state = 'Command' | |
975 | |
976 protocol.makeConnection(self.transport) | |
977 self.transport.protocol = protocol | |
978 | |
979 def _ebLogin(self, failure): | |
980 self.transport.write("\nAuthentication failed\n") | |
981 self.transport.write("Username: ") | |
982 self.state = "User" | |
983 | |
984 __all__ = [ | |
985 # Exceptions | |
986 'TelnetError', 'NegotiationError', 'OptionRefused', | |
987 'AlreadyNegotiating', 'AlreadyEnabled', 'AlreadyDisabled', | |
988 | |
989 # Interfaces | |
990 'ITelnetProtocol', 'ITelnetTransport', | |
991 | |
992 # Other stuff, protocols, etc. | |
993 'Telnet', 'TelnetProtocol', 'TelnetTransport', | |
994 'TelnetBootstrapProtocol', | |
995 | |
996 ] | |
OLD | NEW |