Annotation of embedaddon/curl/tests/negtelnetserver.py, revision 1.1.1.1
1.1 misho 1: #!/usr/bin/env python
2: # -*- coding: utf-8 -*-
3: #
4: # Project ___| | | | _ \| |
5: # / __| | | | |_) | |
6: # | (__| |_| | _ <| |___
7: # \___|\___/|_| \_\_____|
8: #
9: # Copyright (C) 2017 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
10: #
11: # This software is licensed as described in the file COPYING, which
12: # you should have received as part of this distribution. The terms
13: # are also available at https://curl.haxx.se/docs/copyright.html.
14: #
15: # You may opt to use, copy, modify, merge, publish, distribute and/or sell
16: # copies of the Software, and permit persons to whom the Software is
17: # furnished to do so, under the terms of the COPYING file.
18: #
19: # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20: # KIND, either express or implied.
21: #
22: """ A telnet server which negotiates"""
23:
24: from __future__ import (absolute_import, division, print_function,
25: unicode_literals)
26: import argparse
27: import os
28: import sys
29: import logging
30: if sys.version_info.major >= 3:
31: import socketserver
32: else:
33: import SocketServer as socketserver
34:
35: log = logging.getLogger(__name__)
36: HOST = "localhost"
37: IDENT = "NTEL"
38:
39:
40: # The strings that indicate the test framework is checking our aliveness
41: VERIFIED_REQ = "verifiedserver"
42: VERIFIED_RSP = "WE ROOLZ: {pid}"
43:
44:
45: def telnetserver(options):
46: """
47: Starts up a TCP server with a telnet handler and serves DICT requests
48: forever.
49: """
50: if options.pidfile:
51: pid = os.getpid()
52: # see tests/server/util.c function write_pidfile
53: if os.name == "nt":
54: pid += 65536
55: with open(options.pidfile, "w") as f:
56: f.write(str(pid))
57:
58: local_bind = (HOST, options.port)
59: log.info("Listening on %s", local_bind)
60:
61: # Need to set the allow_reuse on the class, not on the instance.
62: socketserver.TCPServer.allow_reuse_address = True
63: server = socketserver.TCPServer(local_bind, NegotiatingTelnetHandler)
64: server.serve_forever()
65:
66: return ScriptRC.SUCCESS
67:
68:
69: class NegotiatingTelnetHandler(socketserver.BaseRequestHandler):
70: """Handler class for Telnet connections.
71:
72: """
73: def handle(self):
74: """
75: Negotiates options before reading data.
76: """
77: neg = Negotiator(self.request)
78:
79: try:
80: # Send some initial negotiations.
81: neg.send_do("NEW_ENVIRON")
82: neg.send_will("NEW_ENVIRON")
83: neg.send_dont("NAWS")
84: neg.send_wont("NAWS")
85:
86: # Get the data passed through the negotiator
87: data = neg.recv(1024)
88: log.debug("Incoming data: %r", data)
89:
90: if VERIFIED_REQ.encode('utf-8') in data:
91: log.debug("Received verification request from test framework")
92: pid = os.getpid()
93: # see tests/server/util.c function write_pidfile
94: if os.name == "nt":
95: pid += 65536
96: response = VERIFIED_RSP.format(pid=pid)
97: response_data = response.encode('utf-8')
98: else:
99: log.debug("Received normal request - echoing back")
100: response_data = data.decode('utf-8').strip().encode('utf-8')
101:
102: if response_data:
103: log.debug("Sending %r", response_data)
104: self.request.sendall(response_data)
105:
106: except IOError:
107: log.exception("IOError hit during request")
108:
109:
110: class Negotiator(object):
111: NO_NEG = 0
112: START_NEG = 1
113: WILL = 2
114: WONT = 3
115: DO = 4
116: DONT = 5
117:
118: def __init__(self, tcp):
119: self.tcp = tcp
120: self.state = self.NO_NEG
121:
122: def recv(self, bytes):
123: """
124: Read bytes from TCP, handling negotiation sequences
125:
126: :param bytes: Number of bytes to read
127: :return: a buffer of bytes
128: """
129: buffer = bytearray()
130:
131: # If we keep receiving negotiation sequences, we won't fill the buffer.
132: # Keep looping while we can, and until we have something to give back
133: # to the caller.
134: while len(buffer) == 0:
135: data = self.tcp.recv(bytes)
136: if not data:
137: # TCP failed to give us any data. Break out.
138: break
139:
140: for byte_int in bytearray(data):
141: if self.state == self.NO_NEG:
142: self.no_neg(byte_int, buffer)
143: elif self.state == self.START_NEG:
144: self.start_neg(byte_int)
145: elif self.state in [self.WILL, self.WONT, self.DO, self.DONT]:
146: self.handle_option(byte_int)
147: else:
148: # Received an unexpected byte. Stop negotiations
149: log.error("Unexpected byte %s in state %s",
150: byte_int,
151: self.state)
152: self.state = self.NO_NEG
153:
154: return buffer
155:
156: def no_neg(self, byte_int, buffer):
157: # Not negotiating anything thus far. Check to see if we
158: # should.
159: if byte_int == NegTokens.IAC:
160: # Start negotiation
161: log.debug("Starting negotiation (IAC)")
162: self.state = self.START_NEG
163: else:
164: # Just append the incoming byte to the buffer
165: buffer.append(byte_int)
166:
167: def start_neg(self, byte_int):
168: # In a negotiation.
169: log.debug("In negotiation (%s)",
170: NegTokens.from_val(byte_int))
171:
172: if byte_int == NegTokens.WILL:
173: # Client is confirming they are willing to do an option
174: log.debug("Client is willing")
175: self.state = self.WILL
176: elif byte_int == NegTokens.WONT:
177: # Client is confirming they are unwilling to do an
178: # option
179: log.debug("Client is unwilling")
180: self.state = self.WONT
181: elif byte_int == NegTokens.DO:
182: # Client is indicating they can do an option
183: log.debug("Client can do")
184: self.state = self.DO
185: elif byte_int == NegTokens.DONT:
186: # Client is indicating they can't do an option
187: log.debug("Client can't do")
188: self.state = self.DONT
189: else:
190: # Received an unexpected byte. Stop negotiations
191: log.error("Unexpected byte %s in state %s",
192: byte_int,
193: self.state)
194: self.state = self.NO_NEG
195:
196: def handle_option(self, byte_int):
197: if byte_int in [NegOptions.BINARY,
198: NegOptions.CHARSET,
199: NegOptions.SUPPRESS_GO_AHEAD,
200: NegOptions.NAWS,
201: NegOptions.NEW_ENVIRON]:
202: log.debug("Option: %s", NegOptions.from_val(byte_int))
203:
204: # No further negotiation of this option needed. Reset the state.
205: self.state = self.NO_NEG
206:
207: else:
208: # Received an unexpected byte. Stop negotiations
209: log.error("Unexpected byte %s in state %s",
210: byte_int,
211: self.state)
212: self.state = self.NO_NEG
213:
214: def send_message(self, message_ints):
215: self.tcp.sendall(bytearray(message_ints))
216:
217: def send_iac(self, arr):
218: message = [NegTokens.IAC]
219: message.extend(arr)
220: self.send_message(message)
221:
222: def send_do(self, option_str):
223: log.debug("Sending DO %s", option_str)
224: self.send_iac([NegTokens.DO, NegOptions.to_val(option_str)])
225:
226: def send_dont(self, option_str):
227: log.debug("Sending DONT %s", option_str)
228: self.send_iac([NegTokens.DONT, NegOptions.to_val(option_str)])
229:
230: def send_will(self, option_str):
231: log.debug("Sending WILL %s", option_str)
232: self.send_iac([NegTokens.WILL, NegOptions.to_val(option_str)])
233:
234: def send_wont(self, option_str):
235: log.debug("Sending WONT %s", option_str)
236: self.send_iac([NegTokens.WONT, NegOptions.to_val(option_str)])
237:
238:
239: class NegBase(object):
240: @classmethod
241: def to_val(cls, name):
242: return getattr(cls, name)
243:
244: @classmethod
245: def from_val(cls, val):
246: for k in cls.__dict__.keys():
247: if getattr(cls, k) == val:
248: return k
249:
250: return "<unknown>"
251:
252:
253: class NegTokens(NegBase):
254: # The start of a negotiation sequence
255: IAC = 255
256: # Confirm willingness to negotiate
257: WILL = 251
258: # Confirm unwillingness to negotiate
259: WONT = 252
260: # Indicate willingness to negotiate
261: DO = 253
262: # Indicate unwillingness to negotiate
263: DONT = 254
264:
265: # The start of sub-negotiation options.
266: SB = 250
267: # The end of sub-negotiation options.
268: SE = 240
269:
270:
271: class NegOptions(NegBase):
272: # Binary Transmission
273: BINARY = 0
274: # Suppress Go Ahead
275: SUPPRESS_GO_AHEAD = 3
276: # NAWS - width and height of client
277: NAWS = 31
278: # NEW-ENVIRON - environment variables on client
279: NEW_ENVIRON = 39
280: # Charset option
281: CHARSET = 42
282:
283:
284: def get_options():
285: parser = argparse.ArgumentParser()
286:
287: parser.add_argument("--port", action="store", default=9019,
288: type=int, help="port to listen on")
289: parser.add_argument("--verbose", action="store", type=int, default=0,
290: help="verbose output")
291: parser.add_argument("--pidfile", action="store",
292: help="file name for the PID")
293: parser.add_argument("--logfile", action="store",
294: help="file name for the log")
295: parser.add_argument("--srcdir", action="store", help="test directory")
296: parser.add_argument("--id", action="store", help="server ID")
297: parser.add_argument("--ipv4", action="store_true", default=0,
298: help="IPv4 flag")
299:
300: return parser.parse_args()
301:
302:
303: def setup_logging(options):
304: """
305: Set up logging from the command line options
306: """
307: root_logger = logging.getLogger()
308: add_stdout = False
309:
310: formatter = logging.Formatter("%(asctime)s %(levelname)-5.5s "
311: "[{ident}] %(message)s"
312: .format(ident=IDENT))
313:
314: # Write out to a logfile
315: if options.logfile:
316: handler = logging.FileHandler(options.logfile, mode="w")
317: handler.setFormatter(formatter)
318: handler.setLevel(logging.DEBUG)
319: root_logger.addHandler(handler)
320: else:
321: # The logfile wasn't specified. Add a stdout logger.
322: add_stdout = True
323:
324: if options.verbose:
325: # Add a stdout logger as well in verbose mode
326: root_logger.setLevel(logging.DEBUG)
327: add_stdout = True
328: else:
329: root_logger.setLevel(logging.INFO)
330:
331: if add_stdout:
332: stdout_handler = logging.StreamHandler(sys.stdout)
333: stdout_handler.setFormatter(formatter)
334: stdout_handler.setLevel(logging.DEBUG)
335: root_logger.addHandler(stdout_handler)
336:
337:
338: class ScriptRC(object):
339: """Enum for script return codes"""
340: SUCCESS = 0
341: FAILURE = 1
342: EXCEPTION = 2
343:
344:
345: class ScriptException(Exception):
346: pass
347:
348:
349: if __name__ == '__main__':
350: # Get the options from the user.
351: options = get_options()
352:
353: # Setup logging using the user options
354: setup_logging(options)
355:
356: # Run main script.
357: try:
358: rc = telnetserver(options)
359: except Exception as e:
360: log.exception(e)
361: rc = ScriptRC.EXCEPTION
362:
363: log.info("Returning %d", rc)
364: sys.exit(rc)
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>