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