206e36008f
The values of 'sequence' and 'secret' are arbitrary byte strings which might contain IAC (0xff) or SE (0xf0) in any position. Lack of escaping during transmission might cause receiver confusion and potentially vMotion failures. This change adds escaping of these 'sequence' and 'secret' values before transmission. They are already being unescaped correctly during receipt; Only the transmit side needs to be fixed. Change-Id: I82384424d684b931805ca921d0597ad7642f66ac
152 lines
5.0 KiB
Python
152 lines
5.0 KiB
Python
# Copyright (c) 2001-2016 Python Software Foundation
|
|
# Copyright (c) 2017 VMware Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
"""
|
|
Telnet client class using asyncio.
|
|
Based on the standard telnetlib module from Python3.
|
|
"""
|
|
import asyncio
|
|
|
|
# Telnet protocol characters (don't change)
|
|
IAC = bytes([255]) # "Interpret As Command"
|
|
DONT = bytes([254])
|
|
DO = bytes([253])
|
|
WONT = bytes([252])
|
|
WILL = bytes([251])
|
|
theNULL = bytes([0])
|
|
SE = bytes([240]) # Subnegotiation End
|
|
NOP = bytes([241]) # No Operation
|
|
SB = bytes([250]) # Subnegotiation Begin
|
|
NOOPT = bytes([0])
|
|
|
|
|
|
class AsyncTelnet:
|
|
def __init__(self, reader, opt_handler):
|
|
self._reader = reader
|
|
self._opt_handler = opt_handler
|
|
self.rawq = b''
|
|
self.irawq = 0
|
|
self.cookedq = b''
|
|
self.eof = 0
|
|
self.iacseq = b'' # Buffer for IAC sequence.
|
|
self.sb = 0 # flag for SB and SE sequence.
|
|
self.sbdataq = b''
|
|
|
|
@staticmethod
|
|
def escape(data):
|
|
"""Escape any Telnet IACs with another IAC.
|
|
"""
|
|
return data.replace(IAC, IAC + IAC)
|
|
|
|
@asyncio.coroutine
|
|
def process_rawq(self):
|
|
"""Transfer from raw queue to cooked queue.
|
|
|
|
Set self.eof when connection is closed.
|
|
"""
|
|
buf = [b'', b'']
|
|
try:
|
|
while self.rawq:
|
|
c = yield from self.rawq_getchar()
|
|
if not self.iacseq:
|
|
if self.sb == 0 and c == theNULL:
|
|
continue
|
|
if self.sb == 0 and c == b"\021":
|
|
continue
|
|
if c != IAC:
|
|
buf[self.sb] = buf[self.sb] + c
|
|
continue
|
|
else:
|
|
self.iacseq += c
|
|
elif len(self.iacseq) == 1:
|
|
# 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]'
|
|
if c in (DO, DONT, WILL, WONT):
|
|
self.iacseq += c
|
|
continue
|
|
|
|
self.iacseq = b''
|
|
if c == IAC:
|
|
buf[self.sb] = buf[self.sb] + c
|
|
else:
|
|
if c == SB: # SB ... SE start.
|
|
self.sb = 1
|
|
self.sbdataq = b''
|
|
elif c == SE:
|
|
self.sb = 0
|
|
self.sbdataq = self.sbdataq + buf[1]
|
|
buf[1] = b''
|
|
yield from self._opt_handler(c, NOOPT,
|
|
data=self.sbdataq)
|
|
elif len(self.iacseq) == 2:
|
|
cmd = self.iacseq[1:2]
|
|
self.iacseq = b''
|
|
opt = c
|
|
if cmd in (DO, DONT):
|
|
yield from self._opt_handler(cmd, opt)
|
|
elif cmd in (WILL, WONT):
|
|
yield from self._opt_handler(cmd, opt)
|
|
except EOFError: # raised by self.rawq_getchar()
|
|
self.iacseq = b'' # Reset on EOF
|
|
self.sb = 0
|
|
pass
|
|
self.cookedq = self.cookedq + buf[0]
|
|
self.sbdataq = self.sbdataq + buf[1]
|
|
|
|
@asyncio.coroutine
|
|
def rawq_getchar(self):
|
|
"""Get next char from raw queue.
|
|
|
|
Raise EOFError when connection is closed.
|
|
"""
|
|
if not self.rawq:
|
|
yield from self.fill_rawq()
|
|
if self.eof:
|
|
raise EOFError
|
|
c = self.rawq[self.irawq:self.irawq + 1]
|
|
self.irawq = self.irawq + 1
|
|
if self.irawq >= len(self.rawq):
|
|
self.rawq = b''
|
|
self.irawq = 0
|
|
return c
|
|
|
|
@asyncio.coroutine
|
|
def fill_rawq(self):
|
|
"""Fill raw queue from exactly one recv() system call.
|
|
|
|
Set self.eof when connection is closed.
|
|
"""
|
|
if self.irawq >= len(self.rawq):
|
|
self.rawq = b''
|
|
self.irawq = 0
|
|
# The buffer size should be fairly small so as to avoid quadratic
|
|
# behavior in process_rawq() above
|
|
buf = yield from self._reader.read(50)
|
|
self.eof = (not buf)
|
|
self.rawq = self.rawq + buf
|
|
|
|
@asyncio.coroutine
|
|
def read_some(self):
|
|
"""Read at least one byte of cooked data unless EOF is hit.
|
|
|
|
Return b'' if EOF is hit.
|
|
"""
|
|
yield from self.process_rawq()
|
|
while not self.cookedq and not self.eof:
|
|
yield from self.fill_rawq()
|
|
yield from self.process_rawq()
|
|
buf = self.cookedq
|
|
self.cookedq = b''
|
|
return buf
|