vmware-vspc/vspc/async_telnet.py
Darius Davis 206e36008f vSPC: Escape 'sequence' and 'secret' in replies.
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
2023-06-22 09:46:05 +03:00

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