class WebTerminalHandler(tornado.websocket.WebSocketHandler): clients = [] tasks = [] def __init__(self, *args, **kwargs): self.term = None self.log_file_f = None self.log_time_f = None self.log = None self.id = 0 self.user = None self.ssh = None self.channel = None super(WebTerminalHandler, self).__init__(*args, **kwargs) def check_origin(self, origin): return True @django_request_support @require_auth('user') def open(self): logger.debug('Websocket: Open request') role_name = self.get_argument('role', 'sb') asset_id = self.get_argument('id', 9999) asset = get_object(Asset, id=asset_id) self.termlog = TermLogRecorder(User.objects.get(id=self.user_id)) if asset: roles = user_have_perm(self.user, asset) logger.debug(roles) logger.debug('系统用户: %s' % role_name) login_role = '' for role in roles: if role.name == role_name: login_role = role break if not login_role: logger.warning( 'Websocket: Not that Role %s for Host: %s User: %s ' % (role_name, asset.hostname, self.user.username)) self.close() return else: logger.warning('Websocket: No that Host: %s User: %s ' % (asset_id, self.user.username)) self.close() return logger.debug( 'Websocket: request web terminal Host: %s User: %s Role: %s' % (asset.hostname, self.user.username, login_role.name)) self.term = WebTty(self.user, asset, login_role, login_type='web') # self.term.remote_ip = self.request.remote_ip self.term.remote_ip = self.request.headers.get("X-Real-IP") if not self.term.remote_ip: self.term.remote_ip = self.request.remote_ip self.ssh = self.term.get_connection() self.channel = self.ssh.invoke_shell(term='xterm') WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound)) WebTerminalHandler.clients.append(self) for t in WebTerminalHandler.tasks: if t.is_alive(): continue try: t.setDaemon(True) t.start() except RuntimeError: pass def on_message(self, message): jsondata = json.loads(message) if not jsondata: return if 'resize' in jsondata.get('data'): self.termlog.write(message) self.channel.resize_pty( width=int(jsondata.get('data').get('resize').get('cols', 100)), height=int(jsondata.get('data').get('resize').get('rows', 35))) elif jsondata.get('data'): self.termlog.recoder = True self.term.input_mode = True if str(jsondata['data']) in ['\r', '\n', '\r\n']: match = re.compile(r'\x1b\[\?1049', re.X).findall(self.term.vim_data) if match: if self.term.vim_flag or len(match) == 2: self.term.vim_flag = False else: self.term.vim_flag = True elif not self.term.vim_flag: result = self.term.deal_command(self.term.data)[0:200] if len(result) > 0: TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=result).save() self.term.vim_data = '' self.term.data = '' self.term.input_mode = False self.channel.send(jsondata['data']) else: pass def on_close(self): logger.debug('Websocket: Close request') print self.termlog.CMD self.termlog.save() if self in WebTerminalHandler.clients: WebTerminalHandler.clients.remove(self) try: self.log_file_f.write('End time is %s' % datetime.datetime.now()) self.log.is_finished = True self.log.end_time = datetime.datetime.now() self.log.filename = self.termlog.filename self.log.save() self.log_time_f.close() self.ssh.close() self.close() except AttributeError: pass def forward_outbound(self): self.log_file_f, self.log_time_f, self.log = self.term.get_log() self.id = self.log.id self.termlog.setid(self.id) try: data = '' pre_timestamp = time.time() while True: r, w, e = select.select([self.channel], [], []) if self.channel in r: recv = self.channel.recv(1024) if not len(recv): return data += recv self.term.vim_data += recv try: self.write_message(data.decode('utf-8', 'replace')) self.termlog.write(data) self.termlog.recoder = False now_timestamp = time.time() self.log_time_f.write('%s %s\n' % (round( now_timestamp - pre_timestamp, 4), len(data))) self.log_file_f.write(data) pre_timestamp = now_timestamp self.log_file_f.flush() self.log_time_f.flush() if self.term.input_mode: self.term.data += data data = '' except UnicodeDecodeError: pass except IndexError: pass
def posix_shell(self): """ Use paramiko channel connect server interactive. 使用paramiko模块的channel,连接后端,进入交互式 """ log_file_f, log_time_f, log = self.get_log() termlog = TermLogRecorder(User.objects.get(id=self.user.id)) termlog.setid(log.id) old_tty = termios.tcgetattr(sys.stdin) pre_timestamp = time.time() data = '' input_mode = False try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) self.channel.settimeout(0.0) while True: try: r, w, e = select.select([self.channel, sys.stdin], [], []) flag = fcntl.fcntl(sys.stdin, fcntl.F_GETFL, 0) fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flag | os.O_NONBLOCK) except Exception: pass if self.channel in r: try: x = self.channel.recv(10240) if len(x) == 0: break index = 0 len_x = len(x) while index < len_x: try: n = os.write(sys.stdout.fileno(), x[index:]) sys.stdout.flush() index += n except OSError as msg: if msg.errno == errno.EAGAIN: continue now_timestamp = time.time() termlog.write(x) termlog.recoder = False log_time_f.write( '%s %s\n' % (round(now_timestamp - pre_timestamp, 4), len(x))) log_time_f.flush() log_file_f.write(x) log_file_f.flush() pre_timestamp = now_timestamp log_file_f.flush() self.vim_data += x if input_mode: data += x except socket.timeout: pass if sys.stdin in r: try: x = os.read(sys.stdin.fileno(), 4096) except OSError: pass termlog.recoder = True input_mode = True if self.is_output(str(x)): # 如果len(str(x)) > 1 说明是复制输入的 if len(str(x)) > 1: data = x match = self.vim_end_pattern.findall(self.vim_data) if match: if self.vim_flag or len(match) == 2: self.vim_flag = False else: self.vim_flag = True elif not self.vim_flag: self.vim_flag = False data = self.deal_command(data)[0:200] if data is not None: TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save() data = '' self.vim_data = '' input_mode = False if len(x) == 0: break self.channel.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) log_file_f.write('End time is %s' % datetime.datetime.now()) log_file_f.close() log_time_f.close() termlog.save() log.filename = termlog.filename log.is_finished = True log.end_time = datetime.datetime.now() log.save()
class WebTerminalHandler(tornado.websocket.WebSocketHandler): clients = [] tasks = [] def __init__(self, *args, **kwargs): self.term = None self.log_file_f = None self.log_time_f = None self.log = None self.id = 0 self.user = None self.ssh = None self.channel = None super(WebTerminalHandler, self).__init__(*args, **kwargs) def check_origin(self, origin): return True @django_request_support @require_auth('user') def open(self): logger.debug('Websocket: Open request') role_name = self.get_argument('role', 'sb') asset_id = self.get_argument('id', 9999) asset = get_object(Asset, id=asset_id) self.termlog = TermLogRecorder(User.objects.get(id=self.user_id)) if asset: roles = user_have_perm(self.user, asset) logger.debug(roles) logger.debug('系统用户: %s' % role_name) login_role = '' for role in roles: if role.name == role_name: login_role = role break if not login_role: logger.warning('Websocket: Not that Role %s for Host: %s User: %s ' % (role_name, asset.hostname, self.user.username)) self.close() return else: logger.warning('Websocket: No that Host: %s User: %s ' % (asset_id, self.user.username)) self.close() return logger.debug('Websocket: request web terminal Host: %s User: %s Role: %s' % (asset.hostname, self.user.username, login_role.name)) self.term = WebTty(self.user, asset, login_role, login_type='web') # self.term.remote_ip = self.request.remote_ip self.term.remote_ip = self.request.headers.get("X-Real-IP") if not self.term.remote_ip: self.term.remote_ip = self.request.remote_ip self.ssh = self.term.get_connection() self.channel = self.ssh.invoke_shell(term='xterm') WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound)) WebTerminalHandler.clients.append(self) for t in WebTerminalHandler.tasks: if t.is_alive(): continue try: t.setDaemon(True) t.start() except RuntimeError: pass def on_message(self, message): jsondata = json.loads(message) if not jsondata: return if 'resize' in jsondata.get('data'): self.termlog.write(message) self.channel.resize_pty( width=int(jsondata.get('data').get('resize').get('cols', 100)), height=int(jsondata.get('data').get('resize').get('rows', 35)) ) elif jsondata.get('data'): self.termlog.recoder = True self.term.input_mode = True if str(jsondata['data']) in ['\r', '\n', '\r\n']: match = re.compile(r'\x1b\[\?1049', re.X).findall(self.term.vim_data) if match: if self.term.vim_flag or len(match) == 2: self.term.vim_flag = False else: self.term.vim_flag = True elif not self.term.vim_flag: result = self.term.deal_command(self.term.data)[0:200] if len(result) > 0: TtyLog(log=self.log, datetime=datetime.datetime.now(), cmd=result).save() self.term.vim_data = '' self.term.data = '' self.term.input_mode = False self.channel.send(jsondata['data']) else: pass def on_close(self): logger.debug('Websocket: Close request') print self.termlog.CMD self.termlog.save() if self in WebTerminalHandler.clients: WebTerminalHandler.clients.remove(self) try: self.log_file_f.write('End time is %s' % datetime.datetime.now()) self.log.is_finished = True self.log.end_time = datetime.datetime.now() self.log.filename = self.termlog.filename self.log.save() self.log_time_f.close() self.ssh.close() self.close() except AttributeError: pass def forward_outbound(self): self.log_file_f, self.log_time_f, self.log = self.term.get_log() self.id = self.log.id self.termlog.setid(self.id) try: data = '' pre_timestamp = time.time() while True: r, w, e = select.select([self.channel], [], []) if self.channel in r: recv = self.channel.recv(1024) if not len(recv): return data += recv self.term.vim_data += recv try: self.write_message(data.decode('utf-8', 'replace')) self.termlog.write(data) self.termlog.recoder = False now_timestamp = time.time() self.log_time_f.write('%s %s\n' % (round(now_timestamp - pre_timestamp, 4), len(data))) self.log_file_f.write(data) pre_timestamp = now_timestamp self.log_file_f.flush() self.log_time_f.flush() if self.term.input_mode: self.term.data += data data = '' except UnicodeDecodeError: pass except IndexError: pass
def posix_shell(self): """ Use paramiko channel connect server interactive. 使用paramiko模块的channel,连接后端,进入交互式 """ log_file_f, log_time_f, log = self.get_log() termlog = TermLogRecorder(User.objects.get(id=self.user.id)) termlog.setid(log.id) old_tty = termios.tcgetattr(sys.stdin) pre_timestamp = time.time() data = '' input_str = '' input_mode = False try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) self.channel.settimeout(0.0) while True: try: r, w, e = select.select([self.channel, sys.stdin], [], []) flag = fcntl.fcntl(sys.stdin, fcntl.F_GETFL, 0) fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flag|os.O_NONBLOCK) except Exception: pass if self.channel in r: try: x = self.channel.recv(10240) if len(x) == 0: break if self.vim_flag: self.vim_data += x index = 0 len_x = len(x) while index < len_x: try: n = os.write(sys.stdout.fileno(), x[index:]) sys.stdout.flush() index += n except OSError as msg: if msg.errno == errno.EAGAIN: continue now_timestamp = time.time() termlog.write(x) termlog.recoder = False log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x))) log_time_f.flush() log_file_f.write(x) log_file_f.flush() pre_timestamp = now_timestamp log_file_f.flush() if input_mode and not self.is_output(x): data += x input_str = '' except socket.timeout: pass if sys.stdin in r: try: x = os.read(sys.stdin.fileno(), 4096) except OSError: pass termlog.recoder = True input_mode = True input_str += x if str(x) in ['\r', '\n', '\r\n']: # 这个是用来处理用户的复制操作 if input_str != x: data += input_str if self.vim_flag: match = self.vim_end_pattern.findall(self.vim_data) if match: if self.vim_end_flag or len(match) == 2: self.vim_flag = False self.vim_end_flag = False else: self.vim_end_flag = True else: data = self.deal_command(data)[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 if len(x) == 0: break self.channel.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) log_file_f.write('End time is %s' % datetime.datetime.now()) log_file_f.close() log_time_f.close() termlog.save() log.filename = termlog.filename log.is_finished = True log.end_time = datetime.datetime.now() log.save()
def start_shell(): global q """ Use paramiko channel connect server interactive. 使用paramiko模块的channel,连接后端,进入交互式 """ log_file_f, log_time_f, log = self.get_log() termlog = TermLogRecorder(self.user) termlog.setid(log.id) old_tty = termios.tcgetattr(sys.stdin) pre_timestamp = time.time() data = '' input_mode = False try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) self.channel.settimeout(0.0) cache_line = "" RECORD_CMD_FLAG = True while True: try: r, w, e = select.select([self.channel, sys.stdin], [], []) flag = fcntl.fcntl(sys.stdin, fcntl.F_GETFL, 0) fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flag | os.O_NONBLOCK) except Exception: pass if self.channel in r: try: y = self.channel.recv(10240) if len(y) == 0: break index = 0 len_y = len(y) f = open(RECORD_PATH, "a+") while index < len_y: try: n = os.write(sys.stdout.fileno(), y[index:]) if self.role == "rd" and RECORD_CMD_FLAG: f.write(y[index:]) sys.stdout.flush() index += n except OSError as msg: if msg.errno == errno.EAGAIN: continue f.close() now_timestamp = time.time() termlog.write(y) termlog.recoder = False log_time_f.write('%s %s\n' % (round( now_timestamp - pre_timestamp, 4), len(y))) log_time_f.flush() log_file_f.write(y) log_file_f.flush() pre_timestamp = now_timestamp log_file_f.flush() self.vim_data += y if input_mode: data += y except socket.timeout: pass if sys.stdin in r: RECORD_CMD_FLAG = True try: x = os.read(sys.stdin.fileno(), 4096) q.put(x) except OSError: pass termlog.recoder = True input_mode = True if self.is_output(str(x)): # 如果len(str(x)) > 1 说明是复制输入的 if len(str(x)) > 1: data = x match = self.vim_end_pattern.findall(self.vim_data) if match: if self.vim_flag or len(match) == 2: self.vim_flag = False else: self.vim_flag = True elif not self.vim_flag: self.vim_flag = False data = self.deal_command(data)[0:200] if data is not None: TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save() data = '' self.vim_data = '' input_mode = False if len(x) == 0: break if x in ['\x03', '\r\n', '\n', '\x0D']: f = open(RECORD_PATH, "r") lines = f.readlines() cmd = "" if lines: cmd = lines[-1] f.close() f = open(RECORD_PATH, "w") f.close() cmds_str = re.compile('\[?.*@.*\]?[\$#]\s').split( cmd) logger.info("get command from log file is: %s", cmds_str) if len(cmds_str) == 2: cmds = cmds_str[1] logger.info("command >>>: %s", cmds.split("|")) cmd = set([ i.split()[0].strip() for i in cmds.split("|") if i.split() ]) logger.info("command set>>>: %s", cmd) if self.role == "rd" and not ALLOW_CMD.issuperset( cmd): x = '\x03' if "tail" in cmd or "cat" in cmd: RECORD_CMD_FLAG = False else: RECORD_CMD_FLAG = True cache_line = "" self.channel.send(x) if self.kill_shell: break finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) log_file_f.write('End time is %s' % datetime.datetime.now()) log_file_f.close() log_time_f.close() termlog.save() log.filename = termlog.filename log.is_finished = True log.end_time = datetime.datetime.now() log.save() if os.path.isfile(RECORD_PATH): os.remove(RECORD_PATH) sys.exit(0)