2 # -*- coding: utf-8 -*-
4 """ A telnet server which negotiates"""
6 from __future__ import (absolute_import, division, print_function,
14 import SocketServer as socketserver
15 except ImportError: # Python 3
19 log = logging.getLogger(__name__)
24 # The strings that indicate the test framework is checking our aliveness
25 VERIFIED_REQ = b"verifiedserver"
26 VERIFIED_RSP = b"WE ROOLZ: {pid}"
29 def telnetserver(options):
31 Starts up a TCP server with a telnet handler and serves DICT requests
36 with open(options.pidfile, "w") as f:
37 f.write(b"{0}".format(pid))
39 local_bind = (HOST, options.port)
40 log.info("Listening on %s", local_bind)
42 # Need to set the allow_reuse on the class, not on the instance.
43 socketserver.TCPServer.allow_reuse_address = True
44 server = socketserver.TCPServer(local_bind, NegotiatingTelnetHandler)
45 server.serve_forever()
47 return ScriptRC.SUCCESS
50 class NegotiatingTelnetHandler(socketserver.BaseRequestHandler):
51 """Handler class for Telnet connections.
56 Negotiates options before reading data.
58 neg = Negotiator(self.request)
61 # Send some initial negotiations.
62 neg.send_do("NEW_ENVIRON")
63 neg.send_will("NEW_ENVIRON")
67 # Get the data passed through the negotiator
69 log.debug("Incoming data: %r", data)
71 if VERIFIED_REQ in data:
72 log.debug("Received verification request from test framework")
73 response_data = VERIFIED_RSP.format(pid=os.getpid())
75 log.debug("Received normal request - echoing back")
76 response_data = data.strip()
79 log.debug("Sending %r", response_data)
80 self.request.sendall(response_data)
83 log.exception("IOError hit during request")
86 class Negotiator(object):
94 def __init__(self, tcp):
96 self.state = self.NO_NEG
98 def recv(self, bytes):
100 Read bytes from TCP, handling negotiation sequences
102 :param bytes: Number of bytes to read
103 :return: a buffer of bytes
107 # If we keep receiving negotiation sequences, we won't fill the buffer.
108 # Keep looping while we can, and until we have something to give back
110 while len(buffer) == 0:
111 data = self.tcp.recv(bytes)
113 # TCP failed to give us any data. Break out.
117 byte_int = self.byte_to_int(byte)
119 if self.state == self.NO_NEG:
120 self.no_neg(byte, byte_int, buffer)
121 elif self.state == self.START_NEG:
122 self.start_neg(byte_int)
123 elif self.state in [self.WILL, self.WONT, self.DO, self.DONT]:
124 self.handle_option(byte_int)
126 # Received an unexpected byte. Stop negotiations
127 log.error("Unexpected byte %s in state %s",
130 self.state = self.NO_NEG
134 def byte_to_int(self, byte):
135 return struct.unpack(b'B', byte)[0]
137 def no_neg(self, byte, byte_int, buffer):
138 # Not negotiating anything thus far. Check to see if we
140 if byte_int == NegTokens.IAC:
142 log.debug("Starting negotiation (IAC)")
143 self.state = self.START_NEG
145 # Just append the incoming byte to the buffer
148 def start_neg(self, byte_int):
150 log.debug("In negotiation (%s)",
151 NegTokens.from_val(byte_int))
153 if byte_int == NegTokens.WILL:
154 # Client is confirming they are willing to do an option
155 log.debug("Client is willing")
156 self.state = self.WILL
157 elif byte_int == NegTokens.WONT:
158 # Client is confirming they are unwilling to do an
160 log.debug("Client is unwilling")
161 self.state = self.WONT
162 elif byte_int == NegTokens.DO:
163 # Client is indicating they can do an option
164 log.debug("Client can do")
166 elif byte_int == NegTokens.DONT:
167 # Client is indicating they can't do an option
168 log.debug("Client can't do")
169 self.state = self.DONT
171 # Received an unexpected byte. Stop negotiations
172 log.error("Unexpected byte %s in state %s",
175 self.state = self.NO_NEG
177 def handle_option(self, byte_int):
178 if byte_int in [NegOptions.BINARY,
180 NegOptions.SUPPRESS_GO_AHEAD,
182 NegOptions.NEW_ENVIRON]:
183 log.debug("Option: %s", NegOptions.from_val(byte_int))
185 # No further negotiation of this option needed. Reset the state.
186 self.state = self.NO_NEG
189 # Received an unexpected byte. Stop negotiations
190 log.error("Unexpected byte %s in state %s",
193 self.state = self.NO_NEG
195 def send_message(self, message):
196 packed_message = self.pack(message)
197 self.tcp.sendall(packed_message)
200 return struct.pack(b'{0}B'.format(len(arr)), *arr)
202 def send_iac(self, arr):
203 message = [NegTokens.IAC]
205 self.send_message(message)
207 def send_do(self, option_str):
208 log.debug("Sending DO %s", option_str)
209 self.send_iac([NegTokens.DO, NegOptions.to_val(option_str)])
211 def send_dont(self, option_str):
212 log.debug("Sending DONT %s", option_str)
213 self.send_iac([NegTokens.DONT, NegOptions.to_val(option_str)])
215 def send_will(self, option_str):
216 log.debug("Sending WILL %s", option_str)
217 self.send_iac([NegTokens.WILL, NegOptions.to_val(option_str)])
219 def send_wont(self, option_str):
220 log.debug("Sending WONT %s", option_str)
221 self.send_iac([NegTokens.WONT, NegOptions.to_val(option_str)])
224 class NegBase(object):
226 def to_val(cls, name):
227 return getattr(cls, name)
230 def from_val(cls, val):
231 for k in cls.__dict__.keys():
232 if getattr(cls, k) == val:
238 class NegTokens(NegBase):
239 # The start of a negotiation sequence
241 # Confirm willingness to negotiate
243 # Confirm unwillingness to negotiate
245 # Indicate willingness to negotiate
247 # Indicate unwillingness to negotiate
250 # The start of sub-negotiation options.
252 # The end of sub-negotiation options.
256 class NegOptions(NegBase):
257 # Binary Transmission
260 SUPPRESS_GO_AHEAD = 3
261 # NAWS - width and height of client
263 # NEW-ENVIRON - environment variables on client
270 parser = argparse.ArgumentParser()
272 parser.add_argument("--port", action="store", default=9019,
273 type=int, help="port to listen on")
274 parser.add_argument("--verbose", action="store", type=int, default=0,
275 help="verbose output")
276 parser.add_argument("--pidfile", action="store",
277 help="file name for the PID")
278 parser.add_argument("--logfile", action="store",
279 help="file name for the log")
280 parser.add_argument("--srcdir", action="store", help="test directory")
281 parser.add_argument("--id", action="store", help="server ID")
282 parser.add_argument("--ipv4", action="store_true", default=0,
285 return parser.parse_args()
288 def setup_logging(options):
290 Set up logging from the command line options
292 root_logger = logging.getLogger()
295 formatter = logging.Formatter("%(asctime)s %(levelname)-5.5s "
296 "[{ident}] %(message)s"
297 .format(ident=IDENT))
299 # Write out to a logfile
301 handler = logging.FileHandler(options.logfile, mode="w")
302 handler.setFormatter(formatter)
303 handler.setLevel(logging.DEBUG)
304 root_logger.addHandler(handler)
306 # The logfile wasn't specified. Add a stdout logger.
310 # Add a stdout logger as well in verbose mode
311 root_logger.setLevel(logging.DEBUG)
314 root_logger.setLevel(logging.INFO)
317 stdout_handler = logging.StreamHandler(sys.stdout)
318 stdout_handler.setFormatter(formatter)
319 stdout_handler.setLevel(logging.DEBUG)
320 root_logger.addHandler(stdout_handler)
323 class ScriptRC(object):
324 """Enum for script return codes"""
330 class ScriptException(Exception):
334 if __name__ == '__main__':
335 # Get the options from the user.
336 options = get_options()
338 # Setup logging using the user options
339 setup_logging(options)
343 rc = telnetserver(options)
344 except Exception as e:
346 rc = ScriptRC.EXCEPTION
348 log.info("Returning %d", rc)