def check_auth_gssapi_keyex(self, username, gss_authenticated=paramiko.AUTH_FAILED, cc_file=None): Logger.debug(9, f'SSH Server: check_auth_gssapi_keyex') # No real authenticatio, all users welcome return paramiko.AUTH_SUCCESSFUL
def from_trace_file(source_filename): parrot_filename = None if source_filename.endswith(".trace"): parrot_filename = NSO_Trace_Parrot_Builder(source_filename).build() if parrot_filename: return parrot_filename Logger.fatal( f"Parrot Builder, unrecognized trace file type: {source_filename}")
def match(self, xml_match_str): def match_nodes(matchpoint, payloadpoint): def matching_tags(matchtag, payloadtag): mpos = matchtag.find("}") if mpos != -1: # If matchtag has namespace, namespaces and tags have to match Logger.debug( 8, f'Match NS matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag ppos = payloadtag.find("}") if ppos != -1: # If matchtag has no namespace, only tags have to match Logger.debug( 8, f'Match NO matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag[ppos + 1:] # If neither has namespace, tags have to match Logger.debug( 8, f'Match NE matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag Logger.debug( 8, f'Match 1 match_nodes_t({matchpoint}, {payloadpoint})') if matching_tags(matchpoint.tag, payloadpoint.tag): for mch in matchpoint: # must match all Logger.debug(8, f'Match 3 Trying match {mch}') one_match = False for pch in payloadpoint: # needs to match one Logger.debug(8, f'Match 4 Trying payload {pch}') if match_nodes(mch, pch): Logger.debug( 8, f'Match M match_nodes_t({mch}, {pch}) = True') one_match = True break if not one_match: Logger.debug( 8, f'Match O match_nodes_t({matchpoint}, {payloadpoint}) = False' ) return False Logger.debug( 8, f'Match N match_nodes_t({matchpoint}, {payloadpoint}) = True' ) return True Logger.debug( 8, f'Match E match_nodes_t({matchpoint}, {payloadpoint}) = False') return False match_root = ET.fromstring( f'''<parrot-root>{xml_match_str}</parrot-root>''') if match_nodes(match_root, self.get_xml_etree()): Logger.debug(5, f"Incoming message template match '{xml_match_str}'") return True return False
def __init__(self, source_filename, target_filename=None): self.source_filename = pathlib.Path(source_filename) if not target_filename: target_filename = source_filename + ".parrot.xml" self.target_filename = pathlib.Path(target_filename) if not self.source_filename.exists(): Logger.fatal( f"Parrot Builder, can't find trace file: {source_filename}") self.catalog = {} self.catalog_meta = {}
def __init__(self, channel, name, server, *largs, **kwargs): Logger.debug( 9, f'NETCONFsubsys: init channel={channel} name={name} server={server}' ) SubsystemHandler.__init__(self, channel, name, server) transport = channel.get_transport() self.ultra_debug = transport.get_hexdump() self.next_handle = 1 Logger.debug(9, f'NETCONFsubsys: init done')
def reply(self, incoming_message): try: response_msg = self.template.render( request=incoming_message, message_id=incoming_message.get_message_id(), session_id="4711", #FIXME str(self.session_id), ) return response_msg.strip() except Exception as ex: Logger.fatal( f"Template rendering error, {self.template}, {incoming_message.get_xml_text()}: {ex}" )
def run(self, host, port, parrot_file): Logger.info(f"Parroting {parrot_file}") try: parrot_path = pathlib.Path(parrot_file) env = Environment(loader=FileSystemLoader([parrot_path.parent], followlinks=True), autoescape=select_autoescape(['xml'])) self.server.set_template(env.get_template(str(parrot_path.name))) self.server.set_host_port(host, port) self.server.serve() except Exception as e: traceback.print_exc() Logger.fatal(f"Top level exception: {str(e)}")
def build(self): if self.target_filename.exists(): source_stat = self.source_filename.stat() target_stat = self.target_filename.stat() if source_stat.st_mtime <= target_stat.st_mtime: Logger.info( f"Parrot Builder, {self.target_filename} up to date") return str(self.target_filename) with open(self.source_filename, "r") as source_file: with open(self.target_filename, "w") as target_file: self._make_messages(source_file) self._emit_messages(target_file) return str(self.target_filename)
def finish_subsystem(self): Logger.debug(9, f'NETCONFsubsys: finish_subsystem') threading.current_thread().daemon = True self.server.session_ended() Logger.debug(9, 'NETCONF subsys finished') super(NETCONFsubsys, self).finish_subsystem() Logger.debug(9, 'NETCONF subsys finished 2') Logger.debug(9, 'NETCONF subsys finished 3')
def read_msg(self, netconf_ver=0): if netconf_ver == 0: netconf_ver = self.netconf_ver if netconf_ver == 11: msg = self.read_msg_11() else: msg = self.read_msg_10() try: if isinstance(msg, bytes): decoded_msg = msg.decode("utf-8") else: decoded_msg = msg Logger.debug(8,f'Received {len(decoded_msg)} byte message', payload=decoded_msg) except Exception as e: Logger.warning(f"Could not UTF-8 decode message", payload=msg) raise return decoded_msg
def run_command_line(self, sys_argv=sys.argv): def usage(sys_argv): print(f'{sys_argv[0]} --netconf=[host:]port parrot-file.xml') verbosity = 4 host = "localhost" port = 8888 template_dirs = [] trace_files = [] parrot_file = None try: opts, args = getopt.getopt( sys_argv[1:], "hd:vt:m:", ["help", "debug=", "verbose", "netconf=", "template-dir="]) except getopt.GetoptError: usage(sys_argv) sys.exit(2) for opt, arg in opts: if opt in ('-h', '--help'): usage(sys_argv) sys.exit() elif opt in ("--netconf"): self.server = Parrot.NETCONF_Parrot() if ":" in arg: (host, port_str) = arg.split(":") port = int(port_str) else: port = int(arg) elif opt in ("-t", "--template-dir"): template_dirs += [arg] elif opt in ("-d", "--debug"): verbosity = int(arg) elif opt in ("-m", "--make-parrot"): trace_files += [arg] elif opt in ("-v", "--verbose"): verbosity += 1 else: Logger.fatal(f'Unknown option "{opt}".') sys.exit(2) for trace_file_name in trace_files: parrot_file = Parrot_Builder.from_trace_file(trace_file_name) if not parrot_file: if len(args) != 1: usage(sys_argv) Logger.fatal(f"{len(args)} parrot files given.", code=2) parrot_file = args[0] if not self.server: usage(sys_argv) Logger.fatal(f"{len(args)} server specified.", code=2) Logger.set_verbosity(verbosity) self.run(host, port, parrot_file)
def send_msg(self, msg, netconf_ver=0): chunk_size = 10000 if netconf_ver == 0: netconf_ver = self.netconf_ver msg_len = len(msg) while msg != "": chunk = msg[:chunk_size].encode('utf-8') chunk_len = len(chunk) if netconf_ver == 11: header = f"\n#{chunk_len}\n".encode('utf-8') Logger.debug(10, f'Sending NC11 header', payload=header) self.channel.send(header) Logger.debug(9, f'Sending {chunk_len} bytes chunk', payload=chunk) self.channel.send(chunk) msg = msg[chunk_size:] if netconf_ver == 11: Logger.debug(10, f'Sending NC11 delimiter', payload=self.delimiter11) self.channel.send(self.delimiter11) if netconf_ver == 10: Logger.debug(10, f'Sending NC10 delimiter', payload=self.delimiter10b) self.channel.send(self.delimiter10b) Logger.info(f"Sent {msg_len} bytes in NC{netconf_ver} message", payload=msg)
def start_subsystem(self, name, transport, channel): Logger.debug( 8, f'NETCONFsubsys: start_subsystem name={name} transport={transport} channel={channel}' ) self.sock = channel Logger.debug(9, 'Started NETCONF server on channel {!r}'.format(channel)) try: self.handle_session() except Exception as e: Logger.error(f'NETCONFsubsys: callback exception {e}') ##raise Logger.debug(8, 'Stopped NETCONF server on channel {!r}'.format(channel))
def check_channel_request(self, kind, chanid): Logger.debug( 9, f'SSH Server: check_channel_request kind={kind} chanid={chanid}') if kind == 'session': Logger.debug(9, f'SSH Server: check_channel_request accepted') return paramiko.OPEN_SUCCEEDED Logger.debug(9, f'SSH Server: check_channel_request rejected') return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def _record_message(self, message, meta): # This method should maybe parse the XML properly, but since # we also want this to work with potentially malformed XML, # it is instead making some assumptions about the XML encoding direction = NSO_Trace_Parrot_Builder._meta_dir(meta) if not direction: Logger.fatal(f"Message meta malformed: {meta}") message_id_attr_str = "message-id=" # Assume first occurrence of message-id is the NETCONF message-id pos = message.find(message_id_attr_str) if pos >= 0: pos += len(message_id_attr_str) delimiter = message[pos] # Assume message-id value is properly delimited if delimiter not in ['"', "'"]: Logger.fatal(f"Attribute message-id= malformed (1): {meta}") pos += 1 endpos = message[pos:].find(delimiter) # Assume message-id value is at most 100 chars if endpos < 0 or endpos > 100: Logger.fatal(f"Attribute message-id= malformed (2): {meta}") message_id = message[pos:pos + endpos] self.catalog[(direction, message_id)] = message self.catalog_meta[(direction, message_id)] = meta print( f"Recorded message {direction} {message_id} = {message[:40]}..." ) # Assume hello message is not namespaced, and has no redundant whitespace elif message.startswith("<hello "): self.catalog[(direction, "hello")] = message self.catalog_meta[(direction, "hello")] = meta elif direction == "meta": self.catalog[(direction, meta)] = message self.catalog_meta[(direction, meta)] = meta pass elif not message: pass else: Logger.fatal(f"Message not hello and lacks message-id: {meta}")
def read_msg_10(self): chunk_size = 10000 while True: if len(self.indata): Logger.debug(8, f"Reading message10, indata size so far {len(self.indata)}", payload=self.indata) eom = self.indata.find(self.delimiter10b) if eom >= 0: msg = self.indata[:eom] self.indata = self.indata[eom+len(self.delimiter10b):] Logger.debug(8, f"Message10 complete. Size {len(msg)}, remaining in indata {len(self.indata)}") return msg time.sleep(.5) next_chunk = self.channel.recv(chunk_size) Logger.debug(7, f"Read {len(next_chunk)} bytes NC10") if next_chunk: self.indata += next_chunk else: Logger.debug(8, f"Message10 EOF") msg = self.indata #self.indata = b"" self.channel.close() return msg
def matching_tags(matchtag, payloadtag): mpos = matchtag.find("}") if mpos != -1: # If matchtag has namespace, namespaces and tags have to match Logger.debug( 8, f'Match NS matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag ppos = payloadtag.find("}") if ppos != -1: # If matchtag has no namespace, only tags have to match Logger.debug( 8, f'Match NO matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag[ppos + 1:] # If neither has namespace, tags have to match Logger.debug( 8, f'Match NE matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag
def register_callback_object(cb_target): NETCONFsubsys.cb_target = cb_target Logger.debug(9, f'NETCONFsubsys: registered cb={cb_target}')
def match_nodes(matchpoint, payloadpoint): def matching_tags(matchtag, payloadtag): mpos = matchtag.find("}") if mpos != -1: # If matchtag has namespace, namespaces and tags have to match Logger.debug( 8, f'Match NS matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag ppos = payloadtag.find("}") if ppos != -1: # If matchtag has no namespace, only tags have to match Logger.debug( 8, f'Match NO matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag[ppos + 1:] # If neither has namespace, tags have to match Logger.debug( 8, f'Match NE matching_tags({matchtag}, {payloadtag})') return matchtag == payloadtag Logger.debug( 8, f'Match 1 match_nodes_t({matchpoint}, {payloadpoint})') if matching_tags(matchpoint.tag, payloadpoint.tag): for mch in matchpoint: # must match all Logger.debug(8, f'Match 3 Trying match {mch}') one_match = False for pch in payloadpoint: # needs to match one Logger.debug(8, f'Match 4 Trying payload {pch}') if match_nodes(mch, pch): Logger.debug( 8, f'Match M match_nodes_t({mch}, {pch}) = True') one_match = True break if not one_match: Logger.debug( 8, f'Match O match_nodes_t({matchpoint}, {payloadpoint}) = False' ) return False Logger.debug( 8, f'Match N match_nodes_t({matchpoint}, {payloadpoint}) = True' ) return True Logger.debug( 8, f'Match E match_nodes_t({matchpoint}, {payloadpoint}) = False') return False
def handle_session(self): Logger.debug(7, 'Session loop running') self.incoming_message('<?xml version="1.0" encoding="UTF-8"?><hello xmlns="parrot"/>') while True: try: Logger.debug(5,'NETCONF server ready, waiting for next message') msg = self.read_msg() except EOFError: Logger.debug(5,'EOF -- end of session') return except Exception as e: verdict = self.handle_read_exception(e) if verdict == "kill-session": return if verdict == "kill-server": Logger.info('Server terminating') sys.exit(1) # Else keep going try: if("" == msg): Logger.debug(5,'EOF ++ end of session') return if self.incoming_message(msg) == True: return except Exception as e: Logger.error('Exception in server processing: ' + str(e))
def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight, modes): Logger.debug(9, f'SSH Server: check_channel_pty_request') return True
def check_channel_shell_request(self, channel): Logger.debug(9, f'SSH Server: check_channel_shell_request') self.event.set() return True
def get_allowed_auths(self, username): Logger.debug(9, f'SSH Server: get_allowed_auths') return 'gssapi-keyex,gssapi-with-mic,password,publickey'
def enable_auth_gssapi(self): Logger.debug(9, f'SSH Server: enable_auth_gssapi') return True
def check_auth_publickey(self, username, key): Logger.debug(9, f'SSH Server: check_auth_publickey') # No real authenticatio, all users welcome return paramiko.AUTH_SUCCESSFUL
def read_msg_11(self): chunk_size = 10000 max_header_len = 15 frame_len = -1 msg = b"" while True: Logger.debug(8,f'Read data, indata queue length {len(self.indata)} bytes') if frame_len >= 0: # We know the frame length # Read frame body until frame_len bytes available len_indata = len(self.indata) if len_indata >= frame_len: # We have the entire frame msg += self.indata[:frame_len] self.indata = self.indata[frame_len:] frame_len = -1 else: # We got one more chunk of the frame, but not the entire frame msg += self.indata frame_len -= len_indata self.indata = self.channel.recv(chunk_size) if self.indata == b"": Logger.debug(8, f"Message11 EOF") ##self.channel.close() return b"" if frame_len < 0: # Have not the frame length header yet nl0 = self.indata.find(b"\n") nl1 = self.indata.find(b"\n", nl0 + 1) Logger.debug(9, f"Header nl0={nl0}, nl1={nl1}, h={self.indata[1] if len(self.indata) >= 2 else None}") if nl0 == 0 and nl1 > nl0 and nl1 <= max_header_len and self.indata[1] == ord(b'#'): # Found header frame_len_str = self.indata[2:nl1] next_frame_start = nl1 + 1 self.indata = self.indata[next_frame_start:] if frame_len_str == b"#": # We got the entire message return msg try: frame_len = int(frame_len_str.decode('utf-8')) except: Logger.warning(f'Framing error, invalid frame length value: """{frame_len_str}"""') return b"" else: if (nl0 == 0 and nl1 > nl0) or (len(self.indata) > max_header_len): # Definitely should have a complete header, something is wrong Logger.warning(f'Framing error, invalid frame header', payload = self.indata) return b"" # No header in sight, better read some more data = self.channel.recv(chunk_size) self.indata += data if data == b"": Logger.debug(8, f"Message11 header EOF") ##self.channel.close() return b""
def listen(self): NETCONF_Server.set_instance(self) try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #sock.bind(('', self.port)) sock.bind((self.host, self.port)) except Exception as e: Logger.fatal('Bind failed: ' + str(e)) try: sock.listen(100) except Exception as e: Logger.fatal('Listen failed: ' + str(e)) Logger.info(f'Listening for connections on port {self.port}...') host_key = None try: host_key = RSAKey(filename=self.host_key_filename) except: pass if not host_key: Logger.info(f'Generating new host key') host_key = RSAKey.generate(2048) if self.host_key_filename: host_key.write_private_key_file(self.host_key_filename, password=None) Logger.info(f"Wrote host key to file, '{self.host_key_filename}'") while True: try: Logger.info(f'Waiting for client to connect') client, addr = sock.accept() except Exception as e: Logger.fatal('Accept failed: ' + str(e)) self.sock = client (ip, port) = addr Logger.info(f'Client {ip}:{port} connected') self.handle_connection(host_key) Logger.info(f'Client {ip}:{port} disconnected')
def __init__(self): Logger.debug(9, f'SSH Server: init') self.event = threading.Event()
def handle_session(self): Logger.debug(9, 'NETCONF subsys session started') NETCONFsubsys.cb_target.handle_session() Logger.debug(9, 'NETCONF subsys session ended')
def check_auth_password(self, username, password): Logger.debug(9, f'SSH Server: check_auth_password') # No real authenticatio, all users welcome return paramiko.AUTH_SUCCESSFUL