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>