Commit 1da9c57c authored by Martin v. Löwis's avatar Martin v. Löwis

Patch #630829: Don't block on IAC, process suboptions.

parent a178cff9
...@@ -96,6 +96,14 @@ Return \code{''} if no cooked data available otherwise. This method ...@@ -96,6 +96,14 @@ Return \code{''} if no cooked data available otherwise. This method
never blocks. never blocks.
\end{methoddesc} \end{methoddesc}
\begin{methoddesc}{read_sb_data}{}
Return the data collected between a SB/SE pair (suboption begin/end).
The callback should access these data when it was invoked with a
\code{SE} command. This method never blocks.
\versionadded{2.3}
\end{methoddesc}
\begin{methoddesc}{open}{host\optional{, port}} \begin{methoddesc}{open}{host\optional{, port}}
Connect to a host. Connect to a host.
The optional second argument is the port number, which The optional second argument is the port number, which
......
...@@ -25,9 +25,6 @@ EOFError is needed in some cases to distinguish between "no data" and ...@@ -25,9 +25,6 @@ EOFError is needed in some cases to distinguish between "no data" and
"connection closed" (since the socket also appears ready for reading "connection closed" (since the socket also appears ready for reading
when it is closed). when it is closed).
Bugs:
- may hang when connection is slow in the middle of an IAC sequence
To do: To do:
- option negotiation - option negotiation
- timeout should be intrinsic to the connection object instead of an - timeout should be intrinsic to the connection object instead of an
...@@ -56,6 +53,8 @@ DO = chr(253) ...@@ -56,6 +53,8 @@ DO = chr(253)
WONT = chr(252) WONT = chr(252)
WILL = chr(251) WILL = chr(251)
theNULL = chr(0) theNULL = chr(0)
SB = chr(250)
SE = chr(240)
# Telnet protocol options code (don't change) # Telnet protocol options code (don't change)
# These ones all come from arpa/telnet.h # These ones all come from arpa/telnet.h
...@@ -117,6 +116,7 @@ PRAGMA_LOGON = chr(138) # TELOPT PRAGMA LOGON ...@@ -117,6 +116,7 @@ PRAGMA_LOGON = chr(138) # TELOPT PRAGMA LOGON
SSPI_LOGON = chr(139) # TELOPT SSPI LOGON SSPI_LOGON = chr(139) # TELOPT SSPI LOGON
PRAGMA_HEARTBEAT = chr(140) # TELOPT PRAGMA HEARTBEAT PRAGMA_HEARTBEAT = chr(140) # TELOPT PRAGMA HEARTBEAT
EXOPL = chr(255) # Extended-Options-List EXOPL = chr(255) # Extended-Options-List
NOOPT = chr(0)
class Telnet: class Telnet:
...@@ -161,10 +161,14 @@ class Telnet: ...@@ -161,10 +161,14 @@ class Telnet:
Reads all data in the cooked queue, without doing any socket Reads all data in the cooked queue, without doing any socket
I/O. I/O.
read_sb_data()
Reads available data between SB ... SE sequence. Don't block.
set_option_negotiation_callback(callback) set_option_negotiation_callback(callback)
Each time a telnet option is read on the input flow, this callback Each time a telnet option is read on the input flow, this callback
(if set) is called with the following parameters : (if set) is called with the following parameters :
callback(telnet socket, command (DO/DONT/WILL/WONT), option) callback(telnet socket, command, option)
option will be chr(0) when there is no option.
No other action is done afterwards by telnetlib. No other action is done afterwards by telnetlib.
""" """
...@@ -185,6 +189,9 @@ class Telnet: ...@@ -185,6 +189,9 @@ class Telnet:
self.irawq = 0 self.irawq = 0
self.cookedq = '' self.cookedq = ''
self.eof = 0 self.eof = 0
self.iacseq = '' # Buffer for IAC sequence.
self.sb = 0 # flag for SB and SE sequence.
self.sbdataq = ''
self.option_callback = None self.option_callback = None
if host is not None: if host is not None:
self.open(host, port) self.open(host, port)
...@@ -250,6 +257,8 @@ class Telnet: ...@@ -250,6 +257,8 @@ class Telnet:
self.sock.close() self.sock.close()
self.sock = 0 self.sock = 0
self.eof = 1 self.eof = 1
self.iacseq = ''
self.sb = 0
def get_socket(self): def get_socket(self):
"""Return the socket object used internally.""" """Return the socket object used internally."""
...@@ -379,6 +388,18 @@ class Telnet: ...@@ -379,6 +388,18 @@ class Telnet:
if not buf and self.eof and not self.rawq: if not buf and self.eof and not self.rawq:
raise EOFError, 'telnet connection closed' raise EOFError, 'telnet connection closed'
return buf return buf
def read_sb_data(self):
"""Return any data available in the SB ... SE queue.
Return '' if no SB ... SE available. Should only be called
after seeing a SB or SE command. When a new SB command is
found, old unread SB data will be discarded. Don't block.
"""
buf = self.sbdataq
self.sbdataq = ''
return buf
def set_option_negotiation_callback(self, callback): def set_option_negotiation_callback(self, callback):
"""Provide a callback function called after each receipt of a telnet option.""" """Provide a callback function called after each receipt of a telnet option."""
...@@ -391,40 +412,70 @@ class Telnet: ...@@ -391,40 +412,70 @@ class Telnet:
the midst of an IAC sequence. the midst of an IAC sequence.
""" """
buf = '' buf = ['', '']
try: try:
while self.rawq: while self.rawq:
c = self.rawq_getchar() c = self.rawq_getchar()
if c == theNULL: if not self.iacseq:
continue if c == theNULL:
if c == "\021": continue
continue if c == "\021":
if c != IAC: continue
buf = buf + c if c != IAC:
continue buf[self.sb] = buf[self.sb] + c
c = self.rawq_getchar() continue
if c == IAC:
buf = buf + c
elif c in (DO, DONT):
opt = self.rawq_getchar()
self.msg('IAC %s %d', c == DO and 'DO' or 'DONT', ord(opt))
if self.option_callback:
self.option_callback(self.sock, c, opt)
else: else:
self.sock.sendall(IAC + WONT + opt) self.iacseq += c
elif c in (WILL, WONT): elif len(self.iacseq) == 1:
opt = self.rawq_getchar() 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]'
self.msg('IAC %s %d', if c in (DO, DONT, WILL, WONT):
c == WILL and 'WILL' or 'WONT', ord(opt)) self.iacseq += c
if self.option_callback: continue
self.option_callback(self.sock, c, opt)
self.iacseq = ''
if c == IAC:
buf[self.sb] = buf[self.sb] + c
else: else:
self.sock.sendall(IAC + DONT + opt) if c == SB: # SB ... SE start.
else: self.sb = 1
self.msg('IAC %d not recognized' % ord(c)) self.sbdataq = ''
elif c == SE:
self.sb = 0
self.sbdataq = self.sbdataq + buf[1]
buf[1] = ''
if self.option_callback:
# Callback is supposed to look into
# the sbdataq
self.option_callback(self.sock, c, NOOPT)
else:
# We can't offer automatic processing of
# suboptions. Alas, we should not get any
# unless we did a WILL/DO before.
self.msg('IAC %d not recognized' % ord(c))
elif len(self.iacseq) == 2:
cmd = self.iacseq[1]
self.iacseq = ''
opt = c
if cmd in (DO, DONT):
self.msg('IAC %s %d',
cmd == DO and 'DO' or 'DONT', ord(opt))
if self.option_callback:
self.option_callback(self.sock, cmd, opt)
else:
self.sock.sendall(IAC + WONT + opt)
elif cmd in (WILL, WONT):
self.msg('IAC %s %d',
cmd == WILL and 'WILL' or 'WONT', ord(opt))
if self.option_callback:
self.option_callback(self.sock, cmd, opt)
else:
self.sock.sendall(IAC + DONT + opt)
except EOFError: # raised by self.rawq_getchar() except EOFError: # raised by self.rawq_getchar()
self.iacseq = '' # Reset on EOF
self.sb = 0
pass pass
self.cookedq = self.cookedq + buf self.cookedq = self.cookedq + buf[0]
self.sbdataq = self.sbdataq + buf[1]
def rawq_getchar(self): def rawq_getchar(self):
"""Get next char from raw queue. """Get next char from raw queue.
......
...@@ -465,6 +465,7 @@ Steven Scott ...@@ -465,6 +465,7 @@ Steven Scott
Nick Seidenman Nick Seidenman
Fred Sells Fred Sells
Denis Severson Denis Severson
Ha Shao
Bruce Sherwood Bruce Sherwood
Pete Shinners Pete Shinners
Michael Shiplett Michael Shiplett
......
...@@ -1707,7 +1707,8 @@ Library ...@@ -1707,7 +1707,8 @@ Library
------- -------
- telnetlib includes symbolic names for the options, and support for - telnetlib includes symbolic names for the options, and support for
setting an option negotiation callback. setting an option negotiation callback. It also supports processing
of suboptions.
- The new C standard no longer requires that math libraries set errno to - The new C standard no longer requires that math libraries set errno to
ERANGE on overflow. For platform libraries that exploit this new ERANGE on overflow. For platform libraries that exploit this new
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment