Annotation of embedaddon/curl/tests/negtelnetserver.py, revision 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>