Unverified Commit 2444c6fd authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #151 from jumpserver/dev

Dev
parents f98e6815 1eb34b69
...@@ -94,7 +94,7 @@ class Config(dict): ...@@ -94,7 +94,7 @@ class Config(dict):
def __init__(self, root_path, defaults=None): def __init__(self, root_path, defaults=None):
self.defaults = defaults or {} self.defaults = defaults or {}
self.root_path = root_path self.root_path = root_path
super().__init__({}) super(Config, self).__init__({})
def from_envvar(self, variable_name, silent=False): def from_envvar(self, variable_name, silent=False):
"""Loads a configuration from an environment variable pointing to """Loads a configuration from an environment variable pointing to
...@@ -272,7 +272,7 @@ class Config(dict): ...@@ -272,7 +272,7 @@ class Config(dict):
def __getitem__(self, item): def __getitem__(self, item):
try: try:
value = super().__getitem__(item) value = super(Config, self).__getitem__(item)
except KeyError: except KeyError:
value = None value = None
if value is not None: if value is not None:
......
...@@ -4,9 +4,13 @@ ...@@ -4,9 +4,13 @@
import os import os
import re import re
import socket import socket
import selectors
import telnetlib import telnetlib
try:
import selectors
except ImportError:
import selectors2 as selectors
import paramiko import paramiko
from paramiko.ssh_exception import SSHException from paramiko.ssh_exception import SSHException
...@@ -54,7 +58,7 @@ class SSHConnection: ...@@ -54,7 +58,7 @@ class SSHConnection:
asset.ip, port=asset.port, username=system_user.username, asset.ip, port=asset.port, username=system_user.username,
password=system_user.password, pkey=system_user.private_key, password=system_user.password, pkey=system_user.private_key,
timeout=config['SSH_TIMEOUT'], timeout=config['SSH_TIMEOUT'],
compress=True, auth_timeout=config['SSH_TIMEOUT'], compress=False, auth_timeout=config['SSH_TIMEOUT'],
look_for_keys=False, sock=sock look_for_keys=False, sock=sock
) )
except paramiko.AuthenticationException: except paramiko.AuthenticationException:
...@@ -62,7 +66,7 @@ class SSHConnection: ...@@ -62,7 +66,7 @@ class SSHConnection:
ssh.connect( ssh.connect(
asset.ip, port=asset.port, username=system_user.username, asset.ip, port=asset.port, username=system_user.username,
password=system_user.password, timeout=config['SSH_TIMEOUT'], password=system_user.password, timeout=config['SSH_TIMEOUT'],
compress=True, auth_timeout=config['SSH_TIMEOUT'], compress=False, auth_timeout=config['SSH_TIMEOUT'],
look_for_keys=False, sock=sock, allow_agent=False, look_for_keys=False, sock=sock, allow_agent=False,
) )
transport = ssh.get_transport() transport = ssh.get_transport()
...@@ -86,7 +90,7 @@ class SSHConnection: ...@@ -86,7 +90,7 @@ class SSHConnection:
password_short, key_fingerprint, password_short, key_fingerprint,
)) ))
return None, None, error + '\n' + str(e) return None, None, error + '\n' + str(e)
except (socket.error, TimeoutError) as e: except (socket.error, socket.timeout) as e:
return None, None, error + '\n' + str(e) return None, None, error + '\n' + str(e)
return ssh, sock, None return ssh, sock, None
...@@ -197,7 +201,7 @@ class TelnetConnection: ...@@ -197,7 +201,7 @@ class TelnetConnection:
) )
def get_socket(self): def get_socket(self):
logger.info('Get telnet server socket. {}'.format(self.client.user)) logger.debug('Get telnet server socket. {}'.format(self.client.user))
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(10) self.sock.settimeout(10)
self.sock.connect((self.asset.ip, self.asset.port)) self.sock.connect((self.asset.ip, self.asset.port))
...@@ -211,7 +215,7 @@ class TelnetConnection: ...@@ -211,7 +215,7 @@ class TelnetConnection:
for sock in [key.fileobj for key, _ in events]: for sock in [key.fileobj for key, _ in events]:
data = sock.recv(BUF_SIZE) data = sock.recv(BUF_SIZE)
if sock == self.sock: if sock == self.sock:
logger.info(b'[Telnet server send]: ' + data) logger.debug(b'[Telnet server send]: ' + data)
if not data: if not data:
self.sock.close() self.sock.close()
...@@ -247,7 +251,7 @@ class TelnetConnection: ...@@ -247,7 +251,7 @@ class TelnetConnection:
:param data: option negotiate data :param data: option negotiate data
:return: :return:
""" """
logger.info(b'[Server options negotiate]: ' + data) logger.debug(b'[Server options negotiate]: ' + data)
data_list = data.split(telnetlib.IAC) data_list = data.split(telnetlib.IAC)
new_data_list = [] new_data_list = []
for x in data_list: for x in data_list:
...@@ -272,11 +276,11 @@ class TelnetConnection: ...@@ -272,11 +276,11 @@ class TelnetConnection:
else: else:
new_data_list.append(x) new_data_list.append(x)
new_data = telnetlib.IAC.join(new_data_list) new_data = telnetlib.IAC.join(new_data_list)
logger.info(b'[Client options negotiate]: ' + new_data) logger.debug(b'[Client options negotiate]: ' + new_data)
self.sock.send(new_data) self.sock.send(new_data)
def login_auth(self, raw_data): def login_auth(self, raw_data):
logger.info('[Telnet login auth]: ({})'.format(self.client.user)) logger.debug('[Telnet login auth]: ({})'.format(self.client.user))
try: try:
data = raw_data.decode('utf-8') data = raw_data.decode('utf-8')
...@@ -284,23 +288,23 @@ class TelnetConnection: ...@@ -284,23 +288,23 @@ class TelnetConnection:
try: try:
data = raw_data.decode('gbk') data = raw_data.decode('gbk')
except UnicodeDecodeError: except UnicodeDecodeError:
logger.info(b'[Decode error]: ' + b'>>' + raw_data + b'<<') logger.debug(b'[Decode error]: ' + b'>>' + raw_data + b'<<')
return None return None
if self.incorrect_pattern.search(data): if self.incorrect_pattern.search(data):
logger.info(b'[Login incorrect prompt]: ' + b'>>' + raw_data + b'<<') logger.debug(b'[Login incorrect prompt]: ' + b'>>' + raw_data + b'<<')
return False return False
elif self.username_pattern.search(data): elif self.username_pattern.search(data):
logger.info(b'[Username prompt]: ' + b'>>' + raw_data + b'<<') logger.debug(b'[Username prompt]: ' + b'>>' + raw_data + b'<<')
self.sock.send(self.system_user.username.encode('utf-8') + b'\r\n') self.sock.send(self.system_user.username.encode('utf-8') + b'\r\n')
return None return None
elif self.password_pattern.search(data): elif self.password_pattern.search(data):
logger.info(b'[Password prompt]: ' + b'>>' + raw_data + b'<<') logger.debug(b'[Password prompt]: ' + b'>>' + raw_data + b'<<')
self.sock.send(self.system_user.password.encode('utf-8') + b'\r\n') self.sock.send(self.system_user.password.encode('utf-8') + b'\r\n')
return None return None
elif self.success_pattern.search(data): elif self.success_pattern.search(data):
logger.info(b'[Login Success prompt]: ' + b'>>' + raw_data + b'<<') logger.debug(b'[Login Success prompt]: ' + b'>>' + raw_data + b'<<')
return True return True
else: else:
logger.info(b'[No match]: ' + b'>>' + raw_data + b'<<') logger.debug(b'[No match]: ' + b'>>' + raw_data + b'<<')
return None return None
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import os
from flask_socketio import SocketIO from flask_socketio import SocketIO
from flask import Flask from flask import Flask
...@@ -17,8 +18,10 @@ socket_io = SocketIO() ...@@ -17,8 +18,10 @@ socket_io = SocketIO()
socket_io.on_namespace(ProxyNamespace('/ssh')) socket_io.on_namespace(ProxyNamespace('/ssh'))
socket_io.on_namespace(ElfinderNamespace('/elfinder')) socket_io.on_namespace(ElfinderNamespace('/elfinder'))
# init_kwargs = {'async_mode': 'threading'} if os.environ.get('USE_EVENTLET', '1') == '1':
init_kwargs = {'async_mode': 'eventlet'} init_kwargs = {'async_mode': 'eventlet'}
else:
init_kwargs = {'async_mode': 'threading'}
socket_io.init_app(app, **init_kwargs), socket_io.init_app(app, **init_kwargs),
socket_io.on_error_default(lambda x: logger.exception(x)) socket_io.on_error_default(lambda x: logger.exception(x))
......
# -*- coding: utf-8 -*-
#
# -*- coding: utf-8 -*-
#
import base64 import base64
import os import os
import hashlib import hashlib
......
# -*- coding: utf-8 -*-
#
import stat import stat
import threading import threading
...@@ -14,9 +16,9 @@ class SFTPVolume(BaseVolume): ...@@ -14,9 +16,9 @@ class SFTPVolume(BaseVolume):
def __init__(self, sftp): def __init__(self, sftp):
self.sftp = sftp self.sftp = sftp
self.root_name = 'Home' self.root_name = 'Home'
super().__init__()
self._stat_cache = {} self._stat_cache = {}
self.lock = threading.Lock() self.lock = threading.Lock()
super(SFTPVolume, self).__init__()
def close(self): def close(self):
self.sftp.close() self.sftp.close()
......
...@@ -28,7 +28,7 @@ class ProxyNamespace(BaseNamespace): ...@@ -28,7 +28,7 @@ class ProxyNamespace(BaseNamespace):
... ...
} }
""" """
super().__init__(*args, **kwargs) super(BaseNamespace, self).__init__(*args, **kwargs)
self.win_size = None self.win_size = None
def new_connection(self): def new_connection(self):
...@@ -46,7 +46,7 @@ class ProxyNamespace(BaseNamespace): ...@@ -46,7 +46,7 @@ class ProxyNamespace(BaseNamespace):
def on_connect(self): def on_connect(self):
logger.debug("On connect event trigger") logger.debug("On connect event trigger")
self.get_current_user() self.get_current_user()
super().on_connect() super(ProxyNamespace, self).on_connect()
self.new_connection() self.new_connection()
def on_host(self, message): def on_host(self, message):
......
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from __future__ import unicode_literals
import socket import socket
import threading import threading
...@@ -28,20 +29,20 @@ PROXY = 'proxy' ...@@ -28,20 +29,20 @@ PROXY = 'proxy'
class InteractiveServer: class InteractiveServer:
_sentinel = object() _sentinel = object()
_user_assets_cached = {}
def __init__(self, client): def __init__(self, client):
self.client = client self.client = client
self.closed = False self.closed = False
self._results = None self._results = None
self.nodes = None self.nodes = None
self.offset = 0 self.assets = None
self.limit = 100 self.get_user_assets_finished = False
self.assets = []
self.finish = False
self.page = 1 self.page = 1
self.total_assets = 0 # 用户被授权的所有资产 self.total_asset_count = 0 # 用户被授权的所有资产数量
self.total_count = 0 # 分页展示中的资产总数量 self.total_count = 0 # 分页展示中的资产总数量
self.node_tree = None # 授权节点树 self.node_tree = None # 授权节点树
self.load_user_assets_from_cache()
self.get_user_assets_async() self.get_user_assets_async()
self.get_user_nodes_async() self.get_user_nodes_async()
...@@ -82,7 +83,7 @@ class InteractiveServer: ...@@ -82,7 +83,7 @@ class InteractiveServer:
self.client.send(char.CLEAR_CHAR) self.client.send(char.CLEAR_CHAR)
self.display_logo() self.display_logo()
header = _("\n{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system {end}{R}{R}") header = _("\n{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system {end}{R}{R}")
menus = [ menu = [
_("{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, Comment{end} to search login(if unique).{R}"), _("{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, Comment{end} to search login(if unique).{R}"),
_("{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} search, such as: /ip.{R}"), _("{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} search, such as: /ip.{R}"),
_("{T}3) Enter {green}p{end} to display the host you have permission.{R}"), _("{T}3) Enter {green}p{end} to display the host you have permission.{R}"),
...@@ -97,8 +98,8 @@ class InteractiveServer: ...@@ -97,8 +98,8 @@ class InteractiveServer:
title="\033[1;32m", user=self.client.user, end="\033[0m", title="\033[1;32m", user=self.client.user, end="\033[0m",
T='\t', R='\r\n\r' T='\t', R='\r\n\r'
)) ))
for menu in menus: for item in menu:
self.client.send(menu.format( self.client.send(item.format(
green="\033[32m", end="\033[0m", green="\033[32m", end="\033[0m",
T='\t', R='\r\n\r' T='\t', R='\r\n\r'
)) ))
...@@ -143,7 +144,7 @@ class InteractiveServer: ...@@ -143,7 +144,7 @@ class InteractiveServer:
def search_and_display_assets(self, q): def search_and_display_assets(self, q):
assets = self.search_assets(q) assets = self.search_assets(q)
self.display_assets(assets) self.display_assets_paging(assets)
def search_and_proxy_assets(self, opt): def search_and_proxy_assets(self, opt):
assets = self.search_assets(opt) assets = self.search_assets(opt)
...@@ -158,25 +159,13 @@ class InteractiveServer: ...@@ -158,25 +159,13 @@ class InteractiveServer:
return return
self.proxy(asset) self.proxy(asset)
else: else:
self.display_assets(assets) self.display_assets_paging(assets)
def refresh_assets_nodes(self): def refresh_assets_nodes(self):
self.get_user_assets_async() self.get_user_assets_async()
self.get_user_nodes_async() self.get_user_nodes_async()
def search_assets(self, q): def search_assets(self, q):
if self.finish:
assets = self.search_assets_from_local(q)
else:
assets = self.search_assets_from_core(q)
return assets
def search_assets_from_core(self, q):
assets = app_service.get_search_user_granted_assets(self.client.user, q)
assets = self.filter_system_users(assets)
return assets
def search_assets_from_local(self, q):
result = [] result = []
# 所有的 # 所有的
...@@ -204,20 +193,17 @@ class InteractiveServer: ...@@ -204,20 +193,17 @@ class InteractiveServer:
# Display assets # Display assets
# #
def display_assets(self, assets=None): def display_assets(self):
if assets is None: while self.assets is None and not self.get_user_assets_finished:
while not self.assets and not self.finish: time.sleep(0.5)
time.sleep(0.2) if self.assets:
assets = self.assets self.display_assets_paging(self.assets)
self.display_assets_paging(assets)
def display_assets_paging(self, assets): def display_assets_paging(self, assets):
if len(assets) == 0: if len(assets) == 0:
self.client.send(wr(_("No Assets"), before=0)) self.client.send(wr(_("No Assets"), before=0))
return return
self.total_count = len(assets)
self.total_count = self.total_assets if assets is self.assets else len(assets)
action = None action = None
gen = self._page_generator(assets) gen = self._page_generator(assets)
...@@ -238,14 +224,8 @@ class InteractiveServer: ...@@ -238,14 +224,8 @@ class InteractiveServer:
start, page = 0, 1 start, page = 0, 1
while True: while True:
_assets = assets[start:start+self.page_size] _assets = assets[start:start+self.page_size]
# 等待加载
if (assets is self.assets) and (not self.finish) and (not self.need_paging):
time.sleep(1)
continue
# 最后一页 # 最后一页
elif _assets and (page == self.total_pages) and ( if page == self.total_pages:
assets is not self.assets
or (assets is self.assets and self.finish)):
return page, _assets return page, _assets
# 执行动作 # 执行动作
else: else:
...@@ -273,33 +253,8 @@ class InteractiveServer: ...@@ -273,33 +253,8 @@ class InteractiveServer:
def display_a_page_assets(self, page, assets): def display_a_page_assets(self, page, assets):
self.client.send(char.CLEAR_CHAR) self.client.send(char.CLEAR_CHAR)
self.page = page self.page = page
self.results = assets
self.display_results()
def display_page_bottom_prompt(self):
self.client.send(wr(_('Tips: Enter the asset ID and log directly into the asset.'), before=1))
prompt_page_up = _("Page up: P/p")
prompt_page_down = _("Page down: Enter|N/n")
prompt_back = _("BACK: b/q")
prompts = [prompt_page_up, prompt_page_down, prompt_back]
prompt = '\t'.join(prompts)
self.client.send(wr(prompt, before=1))
def get_user_action(self):
opt = net_input(self.client, prompt=':')
if opt in ('p', 'P'):
return PAGE_UP
elif opt in ('b', 'q'):
return BACK
elif opt.isdigit() and self.results and 0 < int(opt) <= len(self.results):
self.proxy(self.results[int(opt)-1])
return BACK
else:
return PAGE_DOWN
def display_results(self):
sort_by = config["ASSET_LIST_SORT_BY"] sort_by = config["ASSET_LIST_SORT_BY"]
self.results = sort_assets(self.results, sort_by) self.results = sort_assets(assets, sort_by)
fake_data = [_("ID"), _("Hostname"), _("IP"), _("LoginAs")] fake_data = [_("ID"), _("Hostname"), _("IP"), _("LoginAs")]
id_length = max(len(str(len(self.results))), 4) id_length = max(len(str(len(self.results))), 4)
hostname_length = item_max_length(self.results, 15, hostname_length = item_max_length(self.results, 15,
...@@ -323,44 +278,59 @@ class InteractiveServer: ...@@ -323,44 +278,59 @@ class InteractiveServer:
] ]
self.client.send(wr(format_with_zh(size_list, *data))) self.client.send(wr(format_with_zh(size_list, *data)))
self.client.send(wr(title(_("Page: {}, Count: {}, Total Page: {}, Total Count: {}").format( self.client.send(wr(title(
self.page, len(self.results), self.total_pages, self.total_count)), before=1) _("Page: {}, Count: {}, Total Page: {}, Total Count: {}").format(
self.page, len(self.results), self.total_pages,
self.total_count)), before=1)
) )
def display_page_bottom_prompt(self):
msg = wr(_('Tips: Enter the asset ID and log directly into the asset.'), before=1)
self.client.send(msg)
prompt_page_up = _("Page up: P/p")
prompt_page_down = _("Page down: Enter|N/n")
prompt_back = _("BACK: b/q")
prompts = [prompt_page_up, prompt_page_down, prompt_back]
prompt = '\t'.join(prompts)
self.client.send(wr(prompt, before=1))
def get_user_action(self):
opt = net_input(self.client, prompt=':')
if opt in ('p', 'P'):
return PAGE_UP
elif opt in ('b', 'q'):
return BACK
elif opt.isdigit() and self.results and 0 < int(opt) <= len(self.results):
self.proxy(self.results[int(opt)-1])
return BACK
else:
return PAGE_DOWN
# #
# Get assets # Get assets
# #
def load_user_assets_from_cache(self):
assets = self.__class__._user_assets_cached.get(
self.client.user.id
)
self.assets = assets
if assets:
self.total_asset_count = len(assets)
def set_user_assets_cache(self, assets):
self.__class__._user_assets_cached[self.client.user.id] = assets
def get_user_assets_async(self): def get_user_assets_async(self):
if self.need_paging: thread = threading.Thread(target=self.get_user_assets)
thread = threading.Thread(target=self.get_user_assets_paging)
else:
thread = threading.Thread(target=self.get_user_assets_direct)
thread.start() thread.start()
def get_user_assets_direct(self): def get_user_assets(self):
assets = app_service.get_user_assets(self.client.user) assets = app_service.get_user_assets(self.client.user)
assets = self.filter_system_users(assets) assets = self.filter_system_users(assets)
self.assets = assets self.set_user_assets_cache(assets)
self.total_assets = len(assets) self.load_user_assets_from_cache()
self.finish = True self.get_user_assets_finished = True
def get_user_assets_paging(self):
while not self.closed:
assets, total = app_service.get_user_assets_paging(
self.client.user, offset=self.offset, limit=self.limit
)
if not assets:
logger.info('Get user assets paging async finished.')
self.finish = True
break
logger.info('Get user assets paging async: {}'.format(len(assets)))
assets = self.filter_system_users(assets)
self.total_assets = total
self.assets.extend(assets)
self.offset += self.limit
# #
# Nodes # Nodes
# #
...@@ -415,7 +385,7 @@ class InteractiveServer: ...@@ -415,7 +385,7 @@ class InteractiveServer:
return return
assets = self.nodes[_id-1].assets_granted assets = self.nodes[_id-1].assets_granted
self.display_assets(assets) self.display_assets_paging(assets)
# #
# System users # System users
...@@ -487,7 +457,6 @@ class InteractiveServer: ...@@ -487,7 +457,6 @@ class InteractiveServer:
def close(self): def close(self):
logger.debug("Interactive server server close: {}".format(self)) logger.debug("Interactive server server close: {}".format(self))
self.closed = True self.closed = True
# current_app.remove_client(self.client)
def interact_async(self): def interact_async(self):
# 目前没用 # 目前没用
......
...@@ -30,14 +30,14 @@ class SSHInterface(paramiko.ServerInterface): ...@@ -30,14 +30,14 @@ class SSHInterface(paramiko.ServerInterface):
self.user = None self.user = None
def check_auth_interactive(self, username, submethods): def check_auth_interactive(self, username, submethods):
logger.info("Check auth interactive: %s %s" % (username, submethods)) logger.debug("Check auth interactive: %s %s" % (username, submethods))
instructions = 'Please enter 6 digits.' instructions = 'Please enter 6 digits.'
interactive = paramiko.server.InteractiveQuery(instructions=instructions) interactive = paramiko.server.InteractiveQuery(instructions=instructions)
interactive.add_prompt(prompt='[MFA auth]: ') interactive.add_prompt(prompt='[MFA auth]: ')
return interactive return interactive
def check_auth_interactive_response(self, responses): def check_auth_interactive_response(self, responses):
logger.info("Check auth interactive response: %s " % responses) logger.debug("Check auth interactive response: %s " % responses)
# TODO:MFA Auth # TODO:MFA Auth
otp_code = responses[0] otp_code = responses[0]
if not otp_code or not len(otp_code) == 6 or not otp_code.isdigit(): if not otp_code or not len(otp_code) == 6 or not otp_code.isdigit():
...@@ -90,7 +90,7 @@ class SSHInterface(paramiko.ServerInterface): ...@@ -90,7 +90,7 @@ class SSHInterface(paramiko.ServerInterface):
logger.debug("Public key auth <%s> failed, try to password" % username) logger.debug("Public key auth <%s> failed, try to password" % username)
return paramiko.AUTH_FAILED return paramiko.AUTH_FAILED
else: else:
logger.debug("Public key auth <%s> success" % username) logger.info("Public key auth <%s> success" % username)
if self.otp_auth: if self.otp_auth:
return paramiko.AUTH_PARTIALLY_SUCCESSFUL return paramiko.AUTH_PARTIALLY_SUCCESSFUL
return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_SUCCESSFUL
...@@ -149,14 +149,14 @@ class SSHInterface(paramiko.ServerInterface): ...@@ -149,14 +149,14 @@ class SSHInterface(paramiko.ServerInterface):
return 0 return 0
def check_port_forward_request(self, address, port): def check_port_forward_request(self, address, port):
logger.info( logger.debug(
"Check channel port forward request: %s %s" % (address, port) "Check channel port forward request: %s %s" % (address, port)
) )
self.event.set() self.event.set()
return False return False
def check_channel_request(self, kind, chan_id): def check_channel_request(self, kind, chan_id):
logger.info("Check channel request: %s %d" % (kind, chan_id)) logger.debug("Check channel request: %s %d" % (kind, chan_id))
client = self.connection.new_client(chan_id) client = self.connection.new_client(chan_id)
client.request.kind = kind client.request.kind = kind
return paramiko.OPEN_SUCCEEDED return paramiko.OPEN_SUCCEEDED
...@@ -188,7 +188,7 @@ class SSHInterface(paramiko.ServerInterface): ...@@ -188,7 +188,7 @@ class SSHInterface(paramiko.ServerInterface):
def check_channel_pty_request( def check_channel_pty_request(
self, channel, term, width, height, self, channel, term, width, height,
pixelwidth, pixelheight, modes): pixelwidth, pixelheight, modes):
logger.info("Check channel pty request: %s %s %s %s %s" % logger.debug("Check channel pty request: %s %s %s %s %s" %
(term, width, height, pixelwidth, pixelheight)) (term, width, height, pixelwidth, pixelheight))
client = self.connection.get_client(channel) client = self.connection.get_client(channel)
client.request.type = 'pty' client.request.type = 'pty'
...@@ -201,18 +201,18 @@ class SSHInterface(paramiko.ServerInterface): ...@@ -201,18 +201,18 @@ class SSHInterface(paramiko.ServerInterface):
return True return True
def check_channel_shell_request(self, channel): def check_channel_shell_request(self, channel):
logger.info("Check channel shell request: %s" % channel.get_id()) logger.debug("Check channel shell request: %s" % channel.get_id())
client = self.connection.get_client(channel) client = self.connection.get_client(channel)
client.request.meta['shell'] = True client.request.meta['shell'] = True
return True return True
def check_channel_subsystem_request(self, channel, name): def check_channel_subsystem_request(self, channel, name):
logger.info("Check channel subsystem request: %s" % name) logger.debug("Check channel subsystem request: %s" % name)
client = self.connection.get_client(channel) client = self.connection.get_client(channel)
client.request.type = 'subsystem' client.request.type = 'subsystem'
client.request.meta['subsystem'] = name client.request.meta['subsystem'] = name
self.event.set() self.event.set()
return super().check_channel_subsystem_request(channel, name) return super(SSHInterface, self).check_channel_subsystem_request(channel, name)
def check_channel_window_change_request(self, channel, width, height, def check_channel_window_change_request(self, channel, width, height,
pixelwidth, pixelheight): pixelwidth, pixelheight):
...@@ -228,7 +228,7 @@ class SSHInterface(paramiko.ServerInterface): ...@@ -228,7 +228,7 @@ class SSHInterface(paramiko.ServerInterface):
def check_channel_x11_request(self, channel, single_connection, def check_channel_x11_request(self, channel, single_connection,
auth_protocol, auth_cookie, screen_number): auth_protocol, auth_cookie, screen_number):
logger.info("Check channel x11 request %s %s %s %s" % logger.debug("Check channel x11 request %s %s %s %s" %
(single_connection, auth_protocol, (single_connection, auth_protocol,
auth_cookie, screen_number)) auth_cookie, screen_number))
client = self.connection.get_client(channel) client = self.connection.get_client(channel)
......
...@@ -51,12 +51,13 @@ def create_logger(): ...@@ -51,12 +51,13 @@ def create_logger():
}, },
loggers={ loggers={
'coco': main_setting, 'coco': main_setting,
'paramiko': main_setting,
'jms': main_setting, 'jms': main_setting,
# 'socket.io': main_setting, # 'socket.io': main_setting,
# 'engineio': main_setting, # 'engineio': main_setting,
} }
) )
if level.lower() == 'debug':
config['loggers']['paramiko'] = main_setting
dictConfig(config) dictConfig(config)
logger = logging.getLogger() logger = logging.getLogger()
return logger return logger
......
...@@ -8,14 +8,14 @@ from .service import app_service ...@@ -8,14 +8,14 @@ from .service import app_service
from .struct import SizedList, SelectEvent from .struct import SizedList, SelectEvent
from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \ from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \
ugettext as _ ugettext as _
from . import char from . import char, utils
from . import utils from .compat import str
BUF_SIZE = 4096 BUF_SIZE = 4096
logger = utils.get_logger(__file__) logger = utils.get_logger(__file__)
class Connection: class Connection(object):
connections = {} connections = {}
clients_num = 0 clients_num = 0
...@@ -80,6 +80,8 @@ class Connection: ...@@ -80,6 +80,8 @@ class Connection:
@classmethod @classmethod
def remove_connection(cls, cid): def remove_connection(cls, cid):
connection = cls.get_connection(cid) connection = cls.get_connection(cid)
if not connection:
return
connection.close() connection.close()
del cls.connections[cid] del cls.connections[cid]
...@@ -88,7 +90,7 @@ class Connection: ...@@ -88,7 +90,7 @@ class Connection:
return cls.connections.get(cid) return cls.connections.get(cid)
class Request: class Request(object):
def __init__(self): def __init__(self):
self.type = None self.type = None
self.x11 = None self.x11 = None
...@@ -96,7 +98,7 @@ class Request: ...@@ -96,7 +98,7 @@ class Request:
self.meta = {'env': {}} self.meta = {'env': {}}
class Client: class Client(object):
""" """
Client is the request client. Nothing more to say Client is the request client. Nothing more to say
...@@ -129,12 +131,17 @@ class Client: ...@@ -129,12 +131,17 @@ class Client:
self.close() self.close()
return return
@property
def closed(self):
return self.chan.closed
def recv(self, size): def recv(self, size):
return self.chan.recv(size) return self.chan.recv(size)
def close(self): def close(self):
logger.info("Client {} close".format(self)) logger.info("Client {} close".format(self))
return self.chan.close() self.chan.close()
return
def __getattr__(self, item): def __getattr__(self, item):
return getattr(self.chan, item) return getattr(self.chan, item)
...@@ -143,12 +150,12 @@ class Client: ...@@ -143,12 +150,12 @@ class Client:
return "<%s from %s:%s>" % (self.user, self.addr[0], self.addr[1]) return "<%s from %s:%s>" % (self.user, self.addr[0], self.addr[1])
class ServerFilter: class ServerFilter(object):
def run(self, data): def run(self, data):
pass pass
class BaseServer: class BaseServer(object):
""" """
Base Server Base Server
Achieve command record Achieve command record
...@@ -238,20 +245,29 @@ class BaseServer: ...@@ -238,20 +245,29 @@ class BaseServer:
return data return data
if not self._input: if not self._input:
return data return data
if self._cmd_filter_rules is None:
msg = _("Warning: Failed to load filter rule, "
"please press Ctrl + D to exit retry.")
data = self.command_forbidden(msg)
return data
for rule in self._cmd_filter_rules: for rule in self._cmd_filter_rules:
action, cmd = rule.match(self._input) action, cmd = rule.match(self._input)
if action == rule.ALLOW: if action == rule.ALLOW:
break break
elif action == rule.DENY: elif action == rule.DENY:
data = char.CLEAR_LINE_CHAR + b'\r'
msg = _("Command `{}` is forbidden ........").format(cmd) msg = _("Command `{}` is forbidden ........").format(cmd)
self.command_forbidden(msg)
break
return data
def command_forbidden(self, msg):
data = char.CLEAR_LINE_CHAR + b'\r'
msg = wr(warning(msg.encode()), before=1, after=1) msg = wr(warning(msg.encode()), before=1, after=1)
self.output_data.append(msg) self.output_data.append(msg)
self.session.send_to_clients(msg) self.session.send_to_clients(msg)
self.session.put_command(self._input, msg.decode()) self.session.put_command(self._input, msg.decode())
self.session.put_replay(msg) self.session.put_replay(msg)
self.input_data.clean() self.input_data.clean()
break
return data return data
def r_replay_filter(self, data): def r_replay_filter(self, data):
...@@ -382,13 +398,14 @@ class Server(BaseServer): ...@@ -382,13 +398,14 @@ class Server(BaseServer):
super(Server, self).__init__(chan=chan) super(Server, self).__init__(chan=chan)
def close(self): def close(self):
super().close() super(Server, self).close()
self.chan.transport.close() self.chan.transport.close()
logger.debug("Backend server closed")
if self.sock: if self.sock:
self.sock.transport.close() self.sock.transport.close()
class WSProxy: class WSProxy(object):
def __init__(self, ws, client_id): def __init__(self, ws, client_id):
self.ws = ws self.ws = ws
self.client_id = client_id self.client_id = client_id
......
...@@ -52,7 +52,7 @@ class ProxyServer: ...@@ -52,7 +52,7 @@ class ProxyServer:
return False return False
return True return True
def manual_set_system_user_username_if_need(self): def get_system_user_username_if_need(self):
if self.system_user.login_mode == MANUAL_LOGIN and \ if self.system_user.login_mode == MANUAL_LOGIN and \
not self.system_user.username: not self.system_user.username:
username = net_input(self.client, prompt='username: ', before=1) username = net_input(self.client, prompt='username: ', before=1)
...@@ -63,13 +63,15 @@ class ProxyServer: ...@@ -63,13 +63,15 @@ class ProxyServer:
def proxy(self): def proxy(self):
if not self.check_protocol(): if not self.check_protocol():
return return
self.manual_set_system_user_username_if_need() self.get_system_user_username_if_need()
self.get_system_user_auth_or_manual_set() self.get_system_user_auth_or_manual_set()
self.server = self.get_server_conn() self.server = self.get_server_conn()
if self.server is None: if self.server is None:
return return
session = Session.new_session(self.client, self.server) session = Session.new_session(self.client, self.server)
try:
session.bridge() session.bridge()
finally:
Session.remove_session(session.id) Session.remove_session(session.id)
self.server.close() self.server.close()
...@@ -83,7 +85,7 @@ class ProxyServer: ...@@ -83,7 +85,7 @@ class ProxyServer:
) )
def get_server_conn(self): def get_server_conn(self):
logger.info("Connect to {}".format(self.asset.hostname)) logger.info("Connect to {}:{} ...".format(self.asset.hostname, self.asset.port))
self.send_connecting_message() self.send_connecting_message()
if not self.validate_permission(): if not self.validate_permission():
self.client.send(warning(_('No permission'))) self.client.send(warning(_('No permission')))
......
...@@ -21,12 +21,12 @@ logger = get_logger(__file__) ...@@ -21,12 +21,12 @@ logger = get_logger(__file__)
BUF_SIZE = 1024 BUF_SIZE = 1024
class ReplayRecorder(metaclass=abc.ABCMeta): class ReplayRecorder(object):
time_start = None time_start = None
storage = None storage = None
def __init__(self): def __init__(self):
super().__init__() super(ReplayRecorder, self).__init__()
self.file = None self.file = None
self.file_path = None self.file_path = None
self.get_storage() self.get_storage()
...@@ -82,7 +82,7 @@ class ReplayRecorder(metaclass=abc.ABCMeta): ...@@ -82,7 +82,7 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
self.upload_replay(session_id, times-1) self.upload_replay(session_id, times-1)
else: else:
msg = 'Success push replay file: {}'.format(session_id) msg = 'Success push replay file: {}'.format(session_id)
logger.info(msg) logger.debug(msg)
self.finish_replay(3, session_id) self.finish_replay(3, session_id)
os.unlink(self.file_path) os.unlink(self.file_path)
return True return True
...@@ -101,23 +101,32 @@ class ReplayRecorder(metaclass=abc.ABCMeta): ...@@ -101,23 +101,32 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
if app_service.finish_replay(session_id): if app_service.finish_replay(session_id):
logger.info( logger.info(
"Success finish session {}'s replay ".format(session_id) "Success finished session {}'s replay ".format(session_id)
) )
return True return True
else: else:
msg = "Failed finish session {}'s replay, try {} times" msg = "Failed finished session {}'s replay, try {} times"
logger.error(msg.format(session_id, times)) logger.error(msg.format(session_id, times))
return self.finish_replay(times - 1, session_id) return self.finish_replay(times - 1, session_id)
class CommandRecorder(metaclass=Singleton): class CommandRecorder(object):
batch_size = 10 batch_size = 10
timeout = 5 timeout = 5
no = 0 no = 0
storage = None storage = None
_cache = []
def __new__(cls, *args, **kwargs):
if cls._cache:
return cls._cache[0]
else:
self = super(CommandRecorder, cls).__new__(cls, *args, **kwargs)
cls._cache.append(self)
return self
def __init__(self): def __init__(self):
super().__init__() super(CommandRecorder, self).__init__()
self.queue = MemoryQueue() self.queue = MemoryQueue()
self.stop_evt = threading.Event() self.stop_evt = threading.Event()
self.push_to_server_async() self.push_to_server_async()
......
...@@ -3,9 +3,13 @@ ...@@ -3,9 +3,13 @@
# #
import uuid import uuid
import datetime import datetime
import selectors
import time import time
try:
import selectors
except ImportError:
import selectors2 as selectors
from .utils import get_logger, wrap_with_warning as warn, \ from .utils import get_logger, wrap_with_warning as warn, \
wrap_with_line_feed as wr, ugettext as _, ignore_error wrap_with_line_feed as wr, ugettext as _, ignore_error
from .service import app_service from .service import app_service
...@@ -68,14 +72,14 @@ class Session: ...@@ -68,14 +72,14 @@ class Session:
:param silent: If true not send welcome message :param silent: If true not send welcome message
:return: :return:
""" """
logger.info("Session add watcher: {} -> {} ".format(self.id, watcher)) logger.debug("Session add watcher: {} -> {} ".format(self.id, watcher))
if not silent: if not silent:
watcher.send("Welcome to watch session {}\r\n".format(self.id).encode()) watcher.send("Welcome to watch session {}\r\n".format(self.id).encode())
self.sel.register(watcher, selectors.EVENT_READ) self.sel.register(watcher, selectors.EVENT_READ)
self._watchers.append(watcher) self._watchers.append(watcher)
def remove_watcher(self, watcher): def remove_watcher(self, watcher):
logger.info("Session %s remove watcher %s" % (self.id, watcher)) logger.debug("Session %s remove watcher %s" % (self.id, watcher))
self.sel.unregister(watcher) self.sel.unregister(watcher)
self._watchers.remove(watcher) self._watchers.remove(watcher)
...@@ -86,7 +90,7 @@ class Session: ...@@ -86,7 +90,7 @@ class Session:
:param silent: If true not send welcome message :param silent: If true not send welcome message
:return: :return:
""" """
logger.info("Session %s add share %s" % (self.id, sharer)) logger.debug("Session %s add share %s" % (self.id, sharer))
if not silent: if not silent:
sharer.send("Welcome to join session: {}\r\n" sharer.send("Welcome to join session: {}\r\n"
.format(self.id).encode("utf-8")) .format(self.id).encode("utf-8"))
...@@ -190,7 +194,7 @@ class Session: ...@@ -190,7 +194,7 @@ class Session:
break break
elif sock == self.client.change_size_evt: elif sock == self.client.change_size_evt:
self.resize_win_size() self.resize_win_size()
logger.info("Session stop event set: {}".format(self.id)) logger.debug("Session stop event set: {}".format(self.id))
def resize_win_size(self): def resize_win_size(self):
width, height = self.client.request.meta['width'], \ width, height = self.client.request.meta['width'], \
...@@ -201,7 +205,7 @@ class Session: ...@@ -201,7 +205,7 @@ class Session:
@ignore_error @ignore_error
def close(self): def close(self):
if self.closed: if self.closed:
logger.info("Session has been closed: {} ".format(self.id)) logger.debug("Session has been closed: {} ".format(self.id))
return return
logger.info("Close the session: {} ".format(self.id)) logger.info("Close the session: {} ".format(self.id))
self.is_finished = True self.is_finished = True
...@@ -228,6 +232,3 @@ class Session: ...@@ -228,6 +232,3 @@ class Session:
def __repr__(self): def __repr__(self):
return self.id return self.id
# def __del__(self):
# print("GC: Session object has been GC")
...@@ -62,10 +62,11 @@ class SFTPServer(paramiko.SFTPServerInterface): ...@@ -62,10 +62,11 @@ class SFTPServer(paramiko.SFTPServerInterface):
} }
""" """
super().__init__(server, **kwargs) super(SFTPServer, self).__init__(server, **kwargs)
self.server = server self.server = server
self._sftp = {} self._sftp = {}
self.hosts = self.get_permed_hosts() self.hosts = self.get_permed_hosts()
self.is_finished = False
def get_permed_hosts(self): def get_permed_hosts(self):
hosts = {} hosts = {}
...@@ -88,7 +89,8 @@ class SFTPServer(paramiko.SFTPServerInterface): ...@@ -88,7 +89,8 @@ class SFTPServer(paramiko.SFTPServerInterface):
return hosts return hosts
def session_ended(self): def session_ended(self):
super().session_ended() self.is_finished = True
super(SFTPServer, self).session_ended()
for _, v in self._sftp.items(): for _, v in self._sftp.items():
sftp = v['client'] sftp = v['client']
proxy = v.get('proxy') proxy = v.get('proxy')
...@@ -144,7 +146,7 @@ class SFTPServer(paramiko.SFTPServerInterface): ...@@ -144,7 +146,7 @@ class SFTPServer(paramiko.SFTPServerInterface):
if len(data) == 1 and not data[0]: if len(data) == 1 and not data[0]:
return request return request
host, *path = data host, path = data[0], data[1:]
request["host"] = host request["host"] = host
unique, su = self.host_has_unique_su(host) unique, su = self.host_has_unique_su(host)
if unique: if unique:
...@@ -388,7 +390,7 @@ class InternalSFTPClient(SFTPServer): ...@@ -388,7 +390,7 @@ class InternalSFTPClient(SFTPServer):
def __init__(self, connection): def __init__(self, connection):
fake_server = FakeServer() fake_server = FakeServer()
fake_server.connection = connection fake_server.connection = connection
super().__init__(fake_server) super(InternalSFTPClient, self).__init__(fake_server)
def listdir_attr(self, path): def listdir_attr(self, path):
return self.list_folder.__wrapped__(self, path) return self.list_folder.__wrapped__(self, path)
...@@ -408,15 +410,15 @@ class InternalSFTPClient(SFTPServer): ...@@ -408,15 +410,15 @@ class InternalSFTPClient(SFTPServer):
self.create_ftp_log(path, operate, success) self.create_ftp_log(path, operate, success)
def stat(self, path): def stat(self, path):
attr = super().stat.__wrapped__(self, path) attr = super(InternalSFTPClient, self).stat.__wrapped__(self, path)
return attr return attr
def lstat(self, path): def lstat(self, path):
attr = super().lstat.__wrapped__(self, path) attr = super(InternalSFTPClient, self).lstat.__wrapped__(self, path)
return attr return attr
def rmdir(self, path): def rmdir(self, path):
return super().rmdir.__wrapped__(self, path) return super(InternalSFTPClient, self).rmdir.__wrapped__(self, path)
def get_channel(self): def get_channel(self):
return FakeChannel.new() return FakeChannel.new()
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import os import os
import socket import socket
import threading import threading
import time
import paramiko import paramiko
...@@ -25,7 +26,6 @@ class SSHServer: ...@@ -25,7 +26,6 @@ class SSHServer:
self.stop_evt = threading.Event() self.stop_evt = threading.Event()
self.workers = [] self.workers = []
self.pipe = None self.pipe = None
self.connections = []
@property @property
def host_key(self): def host_key(self):
...@@ -57,11 +57,6 @@ class SSHServer: ...@@ -57,11 +57,6 @@ class SSHServer:
except IndexError as e: except IndexError as e:
logger.error("Start SSH server error: {}".format(e)) logger.error("Start SSH server error: {}".format(e))
def new_connection(self, addr, sock):
connection = Connection.new_connection(addr=addr, sock=sock)
self.connections.append(connection)
return connection
def handle_connection(self, sock, addr): def handle_connection(self, sock, addr):
logger.debug("Handle new connection from: {}".format(addr)) logger.debug("Handle new connection from: {}".format(addr))
transport = paramiko.Transport(sock, gss_kex=False) transport = paramiko.Transport(sock, gss_kex=False)
...@@ -74,16 +69,10 @@ class SSHServer: ...@@ -74,16 +69,10 @@ class SSHServer:
transport.set_subsystem_handler( transport.set_subsystem_handler(
'sftp', paramiko.SFTPServer, SFTPServer 'sftp', paramiko.SFTPServer, SFTPServer
) )
connection = self.new_connection(addr, sock=sock) connection = Connection.new_connection(addr=addr, sock=sock)
server = SSHInterface(connection) server = SSHInterface(connection)
try: try:
transport.start_server(server=server) transport.start_server(server=server)
except (paramiko.SSHException, socket.timeout):
logger.warning("SSH negotiation failed")
return
except EOFError as e:
logger.warning("Handle EOF Error: {}".format(e))
return
while transport.is_active(): while transport.is_active():
chan = transport.accept() chan = transport.accept()
server.event.wait(5) server.event.wait(5)
...@@ -93,7 +82,7 @@ class SSHServer: ...@@ -93,7 +82,7 @@ class SSHServer:
if not server.event.is_set(): if not server.event.is_set():
logger.warning("Client not request invalid, exiting") logger.warning("Client not request invalid, exiting")
sock.close() sock.close()
return continue
else: else:
server.event.clear() server.event.clear()
...@@ -102,27 +91,38 @@ class SSHServer: ...@@ -102,27 +91,38 @@ class SSHServer:
t = threading.Thread(target=self.dispatch, args=(client,)) t = threading.Thread(target=self.dispatch, args=(client,))
t.daemon = True t.daemon = True
t.start() t.start()
transport.close()
except (paramiko.SSHException, sock.timeout):
logger.warning("SSH negotiation failed")
except EOFError as e:
logger.warning("Handle EOF Error: {}".format(e))
finally:
Connection.remove_connection(connection.id) Connection.remove_connection(connection.id)
sock.close()
@staticmethod @staticmethod
def dispatch(client): def dispatch(client):
supported = {'pty', 'x11', 'forward-agent'} supported = {'pty', 'x11', 'forward-agent'}
chan_type = client.request.type chan_type = client.request.type
kind = client.request.kind kind = client.request.kind
try:
if kind == 'session' and chan_type in supported: if kind == 'session' and chan_type in supported:
logger.info("Request type `{}:{}`, dispatch to interactive mode".format(kind, chan_type)) logger.info("Dispatch client to interactive mode")
try: try:
InteractiveServer(client).interact() InteractiveServer(client).interact()
except Exception as e: except IndexError as e:
logger.error("Unexpected error occur: {}".format(e)) logger.error("Unexpected error occur: {}".format(e))
connection = Connection.get_connection(client.connection_id)
connection.remove_client(client.id)
elif chan_type == 'subsystem': elif chan_type == 'subsystem':
pass while not client.closed:
time.sleep(5)
logger.debug("SFTP session finished")
else: else:
msg = "Request type `{}:{}` not support now".format(kind, chan_type) msg = "Request type `{}:{}` not support now".format(kind, chan_type)
logger.info(msg) logger.error(msg)
client.send(msg) client.send(msg)
finally:
connection = Connection.get_connection(client.connection_id)
connection.remove_client(client.id)
def shutdown(self): def shutdown(self):
self.stop_evt.set() self.stop_evt.set()
......
...@@ -21,7 +21,7 @@ class MultiQueueMixin: ...@@ -21,7 +21,7 @@ class MultiQueueMixin:
self.put(i) self.put(i)
class MemoryQueue(MultiQueueMixin, queue.Queue): class MemoryQueue(MultiQueueMixin, queue.Queue, object):
pass pass
...@@ -29,11 +29,11 @@ class SizedList(list): ...@@ -29,11 +29,11 @@ class SizedList(list):
def __init__(self, maxsize=0): def __init__(self, maxsize=0):
self.maxsize = maxsize self.maxsize = maxsize
self.size = 0 self.size = 0
super().__init__() super(list, self).__init__()
def append(self, b): def append(self, b):
if self.maxsize == 0 or self.size < self.maxsize: if self.maxsize == 0 or self.size < self.maxsize:
super().append(b) super(SizedList, self).append(b)
self.size += len(b) self.size += len(b)
def clean(self): def clean(self):
......
...@@ -12,7 +12,6 @@ from io import StringIO ...@@ -12,7 +12,6 @@ from io import StringIO
from binascii import hexlify from binascii import hexlify
from werkzeug.local import Local, LocalProxy from werkzeug.local import Local, LocalProxy
from functools import partial, wraps from functools import partial, wraps
import builtins
import paramiko import paramiko
import pyte import pyte
...@@ -294,7 +293,7 @@ def get_logger(file_name): ...@@ -294,7 +293,7 @@ def get_logger(file_name):
return logging.getLogger('coco.'+file_name) return logging.getLogger('coco.'+file_name)
def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_one_char=False): def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0):
"""实现了一个ssh input, 提示用户输入, 获取并返回 """实现了一个ssh input, 提示用户输入, 获取并返回
:return user input string :return user input string
...@@ -303,12 +302,8 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_o ...@@ -303,12 +302,8 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_o
parser = TtyIOParser() parser = TtyIOParser()
client.send(wrap_with_line_feed(prompt, before=before, after=after)) client.send(wrap_with_line_feed(prompt, before=before, after=after))
if only_one_char:
data = client.recv(1)
return data.decode()
while True: while True:
data = client.recv(10) data = client.recv(1)
if len(data) == 0: if len(data) == 0:
break break
# Client input backspace # Client input backspace
...@@ -336,18 +331,8 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_o ...@@ -336,18 +331,8 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_o
client.send(b'') client.send(b'')
continue continue
# handle shell expect
multi_char_with_enter = False
if len(data) > 1 and data[-1] in char.ENTER_CHAR_ORDER:
if sensitive:
client.send(len(data) * '*')
else:
client.send(data)
input_data.append(data[:-1])
multi_char_with_enter = True
# If user types ENTER we should get user input # If user types ENTER we should get user input
if data in char.ENTER_CHAR or multi_char_with_enter: if data in char.ENTER_CHAR:
client.send(wrap_with_line_feed(b'', after=2)) client.send(wrap_with_line_feed(b'', after=2))
option = parser.parse_input(input_data) option = parser.parse_input(input_data)
del input_data[:] del input_data[:]
...@@ -435,11 +420,17 @@ def get_current_lang(attr): ...@@ -435,11 +420,17 @@ def get_current_lang(attr):
def _gettext(lang): def _gettext(lang):
import builtins
if lang == 'en': if lang == 'en':
trans_en.install() trans_en.install()
else: else:
trans_zh.install() trans_zh.install()
try:
return builtins.__dict__['_'] return builtins.__dict__['_']
except KeyError:
def _f(x):
return x
return _f
def _find(attr): def _find(attr):
......
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import os
import eventlet if os.environ.get('USE_EVENTLET', '1') == '1':
from eventlet.debug import hub_prevent_multiple_readers import eventlet
eventlet.monkey_patch() from eventlet.debug import hub_prevent_multiple_readers
hub_prevent_multiple_readers(False) eventlet.monkey_patch()
hub_prevent_multiple_readers(False)
print("Use eventlet dispatch")
else:
print("Use local threading")
import os
import sys import sys
import argparse import argparse
import time import time
...@@ -15,7 +19,8 @@ import signal ...@@ -15,7 +19,8 @@ import signal
dirs = ('logs', 'keys') dirs = ('logs', 'keys')
for d in dirs: for d in dirs:
os.makedirs(d, exist_ok=True) if not os.path.isdir(d):
os.makedirs(d)
from coco import Coco from coco import Coco
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-18 20:03+0800\n" "POT-Creation-Date: 2018-12-21 16:48+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n" "PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n" "Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/en/LC\n" "Language-Team: Language locale/en/LC\n"
...@@ -84,75 +84,80 @@ msgstr "" ...@@ -84,75 +84,80 @@ msgstr ""
msgid "Terminal does not support login rdp, please use web terminal to access" msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr "" msgstr ""
#: coco/interactive.py:212 #: coco/interactive.py:217
msgid "No Assets" msgid "No Assets"
msgstr "" msgstr ""
#: coco/interactive.py:275 #: coco/interactive.py:280
msgid "Tips: Enter the asset ID and log directly into the asset." msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "" msgstr ""
#: coco/interactive.py:276 #: coco/interactive.py:281
msgid "Page up: P/p" msgid "Page up: P/p"
msgstr "" msgstr ""
#: coco/interactive.py:277 #: coco/interactive.py:282
msgid "Page down: Enter|N/n" msgid "Page down: Enter|N/n"
msgstr "" msgstr ""
#: coco/interactive.py:278 #: coco/interactive.py:283
msgid "BACK: b/q" msgid "BACK: b/q"
msgstr "" msgstr ""
#: coco/interactive.py:299 #: coco/interactive.py:303
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: coco/interactive.py:299 #: coco/interactive.py:303
msgid "Hostname" msgid "Hostname"
msgstr "" msgstr ""
#: coco/interactive.py:299 #: coco/interactive.py:303
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#: coco/interactive.py:299 #: coco/interactive.py:303
msgid "LoginAs" msgid "LoginAs"
msgstr "" msgstr ""
#: coco/interactive.py:313 #: coco/interactive.py:317
msgid "Comment" msgid "Comment"
msgstr "" msgstr ""
#: coco/interactive.py:322 #: coco/interactive.py:326
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}" msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "" msgstr ""
#: coco/interactive.py:394 #: coco/interactive.py:398
msgid "No Nodes" msgid "No Nodes"
msgstr "" msgstr ""
#: coco/interactive.py:398 #: coco/interactive.py:402
msgid "Node: [ ID.Name(Asset amount) ]" msgid "Node: [ ID.Name(Asset amount) ]"
msgstr "" msgstr ""
#: coco/interactive.py:400 #: coco/interactive.py:404
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1" msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "" msgstr ""
#: coco/interactive.py:408 #: coco/interactive.py:412
msgid "There is no matched node, please re-enter" msgid "There is no matched node, please re-enter"
msgstr "" msgstr ""
#: coco/interactive.py:438 #: coco/interactive.py:442
msgid "Select a login:: " msgid "Select a login:: "
msgstr "" msgstr ""
#: coco/interactive.py:461 #: coco/interactive.py:465
msgid "No system user" msgid "No system user"
msgstr "" msgstr ""
#: coco/models.py:247 #: coco/models.py:242
msgid ""
"Warning: Failed to load filter rule, please press Ctrl + D to exit retry."
msgstr ""
#: coco/models.py:251
msgid "Command `{}` is forbidden ........" msgid "Command `{}` is forbidden ........"
msgstr "" msgstr ""
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-18 20:04+0800\n" "POT-Creation-Date: 2018-12-21 16:48+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n" "PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n" "Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/zh\n" "Language-Team: Language locale/zh\n"
...@@ -91,75 +91,80 @@ msgstr "{T}0) 输入 {green}q{end} 退出.{R}" ...@@ -91,75 +91,80 @@ msgstr "{T}0) 输入 {green}q{end} 退出.{R}"
msgid "Terminal does not support login rdp, please use web terminal to access" msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问" msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:212 #: coco/interactive.py:217
msgid "No Assets" msgid "No Assets"
msgstr "没有资产" msgstr "没有资产"
#: coco/interactive.py:275 #: coco/interactive.py:280
msgid "Tips: Enter the asset ID and log directly into the asset." msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "提示: 输入资产ID,直接登录资产." msgstr "提示: 输入资产ID,直接登录资产."
#: coco/interactive.py:276 #: coco/interactive.py:281
msgid "Page up: P/p" msgid "Page up: P/p"
msgstr "上一页: P/p" msgstr "上一页: P/p"
#: coco/interactive.py:277 #: coco/interactive.py:282
msgid "Page down: Enter|N/n" msgid "Page down: Enter|N/n"
msgstr "下一页: Enter|N/n" msgstr "下一页: Enter|N/n"
#: coco/interactive.py:278 #: coco/interactive.py:283
msgid "BACK: b/q" msgid "BACK: b/q"
msgstr "返回: B/b" msgstr "返回: B/b"
#: coco/interactive.py:299 #: coco/interactive.py:303
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: coco/interactive.py:299 #: coco/interactive.py:303
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: coco/interactive.py:299 #: coco/interactive.py:303
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#: coco/interactive.py:299 #: coco/interactive.py:303
msgid "LoginAs" msgid "LoginAs"
msgstr "登录用户" msgstr "登录用户"
#: coco/interactive.py:313 #: coco/interactive.py:317
msgid "Comment" msgid "Comment"
msgstr "备注" msgstr "备注"
#: coco/interactive.py:322 #: coco/interactive.py:326
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}" msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}" msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}"
#: coco/interactive.py:394 #: coco/interactive.py:398
msgid "No Nodes" msgid "No Nodes"
msgstr "没有节点" msgstr "没有节点"
#: coco/interactive.py:398 #: coco/interactive.py:402
msgid "Node: [ ID.Name(Asset amount) ]" msgid "Node: [ ID.Name(Asset amount) ]"
msgstr "节点: [ ID.名称(资产数量) ]" msgstr "节点: [ ID.名称(资产数量) ]"
#: coco/interactive.py:400 #: coco/interactive.py:404
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1" msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "提示: 输入 g+节点ID 显示节点下主机. 如: g1" msgstr "提示: 输入 g+节点ID 显示节点下主机. 如: g1"
#: coco/interactive.py:408 #: coco/interactive.py:412
msgid "There is no matched node, please re-enter" msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入" msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:438 #: coco/interactive.py:442
msgid "Select a login:: " msgid "Select a login:: "
msgstr "选择一个登录:" msgstr "选择一个登录:"
#: coco/interactive.py:461 #: coco/interactive.py:465
msgid "No system user" msgid "No system user"
msgstr "没有系统用户" msgstr "没有系统用户"
#: coco/models.py:247 #: coco/models.py:242
msgid ""
"Warning: Failed to load filter rule, please press Ctrl + D to exit retry."
msgstr "警告: 加载过滤规则失败,请按 Ctrl + D 退出重试."
#: coco/models.py:251
msgid "Command `{}` is forbidden ........" msgid "Command `{}` is forbidden ........"
msgstr "命令 `{}` 是被禁止的 ..." msgstr "命令 `{}` 是被禁止的 ..."
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment