~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/conch/telnet.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
2
# See LICENSE for details.
 
3
 
 
4
"""Telnet protocol implementation.
 
5
 
 
6
API Stability: Unstable
 
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.application import internet
 
16
from twisted.internet import protocol, interfaces as iinternet, defer
 
17
from twisted.python import log
 
18
 
 
19
MODE = chr(1)
 
20
EDIT = 1
 
21
TRAPSIG = 2
 
22
MODE_ACK = 4
 
23
SOFT_TAB = 8
 
24
LIT_ECHO = 16
 
25
 
 
26
# Characters gleaned from the various (and conflicting) RFCs.  Not all of these are correct.
 
27
 
 
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
 
34
                          # the left margin.
 
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
 
40
                          # stops are located.
 
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
 
49
                          # stops are located.
 
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.
 
55
 
 
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
 
60
                          # it.
 
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
 
65
                          # negotiated about.
 
66
 
 
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
 
72
                          # notification.
 
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
 
82
                          # option.
 
83
WILL =           chr(251) # Indicates the desire to begin
 
84
                          # performing, or confirmation that
 
85
                          # you are now performing, the
 
86
                          # indicated option.
 
87
WONT =           chr(252) # Indicates the refusal to perform,
 
88
                          # or continue performing, the
 
89
                          # indicated option.
 
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
 
94
                          # indicated option.
 
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
 
101
                          # telnet command.
 
102
 
 
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)
 
122
 
 
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)
 
142
 
 
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)
 
148
 
 
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)
 
155
 
 
156
class ITelnetProtocol(iinternet.IProtocol):
 
157
    def unhandledCommand(command, argument):
 
158
        """A command was received but not understood.
 
159
        """
 
160
 
 
161
    def unhandledSubnegotiation(bytes):
 
162
        """A subnegotiation command was received but not understood.
 
163
        """
 
164
 
 
165
    def enableLocal(option):
 
166
        """Enable the given option locally.
 
167
 
 
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
 
171
        will be notified.
 
172
        """
 
173
 
 
174
    def enableRemote(option):
 
175
        """Indicate whether the peer should be allowed to enable this option.
 
176
 
 
177
        Returns True if the peer should be allowed to enable this option,
 
178
        False otherwise.
 
179
        """
 
180
 
 
181
    def disableLocal(option):
 
182
        """Disable the given option locally.
 
183
 
 
184
        Unlike enableLocal, this method cannot fail.  The option must be
 
185
        disabled.
 
186
        """
 
187
 
 
188
    def disableRemote(option):
 
189
        """Indicate that the peer has disabled this option.
 
190
        """
 
191
 
 
192
class ITelnetTransport(iinternet.ITransport):
 
193
    def do(option):
 
194
        """Indicate a desire for the peer to begin performing the given option.
 
195
 
 
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}.
 
201
 
 
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
 
204
        already be enabled.
 
205
        """
 
206
 
 
207
    def dont(option):
 
208
        """Indicate a desire for the peer to cease performing the given option.
 
209
 
 
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}.
 
215
 
 
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
 
218
        already be disabled.
 
219
        """
 
220
 
 
221
    def will(option):
 
222
        """Indicate our willingness to begin performing this option locally.
 
223
 
 
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}.
 
230
 
 
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
 
233
        already be enabled.
 
234
        """
 
235
 
 
236
    def wont(option):
 
237
        """Indicate that we will stop performing the given option.
 
238
 
 
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}.
 
244
 
 
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
 
247
        already be disabled.
 
248
        """
 
249
 
 
250
    def requestNegotiation(about, bytes):
 
251
        """Send a subnegotiation request.
 
252
 
 
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
 
257
        requires it.
 
258
        """
 
259
 
 
260
class TelnetError(Exception):
 
261
    pass
 
262
 
 
263
class NegotiationError(TelnetError):
 
264
    def __str__(self):
 
265
        return self.__class__.__module__ + '.' + self.__class__.__name__ + ':' + repr(self.args[0])
 
266
 
 
267
class OptionRefused(NegotiationError):
 
268
    pass
 
269
 
 
270
class AlreadyEnabled(NegotiationError):
 
271
    pass
 
272
 
 
273
class AlreadyDisabled(NegotiationError):
 
274
    pass
 
275
 
 
276
class AlreadyNegotiating(NegotiationError):
 
277
    pass
 
278
 
 
279
class TelnetProtocol(protocol.Protocol):
 
280
    implements(ITelnetProtocol)
 
281
 
 
282
    def unhandledCommand(self, command, argument):
 
283
        pass
 
284
 
 
285
    def unhandledSubnegotiation(self, command, bytes):
 
286
        pass
 
287
 
 
288
    def enableLocal(self, option):
 
289
        pass
 
290
 
 
291
    def enableRemote(self, option):
 
292
        pass
 
293
 
 
294
    def disableLocal(self, option):
 
295
        pass
 
296
 
 
297
    def disableRemote(self, option):
 
298
        pass
 
299
 
 
300
 
 
301
class Telnet(protocol.Protocol):
 
302
    """
 
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.
 
312
 
 
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
 
319
    handled.
 
320
 
 
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.
 
324
 
 
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.
 
329
 
 
330
    @ivar transport: This protocol's transport object.
 
331
    """
 
332
 
 
333
    # One of a lot of things
 
334
    state = 'data'
 
335
 
 
336
    def __init__(self):
 
337
        self.options = {}
 
338
        self.negotiationMap = {}
 
339
        self.commandMap = {
 
340
            WILL: self.telnet_WILL,
 
341
            WONT: self.telnet_WONT,
 
342
            DO: self.telnet_DO,
 
343
            DONT: self.telnet_DONT}
 
344
 
 
345
    def _write(self, bytes):
 
346
        self.transport.write(bytes)
 
347
 
 
348
    class _OptionState:
 
349
        class _Perspective:
 
350
            state = 'no'
 
351
            negotiating = False
 
352
            onResult = None
 
353
 
 
354
            def __str__(self):
 
355
                return self.state + ('*' * self.negotiating)
 
356
 
 
357
        def __init__(self):
 
358
            self.us = self._Perspective()
 
359
            self.him = self._Perspective()
 
360
 
 
361
        def __repr__(self):
 
362
            return '<_OptionState us=%s him=%s>' % (self.us, self.him)
 
363
 
 
364
    def getOptionState(self, opt):
 
365
        return self.options.setdefault(opt, self._OptionState())
 
366
 
 
367
    def _do(self, option):
 
368
        self._write(IAC + DO + option)
 
369
 
 
370
    def _dont(self, option):
 
371
        self._write(IAC + DONT + option)
 
372
 
 
373
    def _will(self, option):
 
374
        self._write(IAC + WILL + option)
 
375
 
 
376
    def _wont(self, option):
 
377
        self._write(IAC + WONT + option)
 
378
 
 
379
    def will(self, option):
 
380
        """Indicate our willingness to enable an option.
 
381
        """
 
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))
 
387
        else:
 
388
            s.us.negotiating = True
 
389
            s.us.onResult = d = defer.Deferred()
 
390
            self._will(option)
 
391
            return d
 
392
 
 
393
    def wont(self, option):
 
394
        """Indicate we are not willing to enable an option.
 
395
        """
 
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))
 
401
        else:
 
402
            s.us.negotiating = True
 
403
            s.us.onResult = d = defer.Deferred()
 
404
            self._wont(option)
 
405
            return d
 
406
 
 
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))
 
413
        else:
 
414
            s.him.negotiating = True
 
415
            s.him.onResult = d = defer.Deferred()
 
416
            self._do(option)
 
417
            return d
 
418
 
 
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))
 
425
        else:
 
426
            s.him.negotiating = True
 
427
            s.him.onResult = d = defer.Deferred()
 
428
            self._dont(option)
 
429
            return d
 
430
 
 
431
    def requestNegotiation(self, about, bytes):
 
432
        bytes = bytes.replace(IAC, IAC * 2)
 
433
        self._write(IAC + SB + bytes + IAC + SE)
 
434
 
 
435
    def dataReceived(self, data):
 
436
        # Most grossly inefficient implementation ever
 
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
                    self.applicationDataReceived(b)
 
445
            elif self.state == 'escaped':
 
446
                if b == IAC:
 
447
                    self.applicationDataReceived(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
                    self.commandReceived(b, None)
 
455
                elif b in (WILL, WONT, DO, DONT):
 
456
                    self.state = 'command'
 
457
                    self.command = b
 
458
                else:
 
459
                    raise ValueError("Stumped", b)
 
460
            elif self.state == 'command':
 
461
                self.state = 'data'
 
462
                command = self.command
 
463
                del self.command
 
464
                self.commandReceived(command, b)
 
465
            elif self.state == 'newline':
 
466
                if b == '\n':
 
467
                    self.applicationDataReceived('\n')
 
468
                elif b == '\0':
 
469
                    self.applicationDataReceived('\r')
 
470
                else:
 
471
                    self.applicationDataReceived('\r' + b)
 
472
                self.state = 'data'
 
473
            elif self.state == 'subnegotiation':
 
474
                if b == IAC:
 
475
                    self.state = 'subnegotiation-escaped'
 
476
                else:
 
477
                    self.commands.append(b)
 
478
            elif self.state == 'subnegotiation-escaped':
 
479
                if b == SE:
 
480
                    self.state = 'data'
 
481
                    commands = self.commands
 
482
                    del self.commands
 
483
                    self.negotiate(commands)
 
484
                else:
 
485
                    self.state = 'subnegotiation'
 
486
                    self.commands.append(b)
 
487
            else:
 
488
                raise ValueError("How'd you do this?")
 
489
 
 
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
 
495
                d.errback(reason)
 
496
            if state.him.onResult is not None:
 
497
                d = state.him.onResult
 
498
                state.him.onResult = None
 
499
                d.errback(reason)
 
500
 
 
501
    def applicationDataReceived(self, bytes):
 
502
        """Called with application-level data.
 
503
        """
 
504
 
 
505
    def unhandledCommand(self, command, argument):
 
506
        """Called for commands for which no handler is installed.
 
507
        """
 
508
 
 
509
    def commandReceived(self, command, argument):
 
510
        cmdFunc = self.commandMap.get(command)
 
511
        if cmdFunc is None:
 
512
            self.unhandledCommand(command, argument)
 
513
        else:
 
514
            cmdFunc(argument)
 
515
 
 
516
    def unhandledSubnegotiation(self, command, bytes):
 
517
        """Called for subnegotiations for which no handler is installed.
 
518
        """
 
519
 
 
520
    def negotiate(self, bytes):
 
521
        command, bytes = bytes[0], bytes[1:]
 
522
        cmdFunc = self.negotiationMap.get(command)
 
523
        if cmdFunc is None:
 
524
            self.unhandledSubnegotiation(command, bytes)
 
525
        else:
 
526
            cmdFunc(bytes)
 
527
 
 
528
    def telnet_WILL(self, option):
 
529
        s = self.getOptionState(option)
 
530
        self.willMap[s.him.state, s.him.negotiating](self, s, option)
 
531
 
 
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'
 
536
            self._do(option)
 
537
        else:
 
538
            self._dont(option)
 
539
 
 
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
 
546
        d.callback(True)
 
547
        assert self.enableRemote(option), "enableRemote must return True in this context (for option %r)" % (option,)
 
548
 
 
549
    def will_yes_false(self, state, option):
 
550
        # He is unilaterally offering to enable an already-enabled option.
 
551
        # Ignore this.
 
552
        pass
 
553
 
 
554
    def will_yes_true(self, state, option):
 
555
        # This is a bogus state.  It is here for completeness.  It will
 
556
        # never be entered.
 
557
        assert False, "will_yes_true can never be entered, but was called with %r, %r" % (state, option)
 
558
 
 
559
    willMap = {('no', False): will_no_false,   ('no', True): will_no_true,
 
560
               ('yes', False): will_yes_false, ('yes', True): will_yes_true}
 
561
 
 
562
    def telnet_WONT(self, option):
 
563
        s = self.getOptionState(option)
 
564
        self.wontMap[s.him.state, s.him.negotiating](self, s, option)
 
565
 
 
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)
 
570
        pass
 
571
 
 
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))
 
578
 
 
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)
 
583
        self._dont(option)
 
584
 
 
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
 
591
        d.callback(True)
 
592
        self.disableRemote(option)
 
593
 
 
594
    wontMap = {('no', False): wont_no_false,   ('no', True): wont_no_true,
 
595
               ('yes', False): wont_yes_false, ('yes', True): wont_yes_true}
 
596
 
 
597
    def telnet_DO(self, option):
 
598
        s = self.getOptionState(option)
 
599
        self.doMap[s.us.state, s.us.negotiating](self, s, option)
 
600
 
 
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'
 
605
            self._will(option)
 
606
        else:
 
607
            self._wont(option)
 
608
 
 
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
 
615
        d.callback(True)
 
616
        self.enableLocal(option)
 
617
 
 
618
    def do_yes_false(self, state, option):
 
619
        # Peer is unilaterally requesting us to enable an already-enabled option.
 
620
        # Ignore this.
 
621
        pass
 
622
 
 
623
    def do_yes_true(self, state, option):
 
624
        # This is a bogus state.  It is here for completeness.  It will never be
 
625
        # entered.
 
626
        assert False, "do_yes_true can never be entered, but was called with %r, %r" % (state, option)
 
627
 
 
628
    doMap = {('no', False): do_no_false,   ('no', True): do_no_true,
 
629
             ('yes', False): do_yes_false, ('yes', True): do_yes_true}
 
630
 
 
631
    def telnet_DONT(self, option):
 
632
        s = self.getOptionState(option)
 
633
        self.dontMap[s.us.state, s.us.negotiating](self, s, option)
 
634
 
 
635
    def dont_no_false(self, state, option):
 
636
        # Peer is unilaterally demanding us to disable an already-disabled option.
 
637
        # Ignore this.
 
638
        pass
 
639
 
 
640
    def dont_no_true(self, state, option):
 
641
        # This is a bogus state.  It is here for completeness.  It will never be
 
642
        # entered.
 
643
        assert False, "dont_no_true can never be entered, but was called with %r, %r" % (state, option)
 
644
 
 
645
 
 
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)
 
650
        self._wont(option)
 
651
 
 
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
 
658
        d.callback(True)
 
659
        self.disableLocal(option)
 
660
 
 
661
    dontMap = {('no', False): dont_no_false,   ('no', True): dont_no_true,
 
662
               ('yes', False): dont_yes_false, ('yes', True): dont_yes_true}
 
663
 
 
664
    def enableLocal(self, option):
 
665
        return self.protocol.enableLocal(option)
 
666
 
 
667
    def enableRemote(self, option):
 
668
        return self.protocol.enableRemote(option)
 
669
 
 
670
    def disableLocal(self, option):
 
671
        return self.protocol.disableLocal(option)
 
672
 
 
673
    def disableRemote(self, option):
 
674
        return self.protocol.disableRemote(option)
 
675
 
 
676
class ProtocolTransportMixin:
 
677
    def write(self, bytes):
 
678
        self.transport.write(bytes.replace('\n', '\r\n'))
 
679
 
 
680
    def writeSequence(self, seq):
 
681
        self.transport.writeSequence(seq)
 
682
 
 
683
    def loseConnection(self):
 
684
        self.transport.loseConnection()
 
685
 
 
686
    def getHost(self):
 
687
        return self.transport.getHost()
 
688
 
 
689
    def getPeer(self):
 
690
        return self.transport.getPeer()
 
691
 
 
692
class TelnetTransport(Telnet, ProtocolTransportMixin):
 
693
    """
 
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.
 
697
 
 
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
 
701
    **protocolKwArgs.
 
702
 
 
703
    @ivar protocolArgs: A tuple of additional arguments to
 
704
    pass to protocolFactory.
 
705
 
 
706
    @ivar protocolKwArgs: A dictionary of additional arguments
 
707
    to pass to protocolFactory.
 
708
    """
 
709
 
 
710
    disconnecting = False
 
711
 
 
712
    protocolFactory = None
 
713
    protocol = None
 
714
 
 
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
 
721
 
 
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)
 
726
            try:
 
727
                factory = self.factory
 
728
            except AttributeError:
 
729
                pass
 
730
            else:
 
731
                self.protocol.factory = factory
 
732
            self.protocol.makeConnection(self)
 
733
 
 
734
    def connectionLost(self, reason):
 
735
        Telnet.connectionLost(self, reason)
 
736
        if self.protocol is not None:
 
737
            try:
 
738
                self.protocol.connectionLost(reason)
 
739
            finally:
 
740
                del self.protocol
 
741
 
 
742
    def enableLocal(self, option):
 
743
        return self.protocol.enableLocal(option)
 
744
 
 
745
    def enableRemote(self, option):
 
746
        return self.protocol.enableRemote(option)
 
747
 
 
748
    def disableLocal(self, option):
 
749
        return self.protocol.disableLocal(option)
 
750
 
 
751
    def disableRemote(self, option):
 
752
        return self.protocol.disableRemote(option)
 
753
 
 
754
    def unhandledSubnegotiation(self, command, bytes):
 
755
        self.protocol.unhandledSubnegotiation(command, bytes)
 
756
 
 
757
    def unhandledCommand(self, command, argument):
 
758
        self.protocol.unhandledCommand(command, argument)
 
759
 
 
760
    def applicationDataReceived(self, bytes):
 
761
        self.protocol.dataReceived(bytes)
 
762
 
 
763
    def write(self, data):
 
764
        ProtocolTransportMixin.write(self, data.replace('\xff','\xff\xff'))
 
765
 
 
766
 
 
767
class TelnetBootstrapProtocol(TelnetProtocol, ProtocolTransportMixin):
 
768
    implements()
 
769
 
 
770
    protocol = None
 
771
 
 
772
    def __init__(self, protocolFactory, *args, **kw):
 
773
        self.protocolFactory = protocolFactory
 
774
        self.protocolArgs = args
 
775
        self.protocolKwArgs = kw
 
776
 
 
777
    def connectionMade(self):
 
778
        self.transport.negotiationMap[NAWS] = self.telnet_NAWS
 
779
        self.transport.negotiationMap[LINEMODE] = self.telnet_LINEMODE
 
780
 
 
781
        for opt in (LINEMODE, NAWS, SGA):
 
782
            self.transport.do(opt).addErrback(log.err)
 
783
        for opt in (ECHO,):
 
784
            self.transport.will(opt).addErrback(log.err)
 
785
 
 
786
        self.protocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
 
787
 
 
788
        try:
 
789
            factory = self.factory
 
790
        except AttributeError:
 
791
            pass
 
792
        else:
 
793
            self.protocol.factory = factory
 
794
 
 
795
        self.protocol.makeConnection(self)
 
796
 
 
797
    def connectionLost(self, reason):
 
798
        if self.protocol is not None:
 
799
            try:
 
800
                self.protocol.connectionLost(reason)
 
801
            finally:
 
802
                del self.protocol
 
803
 
 
804
    def dataReceived(self, data):
 
805
        self.protocol.dataReceived(data)
 
806
 
 
807
    def enableLocal(self, opt):
 
808
        if opt == ECHO:
 
809
            return True
 
810
        elif opt == SGA:
 
811
            return True
 
812
        else:
 
813
            return False
 
814
 
 
815
    def enableRemote(self, opt):
 
816
        if opt == LINEMODE:
 
817
            self.transport.requestNegotiation(LINEMODE, MODE + chr(TRAPSIG))
 
818
            return True
 
819
        elif opt == NAWS:
 
820
            return True
 
821
        elif opt == SGA:
 
822
            return True
 
823
        else:
 
824
            return False
 
825
 
 
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.
 
831
        if len(bytes) == 4:
 
832
            width, height = struct.unpack('!HH', ''.join(bytes))
 
833
            self.protocol.terminalProtocol.terminalSize(width, height)
 
834
        else:
 
835
            log.msg("Wrong number of NAWS bytes")
 
836
 
 
837
 
 
838
    linemodeSubcommands = {
 
839
        LINEMODE_SLC: 'SLC'}
 
840
    def telnet_LINEMODE(self, bytes):
 
841
        revmap = {}
 
842
        linemodeSubcommand = bytes[0]
 
843
        if 0:
 
844
            # XXX TODO: This should be enabled to parse linemode subnegotiation.
 
845
            getattr(self, 'linemode_' + self.linemodeSubcommands[linemodeSubcommand])(bytes[1:])
 
846
 
 
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)
 
852
 
 
853
from twisted.protocols import basic
 
854
 
 
855
class StatefulTelnetProtocol(basic.LineReceiver, TelnetProtocol):
 
856
    delimiter = '\n'
 
857
 
 
858
    state = 'Discard'
 
859
 
 
860
    def connectionLost(self, reason):
 
861
        basic.LineReceiver.connectionLost(self, reason)
 
862
        TelnetProtocol.connectionLost(self, reason)
 
863
 
 
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
 
870
            else:
 
871
                log.msg("Warning: state changed and new state returned")
 
872
 
 
873
    def telnet_Discard(self, line):
 
874
        pass
 
875
 
 
876
from twisted.cred import credentials
 
877
 
 
878
class AuthenticatingTelnetProtocol(StatefulTelnetProtocol):
 
879
    """A protocol which prompts for credentials and attempts to authenticate them.
 
