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 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, 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 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 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 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 process_instructions(self, reply_msg): def split_processing_instructions(reply_msg): instructions = [] pi_start_marker = '<?parrot ' pi_end_marker = '?>' while True: pi_start = reply_msg.find(pi_start_marker) if pi_start < 0: break pi_end = reply_msg[pi_start:].find(pi_end_marker) if pi_end < 0: return ('bad-processing-instruction', reply_msg[pi_start:pi_start+20]) pi = reply_msg[pi_start+len(pi_start_marker):pi_end] if pi_start: instructions += [('send', reply_msg[:pi_start])] reply_msg = reply_msg[pi_end+len(pi_end_marker):] cmd = pi.split(" ")[0] data = pi[len(cmd)+1:] instructions += [(cmd, data)] if reply_msg: instructions += [('send', reply_msg)] if not instructions: instructions = [('empty-response', None)] return instructions instructions = split_processing_instructions(reply_msg) end_session = False for (cmd, data) in instructions: if cmd == "send": Logger.debug(6, f'Sending {len(data)} bytes', payload=data) self.send_message(data) elif cmd == "ignore": Logger.debug(5, f'Not sending any response') elif cmd == "netconf11": Logger.debug(5, f'Switching to NETCONF 1.1') self.choose_netconf_ver([11]) elif cmd == "netconf10": Logger.debug(5, f'Switching to NETCONF 1.0') self.choose_netconf_ver([10]) elif cmd == "empty-response": Logger.warning(f'Template did not provide any response. Ending the session.') end_session = True elif cmd == "end-session": Logger.info(f'Ending session') end_session = True else: Logger.error(f'Unknown processing instruction "{cmd}" in template. Ending the session.') end_session = True return end_session
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 handle_connection(self, host_key): try: DoGSSAPIKeyExchange = False t = Transport(self.sock, gss_kex=DoGSSAPIKeyExchange) t.set_subsystem_handler('netconf', self.subsys) t.set_gss_host(socket.getfqdn("")) try: t.load_server_moduli() except: Logger.warning('Failed to load moduli -- gex will be unsupported.') t.add_server_key(host_key) server = Server() t.start_server(server=server) # wait for auth self.channel = t.accept(20) if self.channel is None: Logger.error('No SSH channel') return Logger.info('Waiting for message') server.event.wait(10) Logger.info('Closing') ##self.channel.close() Logger.info('Client connection closed') except ConnectionResetError as e: Logger.debug(5,'Connection reset by peer') except SSHException: Logger.error('SSH negotiation failed, client connection dropped') except Exception as e: Logger.error('Caught exception: ' + str(e.__class__) + ': ' + str(e)) traceback.print_exc() try: t.close() except: pass
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 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 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 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 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 __init__(self): Logger.debug(9, f'SSH Server: init') self.event = threading.Event()
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 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
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 handle_session(self): Logger.debug(9, 'NETCONF subsys session started') NETCONFsubsys.cb_target.handle_session() Logger.debug(9, 'NETCONF subsys session ended')
def incoming_message(self, text): Logger.trace(1,text) Logger.debug(6, f'Incoming message {len(text)} bytes', payload=text) new_text = self.receive_raw_message(text) reply_msg = self.receive_message(new_text) return self.process_instructions(reply_msg)