def websocket_to_django(self): try: while True: time.sleep(0.0001) data = self.guacamoleclient.receive() if not data: return if self.websocker.send_flag == 0: self.websocker.send(data) elif self.websocker.send_flag == 1: async_to_sync(self.websocker.channel_layer.group_send)( self.websocker.group, { "type": "group.message", "text": data, }) self.res.append(data) # 指定条结果或者指定秒数就保存一次 if len(self.res) > 2000 or int(time.time() - self.last_save_time) > 60 or \ sys.getsizeof(self.res) > 2097152: tmp = list(self.res) self.res = [] self.last_save_time = time.time() res(self.res_file, tmp, False) except Exception: print(traceback.format_exc()) if self.websocker.send_flag == 0: self.websocker.send('0.;') elif self.websocker.send_flag == 1: async_to_sync(self.websocker.channel_layer.group_send)( self.websocker.group, { "type": "group.message", "text": '0.;', }) finally: self.close()
def disconnect(self, close_code): try: async_to_sync(self.channel_layer.group_discard)(self.group, self.channel_name) if close_code != 3001: self.ssh.close() except Exception: print(traceback.format_exc()) finally: try: if self.ssh.cmd: tmp = list(self.ssh.res_asciinema) self.ssh.res_asciinema = [] res(self.ssh.res_file, tmp) except Exception: print(traceback.format_exc()) if self.ssh.cmd: terminal_log( self.session.get('username'), self.remote_host.hostname, self.remote_host.ip, self.remote_host.get_protocol_display(), self.remote_host.port, self.remote_host.remote_user.username, self.ssh.cmd, self.ssh.res_file, self.client, self.user_agent, self.start_time, ) TerminalSession.objects.filter(name=self.channel_name, group=self.group).delete()
def disconnect(self, close_code): time.sleep(0.5) if not self.closed: self.closed = True if 'webguacamole终端文件上传下载' in self.session[ settings.INIT_PERMISSION]['titles']: try: upload_file_path = os.path.join(settings.GUACD_ROOT, self.group) shutil.rmtree(upload_file_path, ignore_errors=True) except Exception: pass try: async_to_sync(self.channel_layer.group_send)( self.group, { # 关闭 viewer "type": "close.viewer", "text": "", }) async_to_sync(self.channel_layer.group_discard)( self.group, self.channel_name) if close_code != 3001: self.guacamoleclient.close() except Exception: pass finally: if self.guacamoleclient.res: try: tmp = list(self.guacamoleclient.res) self.guacamoleclient.res = [] res(self.guacamoleclient.res_file, tmp, False) except Exception: pass try: terminal_log( self.session.get('username'), self.remote_host.hostname, self.remote_host.ip, self.remote_host.get_protocol_display(), self.remote_host.port, self.remote_host.remote_user.username, self.guacamoleclient.file_cmd, self.guacamoleclient.res_file, self.client, self.user_agent, self.start_time, ) except Exception: pass TerminalSession.objects.filter(name=self.channel_name, group=self.group).delete()
def disconnect(self, close_code): try: async_to_sync(self.channel_layer.group_discard)(self.group, self.channel_name) if close_code == 3001: pass else: self.telnet.close() except Exception: print(traceback.format_exc()) finally: try: if self.telnet.cmd: tmp = list(self.telnet.res_asciinema) self.telnet.res_asciinema = [] res(self.telnet.res_file, tmp) except Exception: print(traceback.format_exc()) user_agent = None for i in self.scope['headers']: if i[0].decode('utf-8') == 'user-agent': user_agent = i[1].decode('utf-8') break if self.telnet.cmd: terminal_log( self.session.get('username'), self.remote_host.hostname, self.remote_host.ip, self.remote_host.get_protocol_display(), self.remote_host.port, self.remote_host.remote_user.username, self.telnet.cmd, self.telnet.res_file, self.client, self.user_agent, self.start_time, ) TerminalSession.objects.filter(name=self.channel_name, group=self.group).delete()
def disconnect(self, close_code): time.sleep(0.5) if not self.closed: self.closed = True try: async_to_sync(self.channel_layer.group_discard)(self.group, self.channel_name) if close_code != 3001: self.guacamoleclient.close() except Exception: pass finally: if self.guacamoleclient.res: try: tmp = list(self.guacamoleclient.res) self.guacamoleclient.res = [] res(self.guacamoleclient.res_file, tmp, False) except Exception: pass try: terminal_log( self.session.get('username'), self.remote_host.hostname, self.remote_host.ip, self.remote_host.get_protocol_display(), self.remote_host.port, self.remote_host.remote_user.username, '', self.guacamoleclient.res_file, self.client, self.user_agent, self.start_time, ) except Exception: pass TerminalSession.objects.filter(name=self.channel_name, group=self.group).delete()
def close(self, terminal_type='ssh'): time.sleep(0.5) # 防止多次停止重复保存数据 if terminal_type is 'N': # 重复登陆时可能会调用close,这时不能删除这些 key,否则会把当前正常会话也关闭掉 self.closed = True try: # logger.error("密码无效 {} - {}".format(self.http_user, self.password)) self.chan_cli.transport.close() except Exception: logger.error(traceback.format_exc()) return if not self.closed: logger.info("后端主机 (%s@%s) 会话结束" % (self.ssh_args[2], self.ssh_args[0])) self.closed = True # 关闭ssh终端,必须分开 try 关闭,否则当强制关闭一方时,另一方连接可能被挂起 try: self.chan_cli.transport.close() except Exception: logger.error(traceback.format_exc()) try: self.chan_ser.transport.close() except Exception: logger.error(traceback.format_exc()) try: if self.cmd: terminal_log( self.http_user, self.hostname, self.ssh_args[0], 'ssh', self.ssh_args[1], self.ssh_args[2], self.cmd, self.res_file, self.client_addr, # 客户端 ip self.client, self.log_start_time, ) except Exception: logger.error(traceback.format_exc()) try: if self.cmd: tmp = list(self.res_asciinema) self.res_asciinema = [] res(self.res_file, tmp) except Exception: logger.error(traceback.format_exc()) try: TerminalSession.objects.filter( name='{}_{}_{}_session'.format(self.http_user, self.password, terminal_type) ).delete() except Exception: logger.error(traceback.format_exc()) try: # 发送数据给查看会话的 websocket 链接 message = dict() message['status'] = 1 message['message'] = '\n\r连接已断开\r\n' channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)(self.group, { "type": "chat.message", "text": message, }) except Exception: logger.error(traceback.format_exc()) cache.delete('{}_{}_{}_session'.format(self.http_user, self.password, terminal_type)) cache.delete('{}_{}_{}_session_lock'.format(self.http_user, self.password, terminal_type))
def bridge(self): # 桥接 客户终端 和 代理服务终端 交互 # transport_keepalive(self.chan_ser.transport) sel = selectors.DefaultSelector() # Linux epol sel.register(self.chan_cli, selectors.EVENT_READ) sel.register(self.chan_ser, selectors.EVENT_READ) try: while self.chan_ser and self.chan_cli and not (self.chan_ser.closed or self.chan_cli.closed): events = sel.select(timeout=terminal_exipry_time) # 指定时间无数据输入或者无数据返回则断开连接 if not events: raise socket.timeout for key, n in events: if key.fileobj == self.chan_ser: try: recv_message = self.chan_ser.recv(1024) if self.zmodem: if b'**\x18B0800000000022d\r\x8a' in recv_message: self.zmodem = False delay = round(time.time() - self.start_time, 6) self.res_asciinema.append(json.dumps([delay, 'o', '\r\n'])) # logger.info("zmodem end") self.chan_cli.send(recv_message) continue else: if b'rz\r**\x18B00000000000000\r\x8a' in recv_message or b'rz waiting to receive.**\x18B0100000023be50\r\x8a' in recv_message: self.zmodem = True # logger.info("zmodem start") self.chan_cli.send(recv_message) continue # logger.info(recv_message) if len(recv_message) == 0: self.chan_cli.send("\r\n\033[31m服务端已断开连接....\033[0m\r\n") time.sleep(1) break else: try: # 发送数据给查看会话的 websocket 组 message = dict() message['status'] = 0 message['message'] = recv_message.decode('utf-8') channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)(self.group, { "type": "chat.message", "text": message, }) except UnicodeDecodeError: try: recv_message += self.chan_ser.recv(1) message = dict() message['status'] = 0 message['message'] = recv_message.decode('utf-8') channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)(self.group, { "type": "chat.message", "text": message, }) except UnicodeDecodeError: try: recv_message += self.chan_ser.recv(1) message = dict() message['status'] = 0 message['message'] = recv_message.decode('utf-8') channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)(self.group, { "type": "chat.message", "text": message, }) except UnicodeDecodeError: logger.error(traceback.format_exc()) message = dict() message['status'] = 0 # 拼接2次后还是报错则证明结果是乱码,强制转换 message['message'] = recv_message.decode('utf-8', 'ignore') channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)(self.group, { "type": "chat.message", "text": message, }) self.chan_cli.send(recv_message) try: data = recv_message.decode('utf-8') if self.tab_mode: tmp = data.split(' ') # tab 只返回一个命令时匹配 # print(tmp) if len(tmp) == 2 and tmp[1] == '' and tmp[0] != '': self.cmd_tmp = self.cmd_tmp + tmp[0].encode().replace(b'\x07', b'').decode() elif len(tmp) == 1 and tmp[0].encode() != b'\x07': # \x07 蜂鸣声 self.cmd_tmp = self.cmd_tmp + tmp[0].encode().replace(b'\x07', b'').decode() self.tab_mode = False if self.history_mode: # 不完善,只支持向上翻一个历史命令 # print(data) if data.strip() != '': self.cmd_tmp = data self.history_mode = False except Exception: pass # logger.error(traceback.format_exc()) # 记录操作录像 try: """ 防止 sz rz 传输文件时的报错 """ delay = round(time.time() - self.start_time, 6) self.res_asciinema.append(json.dumps([delay, 'o', recv_message.decode('utf-8')])) # 250条结果或者指定秒数就保存一次,这个任务可以优化为使用 celery if len(self.res_asciinema) > 2000 or int(time.time() - self.last_save_time) > 60 \ or sys.getsizeof(self.res_asciinema) > 2097152: tmp = list(self.res_asciinema) self.res_asciinema = [] self.last_save_time = time.time() res(self.res_file, tmp) except Exception: pass # logger.error(traceback.format_exc()) except socket.timeout: logger.error(traceback.format_exc()) if key.fileobj == self.chan_cli: try: send_message = self.chan_cli.recv(4096) if len(send_message) == 0: logger.info('客户端断开了连接 {}....'.format(self.client_addr)) # time.sleep(1) break else: if not self.lock: self.chan_ser.send(send_message) if not self.zmodem: try: data = send_message.decode('utf-8') if data == '\r': # 记录命令 data = '\n' if self.cmd_tmp.strip() != '': self.cmd_tmp += data self.cmd += self.cmd_tmp self.cmd_tmp = '' elif data.encode() == b'\x07': pass else: if data == '\t' or data.encode() == b'\x1b': # \x1b 点击2下esc键也可以补全 self.tab_mode = True elif data.encode() == b'\x1b[A' or data.encode() == b'\x1b[B': self.history_mode = True else: self.cmd_tmp += data except Exception: pass # logger.error(traceback.format_exc()) else: # 红色提示文字 self.chan_cli.send("\r\n\033[31m当前会话已被管理员锁定\033[0m\r\n") self.check_channel_window_change_request( self.chan_cli, self.width - 1, self.height, 0, 0 ) self.check_channel_window_change_request( self.chan_cli, self.width + 1, self.height, 0, 0 ) except socket.timeout: logger.error(traceback.format_exc()) except Exception: logger.error(traceback.format_exc()) break except socket.timeout: self.chan_cli.send("\r\n\033[31m由于长时间没有操作或者没有数据返回,连接已断开!\033[0m\r\n") logger.info("后端主机 (%s@%s) 会话由于长时间没有操作或者没有数据返回,连接断开!" % (self.ssh_args[2], self.ssh_args[0])) except Exception: logger.error(traceback.format_exc())
def bridge(self): # 桥接 客户终端 和 代理服务终端 交互 # transport_keepalive(self.chan_ser.transport) sel = selectors.DefaultSelector( ) # 根据平台自动选择 IO 模式(kqueue, devpoll, epoll, poll, select) sel.register(self.chan_cli, selectors.EVENT_READ) sel.register(self.chan_ser, selectors.EVENT_READ) try: while self.chan_ser and self.chan_cli and not ( self.chan_ser.closed or self.chan_cli.closed): events = sel.select( timeout=terminal_exipry_time) # 指定时间无数据输入或者无数据返回则断开连接 if not events: raise socket.timeout for key, n in events: if key.fileobj == self.chan_ser: try: recv_message = self.chan_ser.recv(BufferSize) if self.zmodem: if zmodemszend in recv_message or zmodemrzend in recv_message: self.zmodem = False delay = round( time.time() - self.start_time, 6) self.res_asciinema.append( json.dumps([delay, 'o', '\r\n'])) # logger.info("zmodem end") if zmodemcancel in recv_message: self.zmodem = False self.chan_ser.send(b'\n') # logger.info("zmodem cancel") self.chan_cli.send(recv_message) continue else: if zmodemszstart in recv_message or zmodemrzstart in recv_message or \ zmodemrzestart in recv_message or zmodemrzsstart in recv_message or \ zmodemrzesstart in recv_message: self.zmodem = True # logger.info("zmodem start") self.chan_cli.send(recv_message) continue if len(recv_message) == 0: self.chan_cli.send( "\r\n\033[31m服务端已断开连接....\033[0m\r\n") time.sleep(1) break else: message = dict() message['status'] = 0 try: # 发送数据给查看会话的 websocket 组 message['message'] = recv_message.decode( 'utf-8') except UnicodeDecodeError: try: recv_message += self.chan_ser.recv(1) message[ 'message'] = recv_message.decode( 'utf-8') except UnicodeDecodeError: try: recv_message += self.chan_ser.recv( 1) message[ 'message'] = recv_message.decode( 'utf-8') except UnicodeDecodeError: logger.error( traceback.format_exc()) # 拼接2次后还是报错则证明结果是乱码,强制转换 message[ 'message'] = recv_message.decode( 'utf-8', 'ignore') self.chan_cli.send(recv_message) channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)( self.group, { "type": "chat.message", "text": message, }) delay = round(time.time() - self.start_time, 6) self.res_asciinema.append( json.dumps([ delay, 'o', recv_message.decode('utf-8') ])) # 250条结果或者指定秒数就保存一次,这个任务可以优化为使用 celery if len(self.res_asciinema) > 2000 or int(time.time() - self.last_save_time) > 60 \ or sys.getsizeof(self.res_asciinema) > 2097152: tmp = list(self.res_asciinema) self.res_asciinema = [] self.last_save_time = time.time() res(self.res_file, tmp) try: data = recv_message.decode('utf-8') if self.enter: self.enter = False if not data.startswith( "\r\n" ): # 回车后结果不以\r\n开头的肯定不是命令 self.cmd_tmp = '' else: if re.match( rb'^\r\n\s+\x1b.*$', recv_message ): # 终端为 xterm,linux 等显示颜色类型时在 vi 编辑模式下回车 self.cmd_tmp = '' # elif x == b'\r\n': # todo 正常模式下 vi 文件会返回 \r\n ,终端为 dumb 类型时在 vi 编辑模式下回车也会返回 \r\n, # self.cmd_tmp = '' else: # 记录真正命令, rl 不支持中文命令 cmd_time = time.strftime( "%Y-%m-%d %H:%M:%S", time.localtime( int(time.time()))) cmd = self.rl.process_line( self.cmd_tmp.encode( "utf-8")) if not cmd: # 有可能 rl 库会返回 None,重试一次 mp_readline.TESTING = True self.rl = mp_readline.MpReadline( ) cmd = self.rl.process_line( self.cmd_tmp.encode( "utf-8")) if cmd: self.cmd += cmd_time + "\t" + remove_control_chars( cmd) + '\n' else: logger.error( "recv from server: {} \nerror command: {}" .format( recv_message, self.cmd_tmp. encode("utf-8"))) self.cmd += cmd_time + "\t" + remove_control_chars( self.cmd_tmp) + '\n' self.cmd_tmp = '' else: if self.tab_mode: # todo 兼容有问题 self.tab_mode = False tmp = data.split(' ') # tab 只返回一个命令时匹配 # print(tmp) if len(tmp) == 2 and tmp[ 1] == '' and tmp[0] != '': self.cmd_tmp = self.cmd_tmp + tmp[ 0].encode().replace( b'\x07', b'').decode() elif len(tmp ) == 1 and tmp[0].encode( ) != b'\x07': # \x07 蜂鸣声 self.cmd_tmp = self.cmd_tmp + tmp[ 0].encode().replace( b'\x07', b'').decode() # 多次上下箭头查找历史命令返回数据中可能会包含 \x1b[1P 导致 rl 无法解析命令,具体原因没有深究 if self.history_mode: self.history_mode = False if recv_message != b'' and recv_message != b'\x07': recv_message = re.sub( rb'\x1b\[\d+P', b'', recv_message) self.cmd_tmp += recv_message.decode( "utf-8") if self.ctrl_c: # 取消命令 self.ctrl_c = False # if x == b'^C\r\n': if re.match( rb'^\^C\r\n[\s\S]*$', recv_message) or re.match( rb'^\r\n[\s\S]*$', recv_message): self.cmd_tmp = "" if self.ctrl_z: self.ctrl_z = False if re.match( rb'^[\s\S]*\[\d+\]\+\s+Stopped\s+\S+[\s\S]*$', recv_message): self.cmd_tmp = "" except Exception: logger.error(traceback.format_exc()) except socket.timeout: logger.error(traceback.format_exc()) if key.fileobj == self.chan_cli: try: send_message = self.chan_cli.recv(BufferSize) if len(send_message) == 0: logger.info('客户端断开了连接 {}....'.format( self.client_addr)) # time.sleep(1) break else: if not self.lock: self.chan_ser.send(send_message) if not self.zmodem: try: data = send_message.decode('utf-8') if data == '\r': # 回车,开始根据服务端返回判断是否是命令,这种判断方式的特性就导致了无法是否禁止命令功能,当然想绝对禁止命令本身就是一个伪命题 if self.cmd_tmp.strip() != '': self.enter = True elif data.encode( ) == b'\x07': # 响铃 pass elif data == '\t' or data.encode( ) == b'\x1b': # \x1b 点击2下esc键也可以补全 self.tab_mode = True elif data.encode( ) == b'\x1b[A' or data.encode( ) == b'\x1b[B': self.history_mode = True elif data.encode( ) == b'\x03': # 输入命令后先 ctrl + v,然后 ctrl + c 需要两次才能取消 self.ctrl_c = True elif data.encode( ) == b'\x1a': # ctrl + z self.ctrl_z = True else: self.cmd_tmp += data except Exception: logger.error( traceback.format_exc()) else: # 红色提示文字 self.chan_cli.send( "\r\n\033[31m当前会话已被管理员锁定\033[0m\r\n") self.check_channel_window_change_request( self.chan_cli, self.width - 1, self.height, 0, 0) self.check_channel_window_change_request( self.chan_cli, self.width + 1, self.height, 0, 0) except socket.timeout: logger.error(traceback.format_exc()) except Exception: logger.error(traceback.format_exc()) break except socket.timeout: self.chan_cli.send( "\r\n\033[31m由于长时间没有操作或者没有数据返回,连接已断开!\033[0m\r\n") logger.info("后端主机 (%s@%s) 会话由于长时间没有操作或者没有数据返回,连接断开!" % (self.ssh_args[2], self.ssh_args[0])) except Exception: logger.error(traceback.format_exc())
def websocket_to_django(self): try: while 1: # time.sleep(0.00001) data = self.guacamoleclient.receive() if not data: message = str( base64.b64encode('连接被断开或者协议不支持'.encode('utf-8')), 'utf-8') self.websocker.send('6.toastr,1.2,{0}.{1};'.format( len(message), message)) break save_res = True if data.startswith("4.file,"): tmp = data.split(",") file_index = tmp[1].split(".")[1] file_type = tmp[2].split(".")[1] file_name_tmp = tmp[3].rstrip(";").split(".") del file_name_tmp[0] file_name = '.'.join(file_name_tmp) self.file_index[file_index] = [file_name, file_type] message = str( base64.b64encode( '开始下载文件 - {}'.format(file_name).encode('utf-8')), 'utf-8') self.websocker.send('6.toastr,1.3,{0}.{1};'.format( len(message), message)) save_res = False if self.file_index: if data.startswith("4.blob,"): tmp = data.split(",") index = tmp[1].split(".")[1] if index in self.file_index: # lenx = tmp[2].split(".")[0] # logger.info("file: {} len: {}".format(self.file_index[index][0], lenx)) save_res = False if data.startswith("3.end,"): tmp = data.split(",") index = tmp[1].rstrip(";").split(".")[1] if index in self.file_index: cmd_time = time.strftime( "%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))) self.file_cmd += cmd_time + "\t" + '下载文件 - {}'.format( self.file_index[index][0]) + '\n' message = str( base64.b64encode('文件下载完成 - {}'.format( self.file_index[index][0]).encode( 'utf-8')), 'utf-8') self.websocker.send('6.toastr,1.3,{0}.{1};'.format( len(message), message)) save_res = False del self.file_index[index] if self.websocker.send_flag == 0: self.websocker.send(data) elif self.websocker.send_flag == 1: async_to_sync(self.websocker.channel_layer.group_send)( self.websocker.group, { "type": "group.message", "text": data, }) if save_res: # 不保存下载文件时的数据到录像 self.res.append(data) # 指定条结果或者指定秒数就保存一次 if len(self.res) > 2000 or int(time.time() - self.last_save_time) > 60 or \ sys.getsizeof(self.res) > 2097152: tmp = list(self.res) self.res = [] self.last_save_time = time.time() res(self.res_file, tmp, False) except Exception: if self.websocker.send_flag == 0: self.websocker.send('0.;') elif self.websocker.send_flag == 1: async_to_sync(self.websocker.channel_layer.group_send)( self.websocker.group, { "type": "group.message", "text": '0.;', }) finally: self.close()