1
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
# See LICENSE for details.
4
"""Telnet protocol implementation.
6
API Stability: Unstable
8
@author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
13
from zope.interface import implements
15
from twisted.application import internet
16
from twisted.internet import protocol, interfaces as iinternet, defer
17
from twisted.python import log
26
# Characters gleaned from the various (and conflicting) RFCs. Not all of these are correct.
28
NULL = chr(0) # No operation.
29
BEL = chr(7) # Produces an audible or
30
# visible signal (which does
31
# NOT move the print head).
32
BS = chr(8) # Moves the print head one
33
# character position towards
35
HT = chr(9) # Moves the printer to the
36
# next horizontal tab stop.
37
# It remains unspecified how
38
# either party determines or
39
# establishes where such tab
41
LF = chr(10) # Moves the printer to the
42
# next print line, keeping the
43
# same horizontal position.
44
VT = chr(11) # Moves the printer to the
45
# next vertical tab stop. It
46
# remains unspecified how
47
# either party determines or
48
# establishes where such tab
50
FF = chr(12) # Moves the printer to the top
51
# of the next page, keeping
52
# the same horizontal position.
53
CR = chr(13) # Moves the printer to the left
54
# margin of the current line.
56
ECHO = chr(1) # User-to-Server: Asks the server to send
57
# Echos of the transmitted data.
58
SGA = chr(3) # Suppress Go Ahead. Go Ahead is silly
59
# and most modern servers should suppress
61
NAWS = chr(31) # Negotiate About Window Size. Indicate that
62
# information about the size of the terminal
63
# can be communicated.
64
LINEMODE = chr(34) # Allow line buffering to be
67
SE = chr(240) # End of subnegotiation parameters.
68
NOP = chr(241) # No operation.
69
DM = chr(242) # "Data Mark": The data stream portion
70
# of a Synch. This should always be
71
# accompanied by a TCP Urgent
73
BRK = chr(243) # NVT character Break.
74
IP = chr(244) # The function Interrupt Process.
75
AO = chr(245) # The function Abort Output
76
AYT = chr(246) # The function Are You There.
77
EC = chr(247) # The function Erase Character.
78
EL = chr(248) # The function Erase Line
79
GA = chr(249) # The Go Ahead signal.
80
SB = chr(250) # Indicates that what follows is
81
# subnegotiation of the indicated
83
WILL = chr(251) # Indicates the desire to begin
84
# performing, or confirmation that
85
# you are now performing, the
87
WONT = chr(252) # Indicates the refusal to perform,
88
# or continue performing, the
90
DO = chr(253) # Indicates the request that the
91
# other party perform, or
92
# confirmation that you are expecting
93
# the other party to perform, the
95
DONT = chr(254) # Indicates the demand that the
96
# other party stop performing,
97
# or confirmation that you are no
98
# longer expecting the other party
99
# to perform, the indicated option.
100
IAC = chr(255) # Data Byte 255. Introduces a
103
LINEMODE_MODE = chr(1)
104
LINEMODE_EDIT = chr(1)
105
LINEMODE_TRAPSIG = chr(2)
106
LINEMODE_MODE_ACK = chr(4)
107
LINEMODE_SOFT_TAB = chr(8)
108
LINEMODE_LIT_ECHO = chr(16)
109
LINEMODE_FORWARDMASK = chr(2)
110
LINEMODE_SLC = chr(3)
111
LINEMODE_SLC_SYNCH = chr(1)
112
LINEMODE_SLC_BRK = chr(2)
113
LINEMODE_SLC_IP = chr(3)
114
LINEMODE_SLC_AO = chr(4)
115
LINEMODE_SLC_AYT = chr(5)
116
LINEMODE_SLC_EOR = chr(6)
117
LINEMODE_SLC_ABORT = chr(7)
118
LINEMODE_SLC_EOF = chr(8)
119
LINEMODE_SLC_SUSP = chr(9)
120
LINEMODE_SLC_EC = chr(10)
121
LINEMODE_SLC_EL = chr(11)
123
LINEMODE_SLC_EW = chr(12)
124
LINEMODE_SLC_RP = chr(13)
125
LINEMODE_SLC_LNEXT = chr(14)
126
LINEMODE_SLC_XON = chr(15)
127
LINEMODE_SLC_XOFF = chr(16)
128
LINEMODE_SLC_FORW1 = chr(17)
129
LINEMODE_SLC_FORW2 = chr(18)
130
LINEMODE_SLC_MCL = chr(19)
131
LINEMODE_SLC_MCR = chr(20)
132
LINEMODE_SLC_MCWL = chr(21)
133
LINEMODE_SLC_MCWR = chr(22)
134
LINEMODE_SLC_MCBOL = chr(23)
135
LINEMODE_SLC_MCEOL = chr(24)
136
LINEMODE_SLC_INSRT = chr(25)
137
LINEMODE_SLC_OVER = chr(26)
138
LINEMODE_SLC_ECR = chr(27)
139
LINEMODE_SLC_EWR = chr(28)
140
LINEMODE_SLC_EBOL = chr(29)
141
LINEMODE_SLC_EEOL = chr(30)
143
LINEMODE_SLC_DEFAULT = chr(3)
144
LINEMODE_SLC_VALUE = chr(2)
145
LINEMODE_SLC_CANTCHANGE = chr(1)
146
LINEMODE_SLC_NOSUPPORT = chr(0)
147
LINEMODE_SLC_LEVELBITS = chr(3)
149
LINEMODE_SLC_ACK = chr(128)
150
LINEMODE_SLC_FLUSHIN = chr(64)
151
LINEMODE_SLC_FLUSHOUT = chr(32)
152
LINEMODE_EOF = chr(236)
153
LINEMODE_SUSP = chr(237)
154
LINEMODE_ABORT = chr(238)
156
class ITelnetProtocol(iinternet.IProtocol):
157
def unhandledCommand(command, argument):
158
"""A command was received but not understood.
161
def unhandledSubnegotiation(bytes):
162
"""A subnegotiation command was received but not understood.
165
def enableLocal(option):
166
"""Enable the given option locally.
168
This should enable the given option on this side of the
169
telnet connection and return True. If False is returned,
170
the option will be treated as still disabled and the peer
174
def enableRemote(option):
175
"""Indicate whether the peer should be allowed to enable this option.
177
Returns True if the peer should be allowed to enable this option,
181
def disableLocal(option):
182
"""Disable the given option locally.
184
Unlike enableLocal, this method cannot fail. The option must be
188
def disableRemote(option):
189
"""Indicate that the peer has disabled this option.
192
class ITelnetTransport(iinternet.ITransport):
194
"""Indicate a desire for the peer to begin performing the given option.
196
Returns a Deferred that fires with True when the peer begins performing
197
the option, or False when the peer refuses to perform it. If the peer
198
is already performing the given option, the Deferred will fail with
199
L{AlreadyEnabled}. If a negotiation regarding this option is already
200
in progress, the Deferred will fail with L{AlreadyNegotiating}.
202
Note: It is currently possible that this Deferred will never fire,
203
if the peer never responds, or if the peer believes the option to
208
"""Indicate a desire for the peer to cease performing the given option.
210
Returns a Deferred that fires with True when the peer ceases performing
211
the option. If the peer is not performing the given option, the
212
Deferred will fail with L{AlreadyDisabled}. If negotiation regarding
213
this option is already in progress, the Deferred will fail with
214
L{AlreadyNegotiating}.
216
Note: It is currently possible that this Deferred will never fire,
217
if the peer never responds, or if the peer believes the option to
222
"""Indicate our willingness to begin performing this option locally.
224
Returns a Deferred that fires with True when the peer agrees to allow
225
us to begin performing this option, or False if the peer refuses to
226
allow us to begin performing it. If the option is already enabled
227
locally, the Deferred will fail with L{AlreadyEnabled}. If negotiation
228
regarding this option is already in progress, the Deferred will fail with
229
L{AlreadyNegotiating}.
231
Note: It is currently possible that this Deferred will never fire,
232
if the peer never responds, or if the peer believes the option to
237
"""Indicate that we will stop performing the given option.
239
Returns a Deferred that fires with True when the peer acknowledges
240
we have stopped performing this option. If the option is already
241
disabled locally, the Deferred will fail with L{AlreadyDisabled}.
242
If negotiation regarding this option is already in progress,
243
the Deferred will fail with L{AlreadyNegotiating}.
245
Note: It is currently possible that this Deferred will never fire,
246
if the peer never responds, or if the peer believes the option to
250
def requestNegotiation(about, bytes):
251
"""Send a subnegotiation request.
253
@param about: A byte indicating the feature being negotiated.
254
@param bytes: Any number of bytes containing specific information
255
about the negotiation being requested. No values in this string
256
need to be escaped, as this function will escape any value which
260
class TelnetError(Exception):
263
class NegotiationError(TelnetError):
265
return self.__class__.__module__ + '.' + self.__class__.__name__ + ':' + repr(self.args[0])
267
class OptionRefused(NegotiationError):
270
class AlreadyEnabled(NegotiationError):
273
class AlreadyDisabled(NegotiationError):
276
class AlreadyNegotiating(NegotiationError):
279
class TelnetProtocol(protocol.Protocol):
280
implements(ITelnetProtocol)
282
def unhandledCommand(self, command, argument):
285
def unhandledSubnegotiation(self, command, bytes):
288
def enableLocal(self, option):
291
def enableRemote(self, option):
294
def disableLocal(self, option):
297
def disableRemote(self, option):
301
class Telnet(protocol.Protocol):
303
@ivar commandMap: A mapping of bytes to callables. When a
304
telnet command is received, the command byte (the first byte
305
after IAC) is looked up in this dictionary. If a callable is
306
found, it is invoked with the argument of the command, or None
307
if the command takes no argument. Values should be added to
308
this dictionary if commands wish to be handled. By default,
309
only WILL, WONT, DO, and DONT are handled. These should not
310
be overridden, as this class handles them correctly and
311
provides an API for interacting with them.
313
@ivar negotiationMap: A mapping of bytes to callables. When
314
a subnegotiation command is received, the command byte (the
315
first byte after SB) is looked up in this dictionary. If
316
a callable is found, it is invoked with the argument of the
317
subnegotiation. Values should be added to this dictionary if
318
subnegotiations are to be handled. By default, no values are
321
@ivar options: A mapping of option bytes to their current
322
state. This state is likely of little use to user code.
323
Changes should not be made to it.
325
@ivar state: A string indicating the current parse state. It
326
can take on the values "data", "escaped", "command", "newline",
327
"subnegotiation", and "subnegotiation-escaped". Changes
328
should not be made to it.
330
@ivar transport: This protocol's transport object.
333
# One of a lot of things
338
self.negotiationMap = {}
340
WILL: self.telnet_WILL,
341
WONT: self.telnet_WONT,
343
DONT: self.telnet_DONT}
345
def _write(self, bytes):
346
self.transport.write(bytes)
355
return self.state + ('*' * self.negotiating)
358
self.us = self._Perspective()
359
self.him = self._Perspective()
362
return '<_OptionState us=%s him=%s>' % (self.us, self.him)
364
def getOptionState(self, opt):
365
return self.options.setdefault(opt, self._OptionState())
367
def _do(self, option):
368
self._write(IAC + DO + option)
370
def _dont(self, option):
371
self._write(IAC + DONT + option)
373
def _will(self, option):
374
self._write(IAC + WILL + option)
376
def _wont(self, option):
377
self._write(IAC + WONT + option)
379
def will(self, option):
380
"""Indicate our willingness to enable an option.
382
s = self.getOptionState(option)
383
if s.us.negotiating or s.him.negotiating:
384
return defer.fail(AlreadyNegotiating(option))
385
elif s.us.state == 'yes':
386
return defer.fail(AlreadyEnabled(option))
388
s.us.negotiating = True
389
s.us.onResult = d = defer.Deferred()
393
def wont(self, option):
394
"""Indicate we are not willing to enable an option.
396
s = self.getOptionState(option)
397
if s.us.negotiating or s.him.negotiating:
398
return defer.fail(AlreadyNegotiating(option))
399
elif s.us.state == 'no':
400
return defer.fail(AlreadyDisabled(option))
402
s.us.negotiating = True
403
s.us.onResult = d = defer.Deferred()
407
def do(self, option):
408
s = self.getOptionState(option)
409
if s.us.negotiating or s.him.negotiating:
410
return defer.fail(AlreadyNegotiating(option))
411
elif s.him.state == 'yes':
412
return defer.fail(AlreadyEnabled(option))
414
s.him.negotiating = True
415
s.him.onResult = d = defer.Deferred()
419
def dont(self, option):
420
s = self.getOptionState(option)
421
if s.us.negotiating or s.him.negotiating:
422
return defer.fail(AlreadyNegotiating(option))
423
elif s.him.state == 'no':
424
return defer.fail(AlreadyDisabled(option))
426
s.him.negotiating = True
427
s.him.onResult = d = defer.Deferred()
431
def requestNegotiation(self, about, bytes):
432
bytes = bytes.replace(IAC, IAC * 2)
433
self._write(IAC + SB + bytes + IAC + SE)
435
def dataReceived(self, data):
436
# Most grossly inefficient implementation ever
438
if self.state == 'data':
440
self.state = 'escaped'
442
self.state = 'newline'
444
self.applicationDataReceived(b)
445
elif self.state == 'escaped':
447
self.applicationDataReceived(b)
450
self.state = 'subnegotiation'
452
elif b in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA):
454
self.commandReceived(b, None)
455
elif b in (WILL, WONT, DO, DONT):
456
self.state = 'command'
459
raise ValueError("Stumped", b)
460
elif self.state == 'command':
462
command = self.command
464
self.commandReceived(command, b)
465
elif self.state == 'newline':
467
self.applicationDataReceived('\n')
469
self.applicationDataReceived('\r')
471
self.applicationDataReceived('\r' + b)
473
elif self.state == 'subnegotiation':
475
self.state = 'subnegotiation-escaped'
477
self.commands.append(b)
478
elif self.state == 'subnegotiation-escaped':
481
commands = self.commands
483
self.negotiate(commands)
485
self.state = 'subnegotiation'
486
self.commands.append(b)
488
raise ValueError("How'd you do this?")
490
def connectionLost(self, reason):
491
for state in self.options.values():
492
if state.us.onResult is not None:
493
d = state.us.onResult
494
state.us.onResult = None
496
if state.him.onResult is not None:
497
d = state.him.onResult
498
state.him.onResult = None
501
def applicationDataReceived(self, bytes):
502
"""Called with application-level data.
505
def unhandledCommand(self, command, argument):
506
"""Called for commands for which no handler is installed.
509
def commandReceived(self, command, argument):
510
cmdFunc = self.commandMap.get(command)
512
self.unhandledCommand(command, argument)
516
def unhandledSubnegotiation(self, command, bytes):
517
"""Called for subnegotiations for which no handler is installed.
520
def negotiate(self, bytes):
521
command, bytes = bytes[0], bytes[1:]
522
cmdFunc = self.negotiationMap.get(command)
524
self.unhandledSubnegotiation(command, bytes)
528
def telnet_WILL(self, option):
529
s = self.getOptionState(option)
530
self.willMap[s.him.state, s.him.negotiating](self, s, option)
532
def will_no_false(self, state, option):
533
# He is unilaterally offering to enable an option.
534
if self.enableRemote(option):
535
state.him.state = 'yes'
540
def will_no_true(self, state, option):
541
# Peer agreed to enable an option in response to our request.
542
state.him.state = 'yes'
543
state.him.negotiating = False
544
d = state.him.onResult
545
state.him.onResult = None
547
assert self.enableRemote(option), "enableRemote must return True in this context (for option %r)" % (option,)
549
def will_yes_false(self, state, option):
550
# He is unilaterally offering to enable an already-enabled option.
554
def will_yes_true(self, state, option):
555
# This is a bogus state. It is here for completeness. It will
557
assert False, "will_yes_true can never be entered, but was called with %r, %r" % (state, option)
559
willMap = {('no', False): will_no_false, ('no', True): will_no_true,
560
('yes', False): will_yes_false, ('yes', True): will_yes_true}
562
def telnet_WONT(self, option):
563
s = self.getOptionState(option)
564
self.wontMap[s.him.state, s.him.negotiating](self, s, option)
566
def wont_no_false(self, state, option):
567
# He is unilaterally demanding that an already-disabled option be/remain disabled.
568
# Ignore this (although we could record it and refuse subsequent enable attempts
569
# from our side - he can always refuse them again though, so we won't)
572
def wont_no_true(self, state, option):
573
# Peer refused to enable an option in response to our request.
574
state.him.negotiating = False
575
d = state.him.onResult
576
state.him.onResult = None
577
d.errback(OptionRefused(option))
579
def wont_yes_false(self, state, option):
580
# Peer is unilaterally demanding that an option be disabled.
581
state.him.state = 'no'
582
self.disableRemote(option)
585
def wont_yes_true(self, state, option):
586
# Peer agreed to disable an option at our request.
587
state.him.state = 'no'
588
state.him.negotiating = False
589
d = state.him.onResult
590
state.him.onResult = None
592
self.disableRemote(option)
594
wontMap = {('no', False): wont_no_false, ('no', True): wont_no_true,
595
('yes', False): wont_yes_false, ('yes', True): wont_yes_true}
597
def telnet_DO(self, option):
598
s = self.getOptionState(option)
599
self.doMap[s.us.state, s.us.negotiating](self, s, option)
601
def do_no_false(self, state, option):
602
# Peer is unilaterally requesting that we enable an option.
603
if self.enableLocal(option):
604
state.us.state = 'yes'
609
def do_no_true(self, state, option):
610
# Peer agreed to allow us to enable an option at our request.
611
state.us.state = 'yes'
612
state.us.negotiating = False
613
d = state.us.onResult
614
state.us.onResult = None
616
self.enableLocal(option)
618
def do_yes_false(self, state, option):
619
# Peer is unilaterally requesting us to enable an already-enabled option.
623
def do_yes_true(self, state, option):
624
# This is a bogus state. It is here for completeness. It will never be
626
assert False, "do_yes_true can never be entered, but was called with %r, %r" % (state, option)
628
doMap = {('no', False): do_no_false, ('no', True): do_no_true,
629
('yes', False): do_yes_false, ('yes', True): do_yes_true}
631
def telnet_DONT(self, option):
632
s = self.getOptionState(option)
633
self.dontMap[s.us.state, s.us.negotiating](self, s, option)
635
def dont_no_false(self, state, option):
636
# Peer is unilaterally demanding us to disable an already-disabled option.
640
def dont_no_true(self, state, option):
641
# This is a bogus state. It is here for completeness. It will never be
643
assert False, "dont_no_true can never be entered, but was called with %r, %r" % (state, option)
646
def dont_yes_false(self, state, option):
647
# Peer is unilaterally demanding we disable an option.
648
state.us.state = 'no'
649
self.disableLocal(option)
652
def dont_yes_true(self, state, option):
653
# Peer acknowledged our notice that we will disable an option.
654
state.us.state = 'no'
655
state.us.negotiating = False
656
d = state.us.onResult
657
state.us.onResult = None
659
self.disableLocal(option)
661
dontMap = {('no', False): dont_no_false, ('no', True): dont_no_true,
662
('yes', False): dont_yes_false, ('yes', True): dont_yes_true}
664
def enableLocal(self, option):
665
return self.protocol.enableLocal(option)
667
def enableRemote(self, option):
668
return self.protocol.enableRemote(option)
670
def disableLocal(self, option):
671
return self.protocol.disableLocal(option)
673
def disableRemote(self, option):
674
return self.protocol.disableRemote(option)
676
class ProtocolTransportMixin:
677
def write(self, bytes):
678
self.transport.write(bytes.replace('\n', '\r\n'))
680
def writeSequence(self, seq):
681
self.transport.writeSequence(seq)
683
def loseConnection(self):
684
self.transport.loseConnection()
687
return self.transport.getHost()
690
return self.transport.getPeer()
692
class TelnetTransport(Telnet, ProtocolTransportMixin):
694
@ivar protocol: An instance of the protocol to which this
695
transport is connected, or None before the connection is
696
established and after it is lost.
698
@ivar protocolFactory: A callable which returns protocol instances
699
which provide L{ITelnetProtocol}. This will be invoked when a
700
connection is established. It is passed *protocolArgs and
703
@ivar protocolArgs: A tuple of additional arguments to
704
pass to protocolFactory.
706
@ivar protocolKwArgs: A dictionary of additional arguments
707
to pass to protocolFactory.
710
disconnecting = False
712
protocolFactory = None
715
def __init__(self, protocolFactory=None, *a, **kw):
716
Telnet.__init__(self)
717
if protocolFactory is not None:
718
self.protocolFactory = protocolFactory
719
self.protocolArgs = a
720
self.protocolKwArgs = kw
722
def connectionMade(self):
723
if self.protocolFactory is not None:
724
self.protocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
725
assert ITelnetProtocol.providedBy(self.protocol)
727
factory = self.factory
728
except AttributeError:
731
self.protocol.factory = factory
732
self.protocol.makeConnection(self)
734
def connectionLost(self, reason):
735
Telnet.connectionLost(self, reason)
736
if self.protocol is not None:
738
self.protocol.connectionLost(reason)
742
def enableLocal(self, option):
743
return self.protocol.enableLocal(option)
745
def enableRemote(self, option):
746
return self.protocol.enableRemote(option)
748
def disableLocal(self, option):
749
return self.protocol.disableLocal(option)
751
def disableRemote(self, option):
752
return self.protocol.disableRemote(option)
754
def unhandledSubnegotiation(self, command, bytes):
755
self.protocol.unhandledSubnegotiation(command, bytes)
757
def unhandledCommand(self, command, argument):
758
self.protocol.unhandledCommand(command, argument)
760
def applicationDataReceived(self, bytes):
761
self.protocol.dataReceived(bytes)
763
def write(self, data):
764
ProtocolTransportMixin.write(self, data.replace('\xff','\xff\xff'))
767
class TelnetBootstrapProtocol(TelnetProtocol, ProtocolTransportMixin):
772
def __init__(self, protocolFactory, *args, **kw):
773
self.protocolFactory = protocolFactory
774
self.protocolArgs = args
775
self.protocolKwArgs = kw
777
def connectionMade(self):
778
self.transport.negotiationMap[NAWS] = self.telnet_NAWS
779
self.transport.negotiationMap[LINEMODE] = self.telnet_LINEMODE
781
for opt in (LINEMODE, NAWS, SGA):
782
self.transport.do(opt).addErrback(log.err)
784
self.transport.will(opt).addErrback(log.err)
786
self.protocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
789
factory = self.factory
790
except AttributeError:
793
self.protocol.factory = factory
795
self.protocol.makeConnection(self)
797
def connectionLost(self, reason):
798
if self.protocol is not None:
800
self.protocol.connectionLost(reason)
804
def dataReceived(self, data):
805
self.protocol.dataReceived(data)
807
def enableLocal(self, opt):
815
def enableRemote(self, opt):
817
self.transport.requestNegotiation(LINEMODE, MODE + chr(TRAPSIG))
826
def telnet_NAWS(self, bytes):
827
# NAWS is client -> server *only*. self.protocol will
828
# therefore be an ITerminalTransport, the `.protocol'
829
# attribute of which will be an ITerminalProtocol. Maybe.
830
# You know what, XXX TODO clean this up.
832
width, height = struct.unpack('!HH', ''.join(bytes))
833
self.protocol.terminalProtocol.terminalSize(width, height)
835
log.msg("Wrong number of NAWS bytes")
838
linemodeSubcommands = {
840
def telnet_LINEMODE(self, bytes):
842
linemodeSubcommand = bytes[0]
844
# XXX TODO: This should be enabled to parse linemode subnegotiation.
845
getattr(self, 'linemode_' + self.linemodeSubcommands[linemodeSubcommand])(bytes[1:])
847
def linemode_SLC(self, bytes):
848
chunks = zip(*[iter(bytes)]*3)
849
for slcFunction, slcValue, slcWhat in chunks:
850
# Later, we should parse stuff.
851
'SLC', ord(slcFunction), ord(slcValue), ord(slcWhat)
853
from twisted.protocols import basic
855
class StatefulTelnetProtocol(basic.LineReceiver, TelnetProtocol):
860
def connectionLost(self, reason):
861
basic.LineReceiver.connectionLost(self, reason)
862
TelnetProtocol.connectionLost(self, reason)
864
def lineReceived(self, line):
865
oldState = self.state
866
newState = getattr(self, "telnet_" + oldState)(line)
867
if newState is not None:
868
if self.state == oldState:
869
self.state = newState
871
log.msg("Warning: state changed and new state returned")
873
def telnet_Discard(self, line):
876
from twisted.cred import credentials
878
class AuthenticatingTelnetProtocol(StatefulTelnetProtocol):
879
"""A protocol which prompts for credentials and attempts to authenticate them.
881
Username and password prompts are given (the password is obscured). When the
882
information is collected, it is passed to a portal and an avatar implementing
883
L{ITelnetProtocol} is requested. If an avatar is returned, it connected to this
884
protocol's transport, and this protocol's transport is connected to it.
885
Otherwise, the user is re-prompted for credentials.
891
def __init__(self, portal):
894
def connectionMade(self):
895
self.transport.write("Username: ")
897
def connectionLost(self, reason):
898
StatefulTelnetProtocol.connectionLost(self, reason)
899
if self.protocol is not None:
901
self.protocol.connectionLost(reason)
904
del self.protocol, self.logout
906
def telnet_User(self, line):
908
self.transport.will(ECHO)
909
self.transport.write("Password: ")
912
def telnet_Password(self, line):
913
username, password = self.username, line
916
creds = credentials.UsernamePassword(username, password)
917
d = self.portal.login(creds, None, ITelnetProtocol)
918
d.addCallback(self._cbLogin)
919
d.addErrback(self._ebLogin)
920
self.transport.wont(ECHO).addCallback(login)
923
def _cbLogin(self, ial):
924
interface, protocol, logout = ial
925
assert interface is ITelnetProtocol
926
self.protocol = protocol
928
self.state = 'Command'
930
protocol.makeConnection(self.transport)
931
self.transport.protocol = protocol
933
def _ebLogin(self, failure):
934
self.transport.write("\nAuthentication failed\n")
935
self.transport.write("Username: ")
940
'TelnetError', 'NegotiationError', 'OptionRefused',
941
'AlreadyNegotiating', 'AlreadyEnabled', 'AlreadyDisabled',
944
'ITelnetProtocol', 'ITelnetTransport',
946
# Other stuff, protocols, etc.
947
'Telnet', 'TelnetProtocol', 'TelnetTransport',
948
'TelnetBootstrapProtocol',