880
 
 
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.
 
886
    """
 
887
 
 
888
    state = "User"
 
889
    protocol = None
 
890
 
 
891
    def __init__(self, portal):
 
892
        self.portal = portal
 
893
 
 
894
    def connectionMade(self):
 
895
        self.transport.write("Username: ")
 
896
 
 
897
    def connectionLost(self, reason):
 
898
        StatefulTelnetProtocol.connectionLost(self, reason)
 
899
        if self.protocol is not None:
 
900
            try:
 
901
                self.protocol.connectionLost(reason)
 
902
                self.logout()
 
903
            finally:
 
904
                del self.protocol, self.logout
 
905
 
 
906
    def telnet_User(self, line):
 
907
        self.username = line
 
908
        self.transport.will(ECHO)
 
909
        self.transport.write("Password: ")
 
910
        return 'Password'
 
911
 
 
912
    def telnet_Password(self, line):
 
913
        username, password = self.username, line
 
914
        del self.username
 
915
        def login(ignored):
 
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)
 
921
        return 'Discard'
 
922
 
 
923
    def _cbLogin(self, ial):
 
924
        interface, protocol, logout = ial
 
925
        assert interface is ITelnetProtocol
 
926
        self.protocol = protocol
 
927
        self.logout = logout
 
928
        self.state = 'Command'
 
929
 
 
930
        protocol.makeConnection(self.transport)
 
931
        self.transport.protocol = protocol
 
932
 
 
933
    def _ebLogin(self, failure):
 
934
        self.transport.write("\nAuthentication failed\n")
 
935
        self.transport.write("Username: ")
 
936
        self.state = "User"
 
937
 
 
938
__all__ = [
 
939
    # Exceptions
 
940
    'TelnetError', 'NegotiationError', 'OptionRefused',
 
941
    'AlreadyNegotiating', 'AlreadyEnabled', 'AlreadyDisabled',
 
942
 
 
943
    # Interfaces
 
944
    'ITelnetProtocol', 'ITelnetTransport',
 
945
 
 
946
    # Other stuff, protocols, etc.
 
947
    'Telnet', 'TelnetProtocol', 'TelnetTransport',
 
948
    'TelnetBootstrapProtocol',
 
949
 
 
950
    ]