diff --git a/connect.py b/connect.py index 603f57f60..2731e358e 100755 --- a/connect.py +++ b/connect.py @@ -16,6 +16,8 @@ import readline import django import paramiko import errno +import pyte +import operator import struct, fcntl, signal, socket, select from io import open as copen import uuid @@ -90,8 +92,11 @@ class Tty(object): self.remote_ip = '' self.login_type = login_type self.vim_flag = False - self.ps1_pattern = re.compile('\[.*@.*\][\$#]') + self.ps1_pattern = re.compile('\[.*@.*\][\$#]\s') + self.vim_pattern = re.compile(r'\Wvi[m]+\s.* | \Wfg\s.*', re.X) self.vim_data = '' + self.stream = pyte.ByteStream() + self.screen = None @staticmethod def is_output(strings): @@ -101,135 +106,50 @@ class Tty(object): return True return False - @staticmethod - def remove_obstruct_char(cmd_str): - '''删除一些干扰的特殊符号''' - control_char = re.compile(r'\x07 | \x1b\[1P | \r ', re.X) - cmd_str = control_char.sub('',cmd_str.strip()) - patch_char = re.compile('\x08\x1b\[C') #删除方向左右一起的按键 - while patch_char.search(cmd_str): - cmd_str = patch_char.sub('', cmd_str.rstrip()) - return cmd_str - - @staticmethod - def deal_backspace(match_str, result_command, pattern_str, backspace_num): - ''' - 处理删除确认键 - ''' - if backspace_num > 0: - if backspace_num > len(result_command): - result_command += pattern_str - result_command = result_command[0:-backspace_num] - else: - result_command = result_command[0:-backspace_num] - result_command += pattern_str - del_len = len(match_str)-3 - if del_len > 0: - result_command = result_command[0:-del_len] - return result_command, len(match_str) - - @staticmethod - def deal_replace_char(match_str,result_command,backspace_num): - ''' - 处理替换命令 - ''' - str_lists = re.findall(r'(?<=\x1b\[1@)\w',match_str) - tmp_str =''.join(str_lists) - result_command_list = list(result_command) - if len(tmp_str) > 1: - result_command_list[-backspace_num:-(backspace_num-len(tmp_str))] = tmp_str - elif len(tmp_str) > 0: - if result_command_list[-backspace_num] == ' ': - result_command_list.insert(-backspace_num, tmp_str) - else: - result_command_list[-backspace_num] = tmp_str - result_command = ''.join(result_command_list) - return result_command, len(match_str) - - def remove_control_char(self, result_command): + def command_parser(self, command): """ - 处理日志特殊字符 + 处理命令中如果有ps1或者mysql的特殊情况,极端情况下会有ps1和mysql + :param command:要处理的字符传 + :return:返回去除PS1或者mysql字符串的结果 """ - control_char = re.compile(r""" - \x1b[ #%()*+\-.\/]. | - \r | #匹配 回车符(CR) - (?:\x1b\[|\x9b) [ -?]* [@-~] | #匹配 控制顺序描述符(CSI)... Cmd - (?:\x1b\]|\x9d) .*? (?:\x1b\\|[\a\x9c]) | \x07 | #匹配 操作系统指令(OSC)...终止符或振铃符(ST|BEL) - (?:\x1b[P^_]|[\x90\x9e\x9f]) .*? (?:\x1b\\|\x9c) | #匹配 设备控制串或私讯或应用程序命令(DCS|PM|APC)...终止符(ST) - \x1b. #匹配 转义过后的字符 - [\x80-\x9f] | (?:\x1b\]0.*) | \[.*@.*\][\$#] | (.*mysql>.*) #匹配 所有控制字符 - """, re.X) - result_command = control_char.sub('', result_command.strip()) - - if not self.vim_flag: - if result_command.startswith('vi') or result_command.startswith('fg'): - self.vim_flag = True - return result_command.decode('utf8',"ignore") + result = None + match = self.ps1_pattern.split(command) + if match: + # 只需要最后的一个PS1后面的字符串 + result = match[-1].strip() else: - return '' + # PS1没找到,查找mysql + match = re.split('mysql>\s', command) + if match: + # 只需要最后一个mysql后面的字符串 + result = match[-1].strip() + return result - def deal_command(self, str_r): + def deal_command(self): """ - 处理命令中特殊字符 + 处理截获的命令 + :return:返回最后的处理结果 """ - str_r = self.remove_obstruct_char(str_r) - - result_command = '' # 最后的结果 - backspace_num = 0 # 光标移动的个数 - reach_backspace_flag = False # 没有检测到光标键则为true - pattern_str = '' - while str_r: - tmp = re.match(r'\s*\w+\s*', str_r) - if tmp: - str_r = str_r[len(str(tmp.group(0))):] - if reach_backspace_flag: - pattern_str += str(tmp.group(0)) - continue + command = '' + # 从虚拟屏幕中获取处理后的数据 + for line in reversed(self.screen.buffer): + line_data = "".join(map(operator.attrgetter("data"), line)).strip() + if len(line_data) > 0: + parser_result = self.command_parser(line_data) + if parser_result is not None: + # 2个条件写一起会有错误的数据 + if len(parser_result) > 0: + command = parser_result else: - result_command += str(tmp.group(0)) - continue - - tmp = re.match(r'\x1b\[K[\x08]*', str_r) - if tmp: - result_command, del_len = self.deal_backspace(str(tmp.group(0)), result_command, pattern_str, backspace_num) - reach_backspace_flag = False - backspace_num = 0 - pattern_str = '' - str_r = str_r[del_len:] - continue - - tmp = re.match(r'\x08+', str_r) - if tmp: - str_r = str_r[len(str(tmp.group(0))):] - if len(str_r) != 0: - if reach_backspace_flag: - result_command = result_command[0:-backspace_num] + pattern_str - pattern_str = '' - else: - reach_backspace_flag = True - backspace_num = len(str(tmp.group(0))) - continue - else: - break - - tmp = re.match(r'(\x1b\[1@\w)+', str_r) #处理替换的命令 - if tmp: - result_command,del_len = self.deal_replace_char(str(tmp.group(0)), result_command, backspace_num) - str_r = str_r[del_len:] - backspace_num = 0 - continue - - if reach_backspace_flag: - pattern_str += str_r[0] - else: - result_command += str_r[0] - str_r = str_r[1:] - - if backspace_num > 0: - result_command = result_command[0:-backspace_num] + pattern_str - - result_command = self.remove_control_char(result_command) - return result_command + command = line_data + break + if command != '': + # 判断用户输入的是否是vim 或者fg命令 + if self.vim_pattern.search(command): + self.vim_flag = True + # 虚拟屏幕清空 + self.screen.reset() + return command def get_log(self): """ @@ -294,7 +214,7 @@ class Tty(object): # 发起ssh连接请求 Make a ssh connection ssh = paramiko.SSHClient() - #ssh.load_system_host_keys() + # ssh.load_system_host_keys() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: role_key = connect_info.get('role_key') @@ -367,6 +287,7 @@ class SshTty(Tty): old_tty = termios.tcgetattr(sys.stdin) pre_timestamp = time.time() data = '' + input_str = '' input_mode = False try: tty.setraw(sys.stdin.fileno()) @@ -411,6 +332,8 @@ class SshTty(Tty): if input_mode and not self.is_output(x): data += x + input_str = '' + except socket.timeout: pass @@ -420,19 +343,25 @@ class SshTty(Tty): except OSError: pass input_mode = True + input_str += x if str(x) in ['\r', '\n', '\r\n']: + # 这个是用来处理用户的复制操作 + if input_str != x: + data += input_str + self.stream.feed(data) if self.vim_flag: match = self.ps1_pattern.search(self.vim_data) if match: self.vim_flag = False - data = self.deal_command(data)[0:200] + data = self.deal_command()[0:200] if len(data) > 0: TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save() else: - data = self.deal_command(data)[0:200] + data = self.deal_command()[0:200] if len(data) > 0: TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save() data = '' + input_str = '' self.vim_data = '' input_mode = False @@ -466,6 +395,8 @@ class SshTty(Tty): win_size = self.get_win_size() #self.channel = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1], term='xterm') self.channel = channel = transport.open_session() + self.screen = pyte.Screen(win_size[1], win_size[0]) + self.stream.attach(self.screen) channel.get_pty(term='xterm', height=win_size[0], width=win_size[1]) channel.invoke_shell() try: